HHH-17164 - Proper, first-class soft-delete support
https://hibernate.atlassian.net/browse/HHH-17164
This commit is contained in:
parent
e323ec3d4a
commit
96a000e8ab
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.type.BooleanAsBooleanConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.PACKAGE;
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a soft-delete indicator mapping.
|
||||||
|
* <p/>
|
||||||
|
* Soft deletes handle "deletions" from a database table by setting a column in
|
||||||
|
* the table to indicate deletion.
|
||||||
|
* <p/>
|
||||||
|
* May be defined at various levels<ul>
|
||||||
|
* <li>
|
||||||
|
* {@linkplain ElementType#PACKAGE PACKAGE}, where it applies to all
|
||||||
|
* mappings defined in the package, unless defined more specifically.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@linkplain ElementType#TYPE TYPE}, where it applies to an entity hierarchy.
|
||||||
|
* The annotation must be defined on the root of the hierarchy and affects to the
|
||||||
|
* hierarchy as a whole. The soft-delete column is assumed to be on the hierarchy's
|
||||||
|
* root table.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@linkplain ElementType#FIELD FIELD} / {@linkplain ElementType#METHOD METHOD}, where
|
||||||
|
* it applies to the rows of an {@link jakarta.persistence.ElementCollection} or
|
||||||
|
* {@link jakarta.persistence.ManyToMany} table.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* {@linkplain ElementType#ANNOTATION_TYPE ANNOTATION_TYPE} on another annotation
|
||||||
|
* defined according to the previous targets.
|
||||||
|
* </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @since 6.4
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Target({PACKAGE, TYPE, FIELD, METHOD, ANNOTATION_TYPE})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface SoftDelete {
|
||||||
|
/**
|
||||||
|
* (Optional) The name of the column.
|
||||||
|
* <p/>
|
||||||
|
* Defaults to {@code deleted}.
|
||||||
|
*/
|
||||||
|
String columnName() default "deleted";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Optional) The Java type used for values when dealing with JDBC.
|
||||||
|
* This type should match the "relational type" of the specified
|
||||||
|
* {@linkplain #converter() converter}.
|
||||||
|
* <p/>
|
||||||
|
* By default, Hibernate will inspect the {@linkplain #converter() converter}
|
||||||
|
* and determine the proper type from its signature.
|
||||||
|
*
|
||||||
|
* @apiNote Sometimes useful since {@linkplain #converter() converter}
|
||||||
|
* signatures are not required to be parameterized.
|
||||||
|
*/
|
||||||
|
Class<?> jdbcType() default void.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Optional) Conversion to apply to determine the appropriate value to
|
||||||
|
* store in the database. The "domain representation" can be: <dl>
|
||||||
|
* <dt>{@code true}</dt>
|
||||||
|
* <dd>Indicates that the row is considered deleted</dd>
|
||||||
|
*
|
||||||
|
* <dt>{@code false}</dt>
|
||||||
|
* <dd>Indicates that the row is considered NOT deleted</dd>
|
||||||
|
* </dl>
|
||||||
|
* <p/>
|
||||||
|
* By default, values are stored as booleans in the database according to
|
||||||
|
* the {@linkplain Dialect#getPreferredSqlTypeCodeForBoolean() dialect}
|
||||||
|
* and {@linkplain org.hibernate.cfg.MappingSettings#PREFERRED_BOOLEAN_JDBC_TYPE settings}
|
||||||
|
*
|
||||||
|
* @apiNote The converter should never return {@code null}
|
||||||
|
*/
|
||||||
|
Class<? extends AttributeConverter<Boolean,?>> converter() default BooleanAsBooleanConverter.class;
|
||||||
|
}
|
|
@ -36,12 +36,15 @@ import org.hibernate.annotations.OnDeleteAction;
|
||||||
import org.hibernate.annotations.SqlFragmentAlias;
|
import org.hibernate.annotations.SqlFragmentAlias;
|
||||||
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
|
import org.hibernate.annotations.common.reflection.XPackage;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
|
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||||
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
||||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.boot.spi.PropertyData;
|
import org.hibernate.boot.spi.PropertyData;
|
||||||
import org.hibernate.dialect.DatabaseVersion;
|
import org.hibernate.dialect.DatabaseVersion;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.mapping.Any;
|
import org.hibernate.mapping.Any;
|
||||||
import org.hibernate.mapping.AttributeContainer;
|
import org.hibernate.mapping.AttributeContainer;
|
||||||
import org.hibernate.mapping.BasicValue;
|
import org.hibernate.mapping.BasicValue;
|
||||||
|
@ -69,6 +72,7 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnOrFor
|
||||||
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
|
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
|
||||||
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
||||||
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
||||||
|
import static org.hibernate.internal.util.StringHelper.qualifier;
|
||||||
import static org.hibernate.internal.util.StringHelper.qualify;
|
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||||
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED;
|
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED;
|
||||||
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP;
|
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP;
|
||||||
|
@ -1120,4 +1124,41 @@ public class BinderHelper {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract an annotation from the package-info for the package the given class is defined in
|
||||||
|
*
|
||||||
|
* @param annotationType The type of annotation to return
|
||||||
|
* @param xClass The class in the package
|
||||||
|
* @param context The processing context
|
||||||
|
*
|
||||||
|
* @return The annotation or {@code null}
|
||||||
|
*/
|
||||||
|
public static <A extends Annotation> A extractFromPackage(
|
||||||
|
Class<A> annotationType,
|
||||||
|
XClass xClass,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
|
||||||
|
// todo (soft-delete) : or if we want caching of this per package
|
||||||
|
// +
|
||||||
|
// final SoftDelete fromPackage = context.getMetadataCollector().resolvePackageAnnotation( packageName, SoftDelete.class );
|
||||||
|
// +
|
||||||
|
// where context.getMetadataCollector() can cache some of this - either the annotations themselves
|
||||||
|
// or even just the XPackage resolutions
|
||||||
|
|
||||||
|
final String declaringClassName = xClass.getName();
|
||||||
|
final String packageName = qualifier( declaringClassName );
|
||||||
|
if ( isNotEmpty( packageName ) ) {
|
||||||
|
final ClassLoaderService classLoaderService = context.getBootstrapContext()
|
||||||
|
.getServiceRegistry()
|
||||||
|
.getService( ClassLoaderService.class );
|
||||||
|
assert classLoaderService != null;
|
||||||
|
final Package declaringClassPackage = classLoaderService.packageForNameOrNull( packageName );
|
||||||
|
if ( declaringClassPackage != null ) {
|
||||||
|
// will be null when there is no `package-info.class`
|
||||||
|
final XPackage xPackage = context.getBootstrapContext().getReflectionManager().toXPackage( declaringClassPackage );
|
||||||
|
return xPackage.getAnnotation( annotationType );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ import org.hibernate.annotations.SQLOrder;
|
||||||
import org.hibernate.annotations.SQLRestriction;
|
import org.hibernate.annotations.SQLRestriction;
|
||||||
import org.hibernate.annotations.SQLSelect;
|
import org.hibernate.annotations.SQLSelect;
|
||||||
import org.hibernate.annotations.SQLUpdate;
|
import org.hibernate.annotations.SQLUpdate;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
import org.hibernate.annotations.SortComparator;
|
import org.hibernate.annotations.SortComparator;
|
||||||
import org.hibernate.annotations.SortNatural;
|
import org.hibernate.annotations.SortNatural;
|
||||||
import org.hibernate.annotations.Synchronize;
|
import org.hibernate.annotations.Synchronize;
|
||||||
|
@ -94,6 +95,7 @@ import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.mapping.Any;
|
import org.hibernate.mapping.Any;
|
||||||
import org.hibernate.mapping.Backref;
|
import org.hibernate.mapping.Backref;
|
||||||
|
import org.hibernate.mapping.BasicValue;
|
||||||
import org.hibernate.mapping.CheckConstraint;
|
import org.hibernate.mapping.CheckConstraint;
|
||||||
import org.hibernate.mapping.Collection;
|
import org.hibernate.mapping.Collection;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
|
@ -110,6 +112,7 @@ import org.hibernate.mapping.SimpleValue;
|
||||||
import org.hibernate.mapping.Table;
|
import org.hibernate.mapping.Table;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
import org.hibernate.metamodel.CollectionClassification;
|
import org.hibernate.metamodel.CollectionClassification;
|
||||||
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
|
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.resource.beans.spi.ManagedBean;
|
import org.hibernate.resource.beans.spi.ManagedBean;
|
||||||
|
@ -170,6 +173,7 @@ import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
|
||||||
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
|
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
|
||||||
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
|
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
|
||||||
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
|
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
|
||||||
|
import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage;
|
||||||
import static org.hibernate.boot.model.source.internal.hbm.ModelBinder.useEntityWhereClauseForCollections;
|
import static org.hibernate.boot.model.source.internal.hbm.ModelBinder.useEntityWhereClauseForCollections;
|
||||||
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
||||||
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
|
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
|
||||||
|
@ -423,6 +427,13 @@ public abstract class CollectionBinder {
|
||||||
+ annotationName( oneToMany, manyToMany, elementCollection ));
|
+ annotationName( oneToMany, manyToMany, elementCollection ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( oneToMany != null && property.isAnnotationPresent( SoftDelete.class ) ) {
|
||||||
|
throw new UnsupportedMappingException(
|
||||||
|
"@SoftDelete cannot be applied to @OneToMany - " +
|
||||||
|
property.getDeclaringClass().getName() + "." + property.getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ( property.isAnnotationPresent( OrderColumn.class )
|
if ( property.isAnnotationPresent( OrderColumn.class )
|
||||||
&& manyToMany != null && !manyToMany.mappedBy().isEmpty() ) {
|
&& manyToMany != null && !manyToMany.mappedBy().isEmpty() ) {
|
||||||
throw new AnnotationException("Collection '" + getPath( propertyHolder, inferredData ) +
|
throw new AnnotationException("Collection '" + getPath( propertyHolder, inferredData ) +
|
||||||
|
@ -2475,6 +2486,7 @@ public abstract class CollectionBinder {
|
||||||
final Table collectionTable = tableBinder.bind();
|
final Table collectionTable = tableBinder.bind();
|
||||||
collection.setCollectionTable( collectionTable );
|
collection.setCollectionTable( collectionTable );
|
||||||
handleCheckConstraints( collectionTable );
|
handleCheckConstraints( collectionTable );
|
||||||
|
processSoftDeletes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCheckConstraints(Table collectionTable) {
|
private void handleCheckConstraints(Table collectionTable) {
|
||||||
|
@ -2500,6 +2512,31 @@ public abstract class CollectionBinder {
|
||||||
: new CheckConstraint( name, constraint ) );
|
: new CheckConstraint( name, constraint ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processSoftDeletes() {
|
||||||
|
assert collection.getCollectionTable() != null;
|
||||||
|
|
||||||
|
final SoftDelete softDelete = extractSoftDelete( property, propertyHolder, buildingContext );
|
||||||
|
if ( softDelete == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoftDeleteHelper.bindSoftDeleteIndicator(
|
||||||
|
softDelete,
|
||||||
|
collection,
|
||||||
|
collection.getCollectionTable(),
|
||||||
|
buildingContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SoftDelete extractSoftDelete(XProperty property, PropertyHolder propertyHolder, MetadataBuildingContext context) {
|
||||||
|
final SoftDelete fromProperty = property.getAnnotation( SoftDelete.class );
|
||||||
|
if ( fromProperty != null ) {
|
||||||
|
return fromProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractFromPackage( SoftDelete.class, property.getDeclaringClass(), context );
|
||||||
|
}
|
||||||
|
|
||||||
private void handleUnownedManyToMany(
|
private void handleUnownedManyToMany(
|
||||||
XClass elementType,
|
XClass elementType,
|
||||||
PersistentClass collectionEntity,
|
PersistentClass collectionEntity,
|
||||||
|
@ -2528,6 +2565,7 @@ public abstract class CollectionBinder {
|
||||||
// this is a ToOne with a @JoinTable or a regular property
|
// this is a ToOne with a @JoinTable or a regular property
|
||||||
: otherSidePropertyValue.getTable();
|
: otherSidePropertyValue.getTable();
|
||||||
collection.setCollectionTable( table );
|
collection.setCollectionTable( table );
|
||||||
|
processSoftDeletes();
|
||||||
|
|
||||||
if ( property.isAnnotationPresent( Checks.class )
|
if ( property.isAnnotationPresent( Checks.class )
|
||||||
|| property.isAnnotationPresent( Check.class ) ) {
|
|| property.isAnnotationPresent( Check.class ) ) {
|
||||||
|
|
|
@ -50,13 +50,14 @@ import org.hibernate.annotations.SQLDeleteAll;
|
||||||
import org.hibernate.annotations.SQLDeletes;
|
import org.hibernate.annotations.SQLDeletes;
|
||||||
import org.hibernate.annotations.SQLInsert;
|
import org.hibernate.annotations.SQLInsert;
|
||||||
import org.hibernate.annotations.SQLInserts;
|
import org.hibernate.annotations.SQLInserts;
|
||||||
|
import org.hibernate.annotations.SQLRestriction;
|
||||||
import org.hibernate.annotations.SQLSelect;
|
import org.hibernate.annotations.SQLSelect;
|
||||||
import org.hibernate.annotations.SQLUpdate;
|
import org.hibernate.annotations.SQLUpdate;
|
||||||
import org.hibernate.annotations.SQLUpdates;
|
import org.hibernate.annotations.SQLUpdates;
|
||||||
import org.hibernate.annotations.SQLRestriction;
|
|
||||||
import org.hibernate.annotations.SecondaryRow;
|
import org.hibernate.annotations.SecondaryRow;
|
||||||
import org.hibernate.annotations.SecondaryRows;
|
import org.hibernate.annotations.SecondaryRows;
|
||||||
import org.hibernate.annotations.SelectBeforeUpdate;
|
import org.hibernate.annotations.SelectBeforeUpdate;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
import org.hibernate.annotations.Subselect;
|
import org.hibernate.annotations.Subselect;
|
||||||
import org.hibernate.annotations.Synchronize;
|
import org.hibernate.annotations.Synchronize;
|
||||||
import org.hibernate.annotations.Tables;
|
import org.hibernate.annotations.Tables;
|
||||||
|
@ -146,6 +147,7 @@ import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClas
|
||||||
import static org.hibernate.boot.model.internal.PropertyBinder.hasIdAnnotation;
|
import static org.hibernate.boot.model.internal.PropertyBinder.hasIdAnnotation;
|
||||||
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
|
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
|
||||||
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
|
import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder;
|
||||||
|
import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage;
|
||||||
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
||||||
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
||||||
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
||||||
|
@ -214,6 +216,7 @@ public class EntityBinder {
|
||||||
final InheritanceState inheritanceState = inheritanceStates.get( clazzToProcess );
|
final InheritanceState inheritanceState = inheritanceStates.get( clazzToProcess );
|
||||||
final PersistentClass superEntity = getSuperEntity( clazzToProcess, inheritanceStates, context, inheritanceState );
|
final PersistentClass superEntity = getSuperEntity( clazzToProcess, inheritanceStates, context, inheritanceState );
|
||||||
detectedAttributeOverrideProblem( clazzToProcess, superEntity );
|
detectedAttributeOverrideProblem( clazzToProcess, superEntity );
|
||||||
|
|
||||||
final PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context );
|
final PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context );
|
||||||
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
||||||
entityBinder.bindEntity();
|
entityBinder.bindEntity();
|
||||||
|
@ -233,6 +236,7 @@ public class EntityBinder {
|
||||||
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
||||||
if ( persistentClass instanceof RootClass ) {
|
if ( persistentClass instanceof RootClass ) {
|
||||||
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
||||||
|
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, inheritanceState, context );
|
||||||
}
|
}
|
||||||
if ( persistentClass instanceof Subclass) {
|
if ( persistentClass instanceof Subclass) {
|
||||||
assert superEntity != null;
|
assert superEntity != null;
|
||||||
|
@ -248,6 +252,51 @@ public class EntityBinder {
|
||||||
entityBinder.callTypeBinders( persistentClass );
|
entityBinder.callTypeBinders( persistentClass );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void bindSoftDelete(
|
||||||
|
XClass xClass,
|
||||||
|
RootClass rootClass,
|
||||||
|
InheritanceState inheritanceState,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
// todo (soft-delete) : do we assume all package-level registrations are already available?
|
||||||
|
// or should this be a "second pass"?
|
||||||
|
|
||||||
|
final SoftDelete softDelete = extractSoftDelete( xClass, rootClass, inheritanceState, context );
|
||||||
|
if ( softDelete == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoftDeleteHelper.bindSoftDeleteIndicator(
|
||||||
|
softDelete,
|
||||||
|
rootClass,
|
||||||
|
rootClass.getRootTable(),
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SoftDelete extractSoftDelete(
|
||||||
|
XClass xClass,
|
||||||
|
RootClass rootClass,
|
||||||
|
InheritanceState inheritanceState,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
final SoftDelete fromClass = xClass.getAnnotation( SoftDelete.class );
|
||||||
|
if ( fromClass != null ) {
|
||||||
|
return fromClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
MappedSuperclass mappedSuperclass = rootClass.getSuperMappedSuperclass();
|
||||||
|
while ( mappedSuperclass != null ) {
|
||||||
|
// todo (soft-delete) : use XClass for MappedSuperclass? for the time being, just use the Java type
|
||||||
|
final SoftDelete fromMappedSuperclass = mappedSuperclass.getMappedClass().getAnnotation( SoftDelete.class );
|
||||||
|
if ( fromMappedSuperclass != null ) {
|
||||||
|
return fromMappedSuperclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedSuperclass = mappedSuperclass.getSuperMappedSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractFromPackage( SoftDelete.class, xClass, context );
|
||||||
|
}
|
||||||
|
|
||||||
private void handleCheckConstraints() {
|
private void handleCheckConstraints() {
|
||||||
if ( annotatedClass.isAnnotationPresent( Checks.class ) ) {
|
if ( annotatedClass.isAnnotationPresent( Checks.class ) ) {
|
||||||
// if we have more than one of them they are not overrideable :-/
|
// if we have more than one of them they are not overrideable :-/
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.boot.model.internal;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
|
||||||
|
import org.hibernate.boot.model.naming.Identifier;
|
||||||
|
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||||
|
import org.hibernate.boot.model.relational.Database;
|
||||||
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.mapping.BasicValue;
|
||||||
|
import org.hibernate.mapping.Column;
|
||||||
|
import org.hibernate.mapping.SoftDeletable;
|
||||||
|
import org.hibernate.mapping.Table;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeletableModelPart;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.SoftDeleteMappingImpl;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||||
|
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||||
|
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
||||||
|
|
||||||
|
import static org.hibernate.internal.util.StringHelper.coalesce;
|
||||||
|
import static org.hibernate.query.sqm.ComparisonOperator.EQUAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for dealing with {@link org.hibernate.annotations.SoftDelete}
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SoftDeleteHelper {
|
||||||
|
|
||||||
|
public static final String DEFAULT_COLUMN_NAME = "deleted";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and binds the column and value for modeling the soft-delete in the database
|
||||||
|
*
|
||||||
|
* @param softDeleteConfig The SoftDelete annotation
|
||||||
|
* @param target The thing which is to be soft-deleted
|
||||||
|
* @param table The table to which the soft-delete should be applied
|
||||||
|
* @param context The processing context for access to needed info and services
|
||||||
|
*/
|
||||||
|
public static void bindSoftDeleteIndicator(
|
||||||
|
SoftDelete softDeleteConfig,
|
||||||
|
SoftDeletable target,
|
||||||
|
Table table,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
final BasicValue softDeleteIndicatorValue = createSoftDeleteIndicatorValue( softDeleteConfig, table, context );
|
||||||
|
final Column softDeleteIndicatorColumn = createSoftDeleteIndicatorColumn(
|
||||||
|
softDeleteConfig,
|
||||||
|
softDeleteIndicatorValue,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
table.addColumn( softDeleteIndicatorColumn );
|
||||||
|
target.enableSoftDelete( softDeleteIndicatorColumn );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BasicValue createSoftDeleteIndicatorValue(
|
||||||
|
SoftDelete softDelete,
|
||||||
|
Table table,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
final ClassBasedConverterDescriptor converterDescriptor = new ClassBasedConverterDescriptor(
|
||||||
|
softDelete.converter(),
|
||||||
|
context.getBootstrapContext().getClassmateContext()
|
||||||
|
);
|
||||||
|
|
||||||
|
final BasicValue softDeleteIndicatorValue = new BasicValue( context, table );
|
||||||
|
softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
|
||||||
|
softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> {
|
||||||
|
return converterDescriptor.getRelationalValueResolvedType().getErasedType();
|
||||||
|
} );
|
||||||
|
return softDeleteIndicatorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Column createSoftDeleteIndicatorColumn(
|
||||||
|
SoftDelete softDeleteConfig,
|
||||||
|
BasicValue softDeleteIndicatorValue,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
final Column softDeleteColumn = new Column();
|
||||||
|
|
||||||
|
applyColumnName( softDeleteColumn, softDeleteConfig, context );
|
||||||
|
|
||||||
|
softDeleteColumn.setLength( 1 );
|
||||||
|
softDeleteColumn.setNullable( false );
|
||||||
|
softDeleteColumn.setUnique( false );
|
||||||
|
softDeleteColumn.setComment( "Soft-delete indicator" );
|
||||||
|
|
||||||
|
softDeleteColumn.setValue( softDeleteIndicatorValue );
|
||||||
|
softDeleteIndicatorValue.addColumn( softDeleteColumn );
|
||||||
|
|
||||||
|
return softDeleteColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyColumnName(
|
||||||
|
Column softDeleteColumn,
|
||||||
|
SoftDelete softDeleteConfig,
|
||||||
|
MetadataBuildingContext context) {
|
||||||
|
final Database database = context.getMetadataCollector().getDatabase();
|
||||||
|
final PhysicalNamingStrategy namingStrategy = context.getBuildingOptions().getPhysicalNamingStrategy();
|
||||||
|
final String logicalColumnName = softDeleteConfig == null
|
||||||
|
? DEFAULT_COLUMN_NAME
|
||||||
|
: coalesce( DEFAULT_COLUMN_NAME, softDeleteConfig.columnName() );
|
||||||
|
final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName(
|
||||||
|
database.toIdentifier( logicalColumnName ),
|
||||||
|
database.getJdbcEnvironment()
|
||||||
|
);
|
||||||
|
softDeleteColumn.setName( physicalColumnName.render( database.getDialect() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SoftDeleteMappingImpl resolveSoftDeleteMapping(
|
||||||
|
SoftDeletableModelPart softDeletableModelPart,
|
||||||
|
SoftDeletable bootMapping,
|
||||||
|
String tableName,
|
||||||
|
MappingModelCreationProcess creationProcess) {
|
||||||
|
return resolveSoftDeleteMapping(
|
||||||
|
softDeletableModelPart,
|
||||||
|
bootMapping,
|
||||||
|
tableName,
|
||||||
|
creationProcess.getCreationContext().getDialect()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SoftDeleteMappingImpl resolveSoftDeleteMapping(
|
||||||
|
SoftDeletableModelPart softDeletableModelPart,
|
||||||
|
SoftDeletable bootMapping,
|
||||||
|
String tableName,
|
||||||
|
Dialect dialect) {
|
||||||
|
final Column softDeleteColumn = bootMapping.getSoftDeleteColumn();
|
||||||
|
if ( softDeleteColumn == null ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BasicValue columnValue = (BasicValue) softDeleteColumn.getValue();
|
||||||
|
final BasicValue.Resolution<?> resolution = columnValue.resolve();
|
||||||
|
//noinspection unchecked
|
||||||
|
final BasicValueConverter<Boolean, Object> converter = (BasicValueConverter<Boolean, Object>) resolution.getValueConverter();
|
||||||
|
//noinspection unchecked
|
||||||
|
final JdbcLiteralFormatter<Object> literalFormatter = resolution.getJdbcMapping().getJdbcLiteralFormatter();
|
||||||
|
|
||||||
|
final Object deletedLiteralValue = converter.toRelationalValue( true );
|
||||||
|
final Object nonDeletedLiteralValue = converter.toRelationalValue( false );
|
||||||
|
|
||||||
|
return new SoftDeleteMappingImpl(
|
||||||
|
softDeletableModelPart,
|
||||||
|
softDeleteColumn.getName(),
|
||||||
|
tableName,
|
||||||
|
deletedLiteralValue,
|
||||||
|
literalFormatter.toJdbcLiteral( deletedLiteralValue, dialect, null ),
|
||||||
|
nonDeletedLiteralValue,
|
||||||
|
literalFormatter.toJdbcLiteral( nonDeletedLiteralValue, dialect, null ),
|
||||||
|
resolution.getJdbcMapping()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL AST Predicate for restricting matches to non-deleted rows
|
||||||
|
*
|
||||||
|
* @param tableReference The table reference for the table containing the soft-delete column
|
||||||
|
* @param softDeleteMapping The soft-delete mapping
|
||||||
|
*/
|
||||||
|
public static Predicate createNonSoftDeletedRestriction(
|
||||||
|
TableReference tableReference,
|
||||||
|
SoftDeleteMapping softDeleteMapping) {
|
||||||
|
final ColumnReference softDeleteColumn = new ColumnReference( tableReference, softDeleteMapping );
|
||||||
|
final JdbcLiteral<?> notDeletedLiteral = new JdbcLiteral<>(
|
||||||
|
softDeleteMapping.getNonDeletedLiteralValue(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
return new ComparisonPredicate( softDeleteColumn, EQUAL, notDeletedLiteral );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL AST Predicate for restricting matches to non-deleted rows
|
||||||
|
*
|
||||||
|
* @param tableReference The table reference for the table containing the soft-delete column
|
||||||
|
* @param softDeleteMapping The soft-delete mapping
|
||||||
|
*/
|
||||||
|
public static Predicate createNonSoftDeletedRestriction(
|
||||||
|
TableReference tableReference,
|
||||||
|
SoftDeleteMapping softDeleteMapping,
|
||||||
|
SqlExpressionResolver expressionResolver) {
|
||||||
|
final Expression softDeleteColumn = expressionResolver.resolveSqlExpression( tableReference, softDeleteMapping );
|
||||||
|
final JdbcLiteral<?> notDeletedLiteral = new JdbcLiteral<>(
|
||||||
|
softDeleteMapping.getNonDeletedLiteralValue(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
return new ComparisonPredicate( softDeleteColumn, EQUAL, notDeletedLiteral );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL AST Assignment for setting the soft-delete column to its
|
||||||
|
* deleted indicate value
|
||||||
|
*
|
||||||
|
* @param tableReference The table reference for the table containing the soft-delete column
|
||||||
|
* @param softDeleteMapping The soft-delete mapping
|
||||||
|
*/
|
||||||
|
public static Assignment createSoftDeleteAssignment(
|
||||||
|
TableReference tableReference,
|
||||||
|
SoftDeleteMapping softDeleteMapping) {
|
||||||
|
final ColumnReference softDeleteColumn = new ColumnReference( tableReference, softDeleteMapping );
|
||||||
|
final JdbcLiteral<?> softDeleteIndicator = new JdbcLiteral<>(
|
||||||
|
softDeleteMapping.getDeletedLiteralValue(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
return new Assignment( softDeleteColumn, softDeleteIndicator );
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.loader.internal.AliasConstantsHelper;
|
import org.hibernate.loader.internal.AliasConstantsHelper;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public final class StringHelper {
|
public final class StringHelper {
|
||||||
|
@ -854,6 +855,22 @@ public final class StringHelper {
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String coalesce(@NonNull String fallbackValue, @NonNull String... values) {
|
||||||
|
for ( int i = 0; i < values.length; i++ ) {
|
||||||
|
if ( isNotEmpty( values[i] ) ) {
|
||||||
|
return values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String coalesce(@NonNull String fallbackValue, String value) {
|
||||||
|
if ( isNotEmpty( value ) ) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
public interface Renderer<T> {
|
public interface Renderer<T> {
|
||||||
String render(T value);
|
String render(T value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable;
|
import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
|
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
|
||||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||||
|
@ -95,6 +97,10 @@ public class CollectionBatchLoaderArrayParam
|
||||||
getSessionFactory()
|
getSessionFactory()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final QuerySpec querySpec = sqlSelect.getQueryPart().getFirstQuerySpec();
|
||||||
|
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||||
|
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||||
|
|
||||||
jdbcSelectOperation = getSessionFactory().getJdbcServices()
|
jdbcSelectOperation = getSessionFactory().getJdbcServices()
|
||||||
.getJdbcEnvironment()
|
.getJdbcEnvironment()
|
||||||
.getSqlAstTranslatorFactory()
|
.getSqlAstTranslatorFactory()
|
||||||
|
|
|
@ -16,6 +16,8 @@ import org.hibernate.loader.ast.spi.CollectionBatchLoader;
|
||||||
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
|
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
|
@ -71,6 +73,11 @@ public class CollectionBatchLoaderInPredicate
|
||||||
jdbcParametersBuilder::add,
|
jdbcParametersBuilder::add,
|
||||||
sessionFactory
|
sessionFactory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final QuerySpec querySpec = sqlAst.getQueryPart().getFirstQuerySpec();
|
||||||
|
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||||
|
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||||
|
|
||||||
this.jdbcParameters = jdbcParametersBuilder.build();
|
this.jdbcParameters = jdbcParametersBuilder.build();
|
||||||
assert this.jdbcParameters.size() == this.sqlBatchSize * this.keyColumnCount;
|
assert this.jdbcParameters.size() == this.sqlBatchSize * this.keyColumnCount;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.loader.ast.internal;
|
||||||
|
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
import org.hibernate.engine.spi.CollectionKey;
|
import org.hibernate.engine.spi.CollectionKey;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
|
@ -19,7 +18,9 @@ import org.hibernate.engine.spi.SubselectFetch;
|
||||||
import org.hibernate.loader.ast.spi.CollectionLoader;
|
import org.hibernate.loader.ast.spi.CollectionLoader;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
import org.hibernate.sql.ast.tree.from.FromClause;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||||
import org.hibernate.sql.exec.internal.BaseExecutionContext;
|
import org.hibernate.sql.exec.internal.BaseExecutionContext;
|
||||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||||
|
@ -64,6 +65,12 @@ public class CollectionLoaderSingleKey implements CollectionLoader {
|
||||||
jdbcParametersBuilder::add,
|
jdbcParametersBuilder::add,
|
||||||
sessionFactory
|
sessionFactory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final QuerySpec querySpec = sqlAst.getQueryPart().getFirstQuerySpec();
|
||||||
|
final FromClause fromClause = querySpec.getFromClause();
|
||||||
|
final TableGroup tableGroup = fromClause.getRoots().get( 0 );
|
||||||
|
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||||
|
|
||||||
this.jdbcParameters = jdbcParametersBuilder.build();
|
this.jdbcParameters = jdbcParametersBuilder.build();
|
||||||
this.jdbcSelect = sessionFactory.getJdbcServices()
|
this.jdbcSelect = sessionFactory.getJdbcServices()
|
||||||
.getJdbcEnvironment()
|
.getJdbcEnvironment()
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.hibernate.loader.ast.spi.CollectionLoader;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.query.spi.QueryOptions;
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
|
@ -61,6 +63,10 @@ public class CollectionLoaderSubSelectFetch implements CollectionLoader {
|
||||||
jdbcParameter -> {},
|
jdbcParameter -> {},
|
||||||
session.getFactory()
|
session.getFactory()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final QuerySpec querySpec = sqlAst.getQueryPart().getFirstQuerySpec();
|
||||||
|
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||||
|
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -77,7 +77,6 @@ public class EntityBatchLoaderInPredicate<T>
|
||||||
final EntityIdentifierMapping identifierMapping = getLoadable().getIdentifierMapping();
|
final EntityIdentifierMapping identifierMapping = getLoadable().getIdentifierMapping();
|
||||||
|
|
||||||
final int expectedNumberOfParameters = identifierMapping.getJdbcTypeCount() * sqlBatchSize;
|
final int expectedNumberOfParameters = identifierMapping.getJdbcTypeCount() * sqlBatchSize;
|
||||||
|
|
||||||
final JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder( expectedNumberOfParameters );
|
final JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder( expectedNumberOfParameters );
|
||||||
sqlAst = LoaderSelectBuilder.createSelect(
|
sqlAst = LoaderSelectBuilder.createSelect(
|
||||||
getLoadable(),
|
getLoadable(),
|
||||||
|
@ -177,7 +176,7 @@ public class EntityBatchLoaderInPredicate<T>
|
||||||
getLoadable().getEntityName(),
|
getLoadable().getEntityName(),
|
||||||
pkValue,
|
pkValue,
|
||||||
startIndex,
|
startIndex,
|
||||||
startIndex + ( sqlBatchSize -1)
|
startIndex + ( sqlBatchSize - 1 )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -187,120 +186,8 @@ public class EntityBatchLoaderInPredicate<T>
|
||||||
},
|
},
|
||||||
session
|
session
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// int numberOfIdsLeft = idsToInitialize.length;
|
|
||||||
// int start = 0;
|
|
||||||
// while ( numberOfIdsLeft > 0 ) {
|
|
||||||
// if ( MULTI_KEY_LOAD_DEBUG_ENABLED ) {
|
|
||||||
// MULTI_KEY_LOAD_LOGGER.debugf( "Processing batch-fetch chunk (`%s#%s`) %s - %s", getLoadable().getEntityName(), pkValue, start, start + ( sqlBatchSize -1) );
|
|
||||||
// }
|
|
||||||
// initializeChunk( idsToInitialize, start, pkValue, entityInstance, lockOptions, readOnly, session );
|
|
||||||
//
|
|
||||||
// start += sqlBatchSize;
|
|
||||||
// numberOfIdsLeft -= sqlBatchSize;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void initializeChunk(
|
|
||||||
// Object[] idsToInitialize,
|
|
||||||
// int start,
|
|
||||||
// Object pkValue,
|
|
||||||
// Object entityInstance,
|
|
||||||
// LockOptions lockOptions,
|
|
||||||
// Boolean readOnly,
|
|
||||||
// SharedSessionContractImplementor session) {
|
|
||||||
// initializeChunk(
|
|
||||||
// idsToInitialize,
|
|
||||||
// getLoadable(),
|
|
||||||
// start,
|
|
||||||
// sqlBatchSize,
|
|
||||||
// jdbcParameters,
|
|
||||||
// sqlAst,
|
|
||||||
// jdbcSelectOperation,
|
|
||||||
// pkValue,
|
|
||||||
// entityInstance,
|
|
||||||
// lockOptions,
|
|
||||||
// readOnly,
|
|
||||||
// session
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private static void initializeChunk(
|
|
||||||
// Object[] idsToInitialize,
|
|
||||||
// EntityMappingType entityMapping,
|
|
||||||
// int startIndex,
|
|
||||||
// int numberOfKeys,
|
|
||||||
// List<JdbcParameter> jdbcParameters,
|
|
||||||
// SelectStatement sqlAst,
|
|
||||||
// JdbcOperationQuerySelect jdbcSelectOperation,
|
|
||||||
// Object pkValue,
|
|
||||||
// Object entityInstance,
|
|
||||||
// LockOptions lockOptions,
|
|
||||||
// Boolean readOnly,
|
|
||||||
// SharedSessionContractImplementor session) {
|
|
||||||
// final BatchFetchQueue batchFetchQueue = session.getPersistenceContext().getBatchFetchQueue();
|
|
||||||
//
|
|
||||||
// final int numberOfJdbcParameters = entityMapping.getIdentifierMapping().getJdbcTypeCount() * numberOfKeys;
|
|
||||||
// final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( numberOfJdbcParameters );
|
|
||||||
//
|
|
||||||
// final List<EntityKey> entityKeys = arrayList( numberOfKeys );
|
|
||||||
// int bindCount = 0;
|
|
||||||
// for ( int i = 0; i < numberOfKeys; i++ ) {
|
|
||||||
// final int idPosition = i + startIndex;
|
|
||||||
// final Object value;
|
|
||||||
// if ( idPosition >= idsToInitialize.length ) {
|
|
||||||
// value = null;
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// value = idsToInitialize[idPosition];
|
|
||||||
// }
|
|
||||||
// if ( value != null ) {
|
|
||||||
// entityKeys.add( session.generateEntityKey( value, entityMapping.getEntityPersister() ) );
|
|
||||||
// }
|
|
||||||
// bindCount += jdbcParameterBindings.registerParametersForEachJdbcValue(
|
|
||||||
// value,
|
|
||||||
// bindCount,
|
|
||||||
// entityMapping.getIdentifierMapping(),
|
|
||||||
// jdbcParameters,
|
|
||||||
// session
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// assert bindCount == jdbcParameters.size();
|
|
||||||
//
|
|
||||||
// if ( entityKeys.isEmpty() ) {
|
|
||||||
// // there are no non-null keys in the chunk
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Create a SubselectFetch.RegistrationHandler for handling any subselect fetches we encounter here
|
|
||||||
// final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler(
|
|
||||||
// batchFetchQueue,
|
|
||||||
// sqlAst,
|
|
||||||
// jdbcParameters,
|
|
||||||
// jdbcParameterBindings
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// session.getJdbcServices().getJdbcSelectExecutor().list(
|
|
||||||
// jdbcSelectOperation,
|
|
||||||
// jdbcParameterBindings,
|
|
||||||
// new SingleIdExecutionContext(
|
|
||||||
// pkValue,
|
|
||||||
// entityInstance,
|
|
||||||
// entityMapping.getRootEntityDescriptor(),
|
|
||||||
// readOnly,
|
|
||||||
// lockOptions,
|
|
||||||
// subSelectFetchableKeysHandler,
|
|
||||||
// session
|
|
||||||
// ),
|
|
||||||
// RowTransformerStandardImpl.instance(),
|
|
||||||
// ListResultsConsumer.UniqueSemantic.FILTER
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// entityKeys.forEach( batchFetchQueue::removeBatchLoadableEntityKey );
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
|
|
@ -41,7 +41,7 @@ import org.hibernate.usertype.UserCollectionType;
|
||||||
*
|
*
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public abstract class Collection implements Fetchable, Value, Filterable {
|
public abstract class Collection implements Fetchable, Value, Filterable, SoftDeletable {
|
||||||
|
|
||||||
public static final String DEFAULT_ELEMENT_COLUMN_NAME = "elt";
|
public static final String DEFAULT_ELEMENT_COLUMN_NAME = "elt";
|
||||||
public static final String DEFAULT_KEY_COLUMN_NAME = "id";
|
public static final String DEFAULT_KEY_COLUMN_NAME = "id";
|
||||||
|
@ -99,6 +99,8 @@ public abstract class Collection implements Fetchable, Value, Filterable {
|
||||||
private boolean customDeleteAllCallable;
|
private boolean customDeleteAllCallable;
|
||||||
private ExecuteUpdateResultCheckStyle deleteAllCheckStyle;
|
private ExecuteUpdateResultCheckStyle deleteAllCheckStyle;
|
||||||
|
|
||||||
|
private Column softDeleteColumn;
|
||||||
|
|
||||||
private String loaderName;
|
private String loaderName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -827,4 +829,14 @@ public abstract class Collection implements Fetchable, Value, Filterable {
|
||||||
public boolean isColumnUpdateable(int index) {
|
public boolean isColumnUpdateable(int index) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableSoftDelete(Column indicatorColumn) {
|
||||||
|
this.softDeleteColumn = indicatorColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Column getSoftDeleteColumn() {
|
||||||
|
return softDeleteColumn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
|
||||||
*
|
*
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public class RootClass extends PersistentClass implements TableOwner {
|
public class RootClass extends PersistentClass implements TableOwner, SoftDeletable {
|
||||||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( RootClass.class );
|
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( RootClass.class );
|
||||||
|
|
||||||
@Deprecated(since = "6.2") @Remove
|
@Deprecated(since = "6.2") @Remove
|
||||||
|
@ -58,6 +58,7 @@ public class RootClass extends PersistentClass implements TableOwner {
|
||||||
private int nextSubclassId;
|
private int nextSubclassId;
|
||||||
private Property declaredIdentifierProperty;
|
private Property declaredIdentifierProperty;
|
||||||
private Property declaredVersion;
|
private Property declaredVersion;
|
||||||
|
private Column softDeleteColumn;
|
||||||
|
|
||||||
public RootClass(MetadataBuildingContext buildingContext) {
|
public RootClass(MetadataBuildingContext buildingContext) {
|
||||||
super( buildingContext );
|
super( buildingContext );
|
||||||
|
@ -401,6 +402,16 @@ public class RootClass extends PersistentClass implements TableOwner {
|
||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableSoftDelete(Column indicatorColumn) {
|
||||||
|
this.softDeleteColumn = indicatorColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Column getSoftDeleteColumn() {
|
||||||
|
return softDeleteColumn;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object accept(PersistentClassVisitor mv) {
|
public Object accept(PersistentClassVisitor mv) {
|
||||||
return mv.accept( this );
|
return mv.accept( this );
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface SoftDeletable {
|
||||||
|
void enableSoftDelete(Column indicatorColumn);
|
||||||
|
Column getSoftDeleteColumn();
|
||||||
|
}
|
|
@ -9,6 +9,10 @@ package org.hibernate.metamodel;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.metamodel.mapping.NonTransientException;
|
import org.hibernate.metamodel.mapping.NonTransientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicated a problem with a mapping. Usually this is a problem with a combination
|
||||||
|
* of mapping constructs.
|
||||||
|
*/
|
||||||
public class UnsupportedMappingException extends HibernateException implements NonTransientException {
|
public class UnsupportedMappingException extends HibernateException implements NonTransientException {
|
||||||
public UnsupportedMappingException(String message) {
|
public UnsupportedMappingException(String message) {
|
||||||
super( message );
|
super( message );
|
||||||
|
|
|
@ -56,7 +56,8 @@ import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCH
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public interface EntityMappingType
|
public interface EntityMappingType
|
||||||
extends ManagedMappingType, EntityValuedModelPart, Loadable, Restrictable, Discriminable {
|
extends ManagedMappingType, EntityValuedModelPart, Loadable, Restrictable, Discriminable,
|
||||||
|
SoftDeletableModelPart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entity name.
|
* The entity name.
|
||||||
|
@ -356,6 +357,18 @@ public interface EntityMappingType
|
||||||
*/
|
*/
|
||||||
EntityRowIdMapping getRowIdMapping();
|
EntityRowIdMapping getRowIdMapping();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping for soft-delete support, or {@code null} if soft-delete not defined
|
||||||
|
*/
|
||||||
|
default SoftDeleteMapping getSoftDeleteMapping() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default TableDetails getSoftDeleteTableDetails() {
|
||||||
|
return getIdentifierTableDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Attribute mappings
|
// Attribute mappings
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public interface PluralAttributeMapping
|
public interface PluralAttributeMapping
|
||||||
extends AttributeMapping, TableGroupJoinProducer, FetchableContainer, Loadable, Restrictable {
|
extends AttributeMapping, TableGroupJoinProducer, FetchableContainer, Loadable, Restrictable, SoftDeletableModelPart {
|
||||||
|
|
||||||
CollectionPersister getCollectionDescriptor();
|
CollectionPersister getCollectionDescriptor();
|
||||||
|
|
||||||
|
@ -44,6 +44,13 @@ public interface PluralAttributeMapping
|
||||||
@Override
|
@Override
|
||||||
CollectionMappingType<?> getMappedType();
|
CollectionMappingType<?> getMappedType();
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PredicateConsumer {
|
||||||
|
void applyPredicate(Predicate predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applySoftDeleteRestrictions(TableGroup tableGroup, PredicateConsumer predicateConsumer);
|
||||||
|
|
||||||
interface IndexMetadata {
|
interface IndexMetadata {
|
||||||
CollectionPart getIndexDescriptor();
|
CollectionPart getIndexDescriptor();
|
||||||
int getListIndexBase();
|
int getListIndexBase();
|
||||||
|
@ -56,6 +63,13 @@ public interface PluralAttributeMapping
|
||||||
|
|
||||||
CollectionIdentifierDescriptor getIdentifierDescriptor();
|
CollectionIdentifierDescriptor getIdentifierDescriptor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping for soft-delete support, or {@code null} if soft-delete not defined
|
||||||
|
*/
|
||||||
|
default SoftDeleteMapping getSoftDeleteMapping() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
OrderByFragment getOrderByFragment();
|
OrderByFragment getOrderByFragment();
|
||||||
OrderByFragment getManyToManyOrderByFragment();
|
OrderByFragment getManyToManyOrderByFragment();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.metamodel.mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model part which can be soft-deleted
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface SoftDeletableModelPart extends ModelPartContainer {
|
||||||
|
/**
|
||||||
|
* Get the mapping of the soft-delete indicator
|
||||||
|
*/
|
||||||
|
SoftDeleteMapping getSoftDeleteMapping();
|
||||||
|
|
||||||
|
TableDetails getSoftDeleteTableDetails();
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.metamodel.mapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Metadata about the indicator column for entities and collections enabled
|
||||||
|
* for soft delete
|
||||||
|
*
|
||||||
|
* @see org.hibernate.annotations.SoftDelete
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface SoftDeleteMapping extends SelectableMapping, VirtualModelPart, SqlExpressible {
|
||||||
|
/**
|
||||||
|
* The name of the soft-delete indicator column.
|
||||||
|
*/
|
||||||
|
String getColumnName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the table which holds the {@linkplain #getColumnName() indicator column}
|
||||||
|
*/
|
||||||
|
String getTableName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQL literal value which indicates a deleted row
|
||||||
|
*/
|
||||||
|
Object getDeletedLiteralValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The String representation of the SQL literal value which indicates a deleted row
|
||||||
|
*/
|
||||||
|
String getDeletedLiteralText();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQL literal value which indicates a non-deleted row
|
||||||
|
*
|
||||||
|
* @apiNote The inverse of {@linkplain #getDeletedLiteralValue()}
|
||||||
|
*/
|
||||||
|
Object getNonDeletedLiteralValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The String representation of the SQL literal value which indicates a non-deleted row
|
||||||
|
*/
|
||||||
|
String getNonDeletedLiteralText();
|
||||||
|
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// SelectableMapping
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSelectionExpression() {
|
||||||
|
return getColumnName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSelectableName() {
|
||||||
|
return getColumnName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getWriteExpression() {
|
||||||
|
return getNonDeletedLiteralText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getContainingTableExpression() {
|
||||||
|
return getTableName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCustomReadExpression() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCustomWriteExpression() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isFormula() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isNullable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isInsertable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isUpdateable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isPartitioned() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getColumnDefinition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Long getLength() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Integer getPrecision() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Integer getScale() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ public interface TableDetails {
|
||||||
/**
|
/**
|
||||||
* Details about a column within the key group
|
* Details about a column within the key group
|
||||||
*/
|
*/
|
||||||
interface KeyColumn {
|
interface KeyColumn extends SelectableMapping {
|
||||||
/**
|
/**
|
||||||
* The name of the column
|
* The name of the column
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -678,7 +678,8 @@ public class MappingModelCreationHelper {
|
||||||
style,
|
style,
|
||||||
cascadeStyle,
|
cascadeStyle,
|
||||||
declaringType,
|
declaringType,
|
||||||
collectionDescriptor
|
collectionDescriptor,
|
||||||
|
creationProcess
|
||||||
) );
|
) );
|
||||||
|
|
||||||
creationProcess.registerInitializationCallback(
|
creationProcess.registerInitializationCallback(
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||||
import org.hibernate.cache.MutableCacheKeyBuilder;
|
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||||
import org.hibernate.engine.FetchStyle;
|
import org.hibernate.engine.FetchStyle;
|
||||||
import org.hibernate.engine.FetchTiming;
|
import org.hibernate.engine.FetchTiming;
|
||||||
|
@ -36,11 +37,14 @@ import org.hibernate.metamodel.mapping.ModelPart;
|
||||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
|
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
|
||||||
import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator;
|
import org.hibernate.metamodel.mapping.ordering.OrderByFragmentTranslator;
|
||||||
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
|
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
|
||||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
|
import org.hibernate.persister.collection.mutation.CollectionMutationTarget;
|
||||||
import org.hibernate.persister.entity.Joinable;
|
import org.hibernate.persister.entity.Joinable;
|
||||||
import org.hibernate.property.access.spi.PropertyAccess;
|
import org.hibernate.property.access.spi.PropertyAccess;
|
||||||
import org.hibernate.spi.NavigablePath;
|
import org.hibernate.spi.NavigablePath;
|
||||||
|
@ -71,6 +75,9 @@ import org.hibernate.sql.results.graph.collection.internal.SelectEagerCollection
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||||
|
import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -106,6 +113,8 @@ public class PluralAttributeMappingImpl
|
||||||
private final CollectionIdentifierDescriptor identifierDescriptor;
|
private final CollectionIdentifierDescriptor identifierDescriptor;
|
||||||
private final FetchTiming fetchTiming;
|
private final FetchTiming fetchTiming;
|
||||||
private final FetchStyle fetchStyle;
|
private final FetchStyle fetchStyle;
|
||||||
|
private final SoftDeleteMapping softDeleteMapping;
|
||||||
|
private Boolean hasSoftDelete;
|
||||||
|
|
||||||
private final String bidirectionalAttributeName;
|
private final String bidirectionalAttributeName;
|
||||||
|
|
||||||
|
@ -136,7 +145,8 @@ public class PluralAttributeMappingImpl
|
||||||
FetchStyle fetchStyle,
|
FetchStyle fetchStyle,
|
||||||
CascadeStyle cascadeStyle,
|
CascadeStyle cascadeStyle,
|
||||||
ManagedMappingType declaringType,
|
ManagedMappingType declaringType,
|
||||||
CollectionPersister collectionDescriptor) {
|
CollectionPersister collectionDescriptor,
|
||||||
|
MappingModelCreationProcess creationProcess) {
|
||||||
super( attributeName, fetchableIndex, declaringType );
|
super( attributeName, fetchableIndex, declaringType );
|
||||||
this.propertyAccess = propertyAccess;
|
this.propertyAccess = propertyAccess;
|
||||||
this.attributeMetadata = attributeMetadata;
|
this.attributeMetadata = attributeMetadata;
|
||||||
|
@ -193,9 +203,12 @@ public class PluralAttributeMappingImpl
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
softDeleteMapping = resolveSoftDeleteMapping( this, bootDescriptor, getSeparateCollectionTable(), creationProcess.getCreationContext().getDialect() );
|
||||||
|
|
||||||
injectAttributeMapping( elementDescriptor, indexDescriptor, collectionDescriptor, this );
|
injectAttributeMapping( elementDescriptor, indexDescriptor, collectionDescriptor, this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For Hibernate Reactive
|
* For Hibernate Reactive
|
||||||
*/
|
*/
|
||||||
|
@ -210,6 +223,8 @@ public class PluralAttributeMappingImpl
|
||||||
this.identifierDescriptor = original.identifierDescriptor;
|
this.identifierDescriptor = original.identifierDescriptor;
|
||||||
this.fetchTiming = original.fetchTiming;
|
this.fetchTiming = original.fetchTiming;
|
||||||
this.fetchStyle = original.fetchStyle;
|
this.fetchStyle = original.fetchStyle;
|
||||||
|
this.softDeleteMapping = original.softDeleteMapping;
|
||||||
|
this.hasSoftDelete = original.hasSoftDelete;
|
||||||
this.collectionDescriptor = original.collectionDescriptor;
|
this.collectionDescriptor = original.collectionDescriptor;
|
||||||
this.referencedPropertyName = original.referencedPropertyName;
|
this.referencedPropertyName = original.referencedPropertyName;
|
||||||
this.mapKeyPropertyName = original.mapKeyPropertyName;
|
this.mapKeyPropertyName = original.mapKeyPropertyName;
|
||||||
|
@ -335,6 +350,16 @@ public class PluralAttributeMappingImpl
|
||||||
return identifierDescriptor;
|
return identifierDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||||
|
return softDeleteMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDetails getSoftDeleteTableDetails() {
|
||||||
|
return ( (CollectionMutationTarget) getCollectionDescriptor() ).getCollectionTableMapping();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OrderByFragment getOrderByFragment() {
|
public OrderByFragment getOrderByFragment() {
|
||||||
return orderByFragment;
|
return orderByFragment;
|
||||||
|
@ -401,6 +426,39 @@ public class PluralAttributeMappingImpl
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applySoftDeleteRestrictions(TableGroup tableGroup, PredicateConsumer predicateConsumer) {
|
||||||
|
if ( !hasSoftDelete() ) {
|
||||||
|
// short-circuit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( getCollectionDescriptor().isOneToMany()
|
||||||
|
|| getCollectionDescriptor().isManyToMany() ) {
|
||||||
|
// see if the associated entity has soft-delete defined
|
||||||
|
final EntityCollectionPart elementDescriptor = (EntityCollectionPart) getElementDescriptor();
|
||||||
|
final EntityMappingType associatedEntityDescriptor = elementDescriptor.getAssociatedEntityMappingType();
|
||||||
|
final SoftDeleteMapping softDeleteMapping = associatedEntityDescriptor.getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final Predicate softDeleteRestriction = SoftDeleteHelper.createNonSoftDeletedRestriction(
|
||||||
|
tableGroup.resolveTableReference( associatedEntityDescriptor.getSoftDeleteTableDetails().getTableName() ),
|
||||||
|
softDeleteMapping
|
||||||
|
);
|
||||||
|
predicateConsumer.applyPredicate( softDeleteRestriction );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply the collection's soft-delete mapping, if one
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final Predicate softDeleteRestriction = SoftDeleteHelper.createNonSoftDeletedRestriction(
|
||||||
|
tableGroup.resolveTableReference( getSoftDeleteTableDetails().getTableName() ),
|
||||||
|
softDeleteMapping
|
||||||
|
);
|
||||||
|
predicateConsumer.applyPredicate( softDeleteRestriction );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> DomainResult<T> createDomainResult(
|
public <T> DomainResult<T> createDomainResult(
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
|
@ -643,6 +701,7 @@ public class PluralAttributeMappingImpl
|
||||||
boolean addsPredicate,
|
boolean addsPredicate,
|
||||||
SqlAstCreationState creationState) {
|
SqlAstCreationState creationState) {
|
||||||
final PredicateCollector predicateCollector = new PredicateCollector();
|
final PredicateCollector predicateCollector = new PredicateCollector();
|
||||||
|
|
||||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
lhs,
|
lhs,
|
||||||
|
@ -672,6 +731,12 @@ public class PluralAttributeMappingImpl
|
||||||
creationState
|
creationState
|
||||||
);
|
);
|
||||||
|
|
||||||
|
applySoftDeleteRestriction(
|
||||||
|
predicateCollector::applyPredicate,
|
||||||
|
tableGroup,
|
||||||
|
creationState
|
||||||
|
);
|
||||||
|
|
||||||
return new TableGroupJoin(
|
return new TableGroupJoin(
|
||||||
navigablePath,
|
navigablePath,
|
||||||
determineSqlJoinType( lhs, requestedJoinType, fetched ),
|
determineSqlJoinType( lhs, requestedJoinType, fetched ),
|
||||||
|
@ -680,6 +745,78 @@ public class PluralAttributeMappingImpl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasSoftDelete() {
|
||||||
|
// NOTE : this needs to be done lazily because the associated entity mapping (if one)
|
||||||
|
// does not know its SoftDeleteMapping yet when this is created
|
||||||
|
if ( hasSoftDelete == null ) {
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
hasSoftDelete = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( getElementDescriptor() instanceof EntityCollectionPart ) {
|
||||||
|
final EntityMappingType associatedEntityMapping = ( (EntityCollectionPart) getElementDescriptor() ).getAssociatedEntityMappingType();
|
||||||
|
hasSoftDelete = associatedEntityMapping.getSoftDeleteMapping() != null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hasSoftDelete = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSoftDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySoftDeleteRestriction(
|
||||||
|
Consumer<Predicate> predicateConsumer,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
SqlAstCreationState creationState) {
|
||||||
|
if ( !hasSoftDelete() ) {
|
||||||
|
// short circuit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( getElementDescriptor() instanceof EntityCollectionPart ) {
|
||||||
|
final EntityMappingType entityMappingType = ( (EntityCollectionPart) getElementDescriptor() ).getAssociatedEntityMappingType();
|
||||||
|
final SoftDeleteMapping softDeleteMapping = entityMappingType.getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final TableDetails softDeleteTable = entityMappingType.getSoftDeleteTableDetails();
|
||||||
|
predicateConsumer.accept( createNonSoftDeletedRestriction(
|
||||||
|
tableGroup.resolveTableReference( softDeleteTable.getTableName() ),
|
||||||
|
softDeleteMapping,
|
||||||
|
creationState.getSqlExpressionResolver()
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final TableDetails softDeleteTable = getSoftDeleteTableDetails();
|
||||||
|
predicateConsumer.accept( createNonSoftDeletedRestriction(
|
||||||
|
tableGroup.resolveTableReference( softDeleteTable.getTableName() ),
|
||||||
|
softDeleteMapping,
|
||||||
|
creationState.getSqlExpressionResolver()
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlAstJoinType determineSqlJoinType(TableGroup lhs, SqlAstJoinType requestedJoinType, boolean fetched) {
|
||||||
|
if ( hasSoftDelete() ) {
|
||||||
|
return SqlAstJoinType.LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( requestedJoinType == null ) {
|
||||||
|
if ( fetched ) {
|
||||||
|
return getDefaultSqlAstJoinType( lhs );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return SqlAstJoinType.INNER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return requestedJoinType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TableGroup createRootTableGroupJoin(
|
public TableGroup createRootTableGroupJoin(
|
||||||
NavigablePath navigablePath,
|
NavigablePath navigablePath,
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.metamodel.mapping.internal;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.internal.util.IndexedConsumer;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.MappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeletableModelPart;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
|
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||||
|
import org.hibernate.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
|
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||||
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SoftDeleteMapping implementation
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SoftDeleteMappingImpl implements SoftDeleteMapping {
|
||||||
|
public static final String ROLE_NAME = "{soft-delete}";
|
||||||
|
|
||||||
|
private final SoftDeletableModelPart softDeletable;
|
||||||
|
private final String columnName;
|
||||||
|
private final String tableName;
|
||||||
|
private final Object deletedLiteralValue;
|
||||||
|
private final String deletedLiteralText;
|
||||||
|
private final Object nonDeletedLiteralValue;
|
||||||
|
private final String nonDeletedLiteralText;
|
||||||
|
private final JdbcMapping jdbcMapping;
|
||||||
|
|
||||||
|
private final NavigableRole navigableRole;
|
||||||
|
|
||||||
|
public SoftDeleteMappingImpl(
|
||||||
|
SoftDeletableModelPart softDeletable,
|
||||||
|
String columnName,
|
||||||
|
String tableName,
|
||||||
|
Object deletedLiteralValue,
|
||||||
|
String deletedLiteralText,
|
||||||
|
Object nonDeletedLiteralValue,
|
||||||
|
String nonDeletedLiteralText,
|
||||||
|
JdbcMapping jdbcMapping) {
|
||||||
|
this.softDeletable = softDeletable;
|
||||||
|
this.columnName = columnName;
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.deletedLiteralValue = deletedLiteralValue;
|
||||||
|
this.deletedLiteralText = deletedLiteralText;
|
||||||
|
this.nonDeletedLiteralValue = nonDeletedLiteralValue;
|
||||||
|
this.nonDeletedLiteralText = nonDeletedLiteralText;
|
||||||
|
this.jdbcMapping = jdbcMapping;
|
||||||
|
|
||||||
|
this.navigableRole = softDeletable.getNavigableRole().append( ROLE_NAME );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getDeletedLiteralValue() {
|
||||||
|
return deletedLiteralValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeletedLiteralText() {
|
||||||
|
return deletedLiteralText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getNonDeletedLiteralValue() {
|
||||||
|
return nonDeletedLiteralValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNonDeletedLiteralText() {
|
||||||
|
return nonDeletedLiteralText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JdbcMapping getJdbcMapping() {
|
||||||
|
return jdbcMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPartName() {
|
||||||
|
return ROLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NavigableRole getNavigableRole() {
|
||||||
|
return navigableRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
|
||||||
|
action.accept( offset, jdbcMapping );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object disassemble(Object value, SharedSessionContractImplementor session) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <X, Y> int forEachDisassembledJdbcValue(
|
||||||
|
Object value,
|
||||||
|
int offset,
|
||||||
|
X x,
|
||||||
|
Y y,
|
||||||
|
JdbcValuesBiConsumer<X, Y> valuesConsumer,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
valuesConsumer.consume( offset, x, y, value, getJdbcMapping() );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MappingType getPartMappingType() {
|
||||||
|
return jdbcMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType<?> getJavaType() {
|
||||||
|
return jdbcMapping.getMappedJavaType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPartitionedSelectionMapping() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> DomainResult<T> createDomainResult(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
String resultVariable,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||||
|
return new BasicResult<>( sqlSelection.getValuesArrayPosition(), resultVariable, getJdbcMapping() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlSelection resolveSqlSelection(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
final TableDetails indicatorTable = softDeletable.getSoftDeleteTableDetails();
|
||||||
|
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||||
|
navigablePath.getRealParent(),
|
||||||
|
indicatorTable.getTableName()
|
||||||
|
);
|
||||||
|
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
|
||||||
|
final SqlSelection sqlSelection = expressionResolver.resolveSqlSelection(
|
||||||
|
expressionResolver.resolveSqlExpression( tableReference, this ),
|
||||||
|
getJavaType(),
|
||||||
|
null,
|
||||||
|
creationState.getSqlAstCreationState().getCreationContext().getMappingMetamodel().getTypeConfiguration()
|
||||||
|
);
|
||||||
|
return sqlSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applySqlSelections(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applySqlSelections(
|
||||||
|
NavigablePath navigablePath,
|
||||||
|
TableGroup tableGroup,
|
||||||
|
DomainResultCreationState creationState,
|
||||||
|
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
|
||||||
|
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||||
|
selectionConsumer.accept( sqlSelection, getJdbcMapping() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <X, Y> int breakDownJdbcValues(
|
||||||
|
Object domainValue,
|
||||||
|
int offset,
|
||||||
|
X x,
|
||||||
|
Y y,
|
||||||
|
JdbcValueBiConsumer<X, Y> valueConsumer,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityMappingType findContainingEntityMapping() {
|
||||||
|
return softDeletable.findContainingEntityMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SoftDeleteMappingImpl(" + tableName + "." + columnName + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -34,6 +35,7 @@ import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.Selectable;
|
import org.hibernate.mapping.Selectable;
|
||||||
import org.hibernate.mapping.ToOne;
|
import org.hibernate.mapping.ToOne;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
import org.hibernate.metamodel.mapping.AssociationKey;
|
import org.hibernate.metamodel.mapping.AssociationKey;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMetadata;
|
import org.hibernate.metamodel.mapping.AttributeMetadata;
|
||||||
|
@ -52,6 +54,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectablePath;
|
import org.hibernate.metamodel.mapping.SelectablePath;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
||||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||||
|
@ -102,6 +105,8 @@ import org.hibernate.type.EmbeddedComponentType;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -413,6 +418,18 @@ public class ToOneAttributeMapping
|
||||||
isInternalLoadNullable = isNullable();
|
isInternalLoadNullable = isNullable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( entityMappingType.getSoftDeleteMapping() != null ) {
|
||||||
|
// cannot be lazy
|
||||||
|
if ( getTiming() == FetchTiming.DELAYED ) {
|
||||||
|
throw new UnsupportedMappingException( String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"To-one attribute (%s.%s) cannot be mapped as LAZY as its associated entity is defined with @SoftDelete",
|
||||||
|
declaringType.getPartName(),
|
||||||
|
getAttributeName()
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( referencedPropertyName == null ) {
|
if ( referencedPropertyName == null ) {
|
||||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||||
targetKeyPropertyNames.add( EntityIdentifierMapping.ID_ROLE_NAME );
|
targetKeyPropertyNames.add( EntityIdentifierMapping.ID_ROLE_NAME );
|
||||||
|
@ -1526,7 +1543,8 @@ public class ToOneAttributeMapping
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( notFoundAction != null ) {
|
else if ( notFoundAction != null
|
||||||
|
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null ) {
|
||||||
// For the target side only add keyResult when a not-found action is present
|
// For the target side only add keyResult when a not-found action is present
|
||||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||||
fetchablePath,
|
fetchablePath,
|
||||||
|
@ -1609,7 +1627,9 @@ public class ToOneAttributeMapping
|
||||||
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
||||||
|
|
||||||
// Consider all associations annotated with @NotFound as EAGER
|
// Consider all associations annotated with @NotFound as EAGER
|
||||||
if ( fetchTiming == FetchTiming.IMMEDIATE || hasNotFoundAction() ) {
|
if ( fetchTiming == FetchTiming.IMMEDIATE
|
||||||
|
|| hasNotFoundAction()
|
||||||
|
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null ) {
|
||||||
return buildEntityFetchSelect(
|
return buildEntityFetchSelect(
|
||||||
fetchParent,
|
fetchParent,
|
||||||
this,
|
this,
|
||||||
|
@ -1978,6 +1998,20 @@ public class ToOneAttributeMapping
|
||||||
if ( getAssociatedEntityMappingType().getSuperMappingType() != null && !creationState.supportsEntityNameUsage() ) {
|
if ( getAssociatedEntityMappingType().getSuperMappingType() != null && !creationState.supportsEntityNameUsage() ) {
|
||||||
getAssociatedEntityMappingType().applyDiscriminator( null, null, tableGroup, creationState );
|
getAssociatedEntityMappingType().applyDiscriminator( null, null, tableGroup, creationState );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getAssociatedEntityMappingType().getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
// add the restriction
|
||||||
|
final TableReference tableReference = lazyTableGroup.resolveTableReference(
|
||||||
|
navigablePath,
|
||||||
|
getAssociatedEntityMappingType().getSoftDeleteTableDetails().getTableName()
|
||||||
|
);
|
||||||
|
join.applyPredicate( createNonSoftDeletedRestriction(
|
||||||
|
tableReference,
|
||||||
|
softDeleteMapping,
|
||||||
|
creationState.getSqlExpressionResolver()
|
||||||
|
) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2014,11 +2048,15 @@ public class ToOneAttributeMapping
|
||||||
creationState.getSqlAliasBaseGenerator()
|
creationState.getSqlAliasBaseGenerator()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getAssociatedEntityMappingType().getSoftDeleteMapping();
|
||||||
|
|
||||||
final boolean canUseInnerJoin;
|
final boolean canUseInnerJoin;
|
||||||
if ( ! lhs.canUseInnerJoins() ) {
|
if ( ! lhs.canUseInnerJoins() ) {
|
||||||
canUseInnerJoin = false;
|
canUseInnerJoin = false;
|
||||||
}
|
}
|
||||||
else if ( isNullable || hasNotFoundAction() ) {
|
else if ( isNullable
|
||||||
|
|| hasNotFoundAction()
|
||||||
|
|| softDeleteMapping != null ) {
|
||||||
canUseInnerJoin = false;
|
canUseInnerJoin = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -2089,6 +2127,19 @@ public class ToOneAttributeMapping
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ( fetched && softDeleteMapping != null ) {
|
||||||
|
// add the restriction
|
||||||
|
final TableReference tableReference = lazyTableGroup.resolveTableReference(
|
||||||
|
navigablePath,
|
||||||
|
getAssociatedEntityMappingType().getSoftDeleteTableDetails().getTableName()
|
||||||
|
);
|
||||||
|
predicateConsumer.accept( createNonSoftDeletedRestriction(
|
||||||
|
tableReference,
|
||||||
|
softDeleteMapping,
|
||||||
|
creationState.getSqlExpressionResolver()
|
||||||
|
) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( requestedJoinType != null && realParentTableGroup instanceof CorrelatedTableGroup ) {
|
if ( requestedJoinType != null && realParentTableGroup instanceof CorrelatedTableGroup ) {
|
||||||
|
|
|
@ -201,6 +201,10 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
|
||||||
registerEntityNameResolvers( persister, entityNameResolvers );
|
registerEntityNameResolvers( persister, entityNameResolvers );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for ( EntityPersister persister : entityPersisterMap.values() ) {
|
||||||
|
persister.prepareLoaders();
|
||||||
|
}
|
||||||
|
|
||||||
collectionPersisterMap.values().forEach( CollectionPersister::postInstantiate );
|
collectionPersisterMap.values().forEach( CollectionPersister::postInstantiate );
|
||||||
|
|
||||||
registerEmbeddableMappingType( bootModel );
|
registerEmbeddableMappingType( bootModel );
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.persister.collection;
|
package org.hibernate.persister.collection;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.hibernate.AssertionFailure;
|
import org.hibernate.AssertionFailure;
|
||||||
import org.hibernate.FetchMode;
|
import org.hibernate.FetchMode;
|
||||||
import org.hibernate.Filter;
|
import org.hibernate.Filter;
|
||||||
|
@ -120,18 +132,6 @@ import org.hibernate.type.CompositeType;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
|
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
|
||||||
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
|
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
|
||||||
|
|
||||||
|
@ -1775,11 +1775,35 @@ public abstract class AbstractCollectionPersister
|
||||||
ParameterUsage.RESTRICT,
|
ParameterUsage.RESTRICT,
|
||||||
keyColumnCount
|
keyColumnCount
|
||||||
);
|
);
|
||||||
final java.util.List<ColumnValueBinding> keyRestrictionBindings = arrayList( keyColumnCount );
|
final java.util.List<ColumnValueBinding> restrictionBindings = arrayList( keyColumnCount );
|
||||||
fkDescriptor.getKeyPart().forEachSelectable( parameterBinders );
|
applyKeyRestrictions( tableReference, parameterBinders, restrictionBindings );
|
||||||
for ( ColumnValueParameter columnValueParameter : parameterBinders ) {
|
|
||||||
|
//noinspection unchecked,rawtypes
|
||||||
|
return (RestrictedTableMutation) new TableDeleteStandard(
|
||||||
|
tableReference,
|
||||||
|
this,
|
||||||
|
"one-shot delete for " + getRolePath(),
|
||||||
|
restrictionBindings,
|
||||||
|
Collections.emptyList(),
|
||||||
|
parameterBinders,
|
||||||
|
sqlWhereString
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyKeyRestrictions(
|
||||||
|
MutatingTableReference tableReference,
|
||||||
|
ColumnValueParameterList parameterList,
|
||||||
|
java.util.List<ColumnValueBinding> restrictionBindings) {
|
||||||
|
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = getAttributeMapping().getKeyDescriptor();
|
||||||
|
assert fkDescriptor != null;
|
||||||
|
|
||||||
|
final int keyColumnCount = fkDescriptor.getJdbcTypeCount();
|
||||||
|
|
||||||
|
fkDescriptor.getKeyPart().forEachSelectable( parameterList );
|
||||||
|
for ( ColumnValueParameter columnValueParameter : parameterList ) {
|
||||||
final ColumnReference columnReference = columnValueParameter.getColumnReference();
|
final ColumnReference columnReference = columnValueParameter.getColumnReference();
|
||||||
keyRestrictionBindings.add(
|
restrictionBindings.add(
|
||||||
new ColumnValueBinding(
|
new ColumnValueBinding(
|
||||||
columnReference,
|
columnReference,
|
||||||
new ColumnWriteFragment(
|
new ColumnWriteFragment(
|
||||||
|
@ -1790,17 +1814,6 @@ public abstract class AbstractCollectionPersister
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection unchecked,rawtypes
|
|
||||||
return (RestrictedTableMutation) new TableDeleteStandard(
|
|
||||||
tableReference,
|
|
||||||
this,
|
|
||||||
"one-shot delete for " + getRolePath(),
|
|
||||||
keyRestrictionBindings,
|
|
||||||
Collections.emptyList(),
|
|
||||||
parameterBinders,
|
|
||||||
sqlWhereString
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.persister.collection;
|
package org.hibernate.persister.collection;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.Internal;
|
import org.hibernate.Internal;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
|
@ -25,6 +28,7 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||||
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinator;
|
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinator;
|
||||||
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp;
|
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinatorNoOp;
|
||||||
|
@ -42,7 +46,11 @@ import org.hibernate.persister.collection.mutation.UpdateRowsCoordinatorNoOp;
|
||||||
import org.hibernate.persister.collection.mutation.UpdateRowsCoordinatorStandard;
|
import org.hibernate.persister.collection.mutation.UpdateRowsCoordinatorStandard;
|
||||||
import org.hibernate.persister.spi.PersisterCreationContext;
|
import org.hibernate.persister.spi.PersisterCreationContext;
|
||||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnValueBinding;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnValueParameterList;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnWriteFragment;
|
||||||
import org.hibernate.sql.model.ast.MutatingTableReference;
|
import org.hibernate.sql.model.ast.MutatingTableReference;
|
||||||
import org.hibernate.sql.model.ast.RestrictedTableMutation;
|
import org.hibernate.sql.model.ast.RestrictedTableMutation;
|
||||||
import org.hibernate.sql.model.ast.TableInsert;
|
import org.hibernate.sql.model.ast.TableInsert;
|
||||||
|
@ -50,8 +58,10 @@ import org.hibernate.sql.model.ast.TableMutation;
|
||||||
import org.hibernate.sql.model.ast.builder.CollectionRowDeleteBuilder;
|
import org.hibernate.sql.model.ast.builder.CollectionRowDeleteBuilder;
|
||||||
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
|
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
|
||||||
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
|
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
|
||||||
|
import org.hibernate.sql.model.internal.TableUpdateStandard;
|
||||||
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
|
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
|
||||||
|
|
||||||
|
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
|
||||||
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
|
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,6 +215,51 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestrictedTableMutation<JdbcMutationOperation> generateDeleteAllAst(MutatingTableReference tableReference) {
|
||||||
|
assert getAttributeMapping() != null;
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getAttributeMapping().getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping == null ) {
|
||||||
|
return super.generateDeleteAllAst( tableReference );
|
||||||
|
}
|
||||||
|
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = getAttributeMapping().getKeyDescriptor();
|
||||||
|
assert fkDescriptor != null;
|
||||||
|
|
||||||
|
final int keyColumnCount = fkDescriptor.getJdbcTypeCount();
|
||||||
|
final ColumnValueParameterList parameterBinders = new ColumnValueParameterList(
|
||||||
|
tableReference,
|
||||||
|
ParameterUsage.RESTRICT,
|
||||||
|
keyColumnCount
|
||||||
|
);
|
||||||
|
final java.util.List<ColumnValueBinding> restrictionBindings = arrayList( keyColumnCount );
|
||||||
|
applyKeyRestrictions( tableReference, parameterBinders, restrictionBindings );
|
||||||
|
|
||||||
|
final ColumnReference softDeleteColumn = new ColumnReference( tableReference, softDeleteMapping );
|
||||||
|
final ColumnWriteFragment nonDeletedLiteral = new ColumnWriteFragment(
|
||||||
|
softDeleteMapping.getNonDeletedLiteralText(),
|
||||||
|
Collections.emptyList(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
final ColumnWriteFragment deletedLiteral = new ColumnWriteFragment(
|
||||||
|
softDeleteMapping.getDeletedLiteralText(),
|
||||||
|
Collections.emptyList(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
restrictionBindings.add( new ColumnValueBinding( softDeleteColumn, nonDeletedLiteral ) );
|
||||||
|
final List<ColumnValueBinding> valueBindings = List.of( new ColumnValueBinding( softDeleteColumn, deletedLiteral ) );
|
||||||
|
|
||||||
|
return new TableUpdateStandard(
|
||||||
|
tableReference,
|
||||||
|
this,
|
||||||
|
"soft-delete removal",
|
||||||
|
valueBindings,
|
||||||
|
restrictionBindings,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected RowMutationOperations buildRowMutationOperations() {
|
protected RowMutationOperations buildRowMutationOperations() {
|
||||||
final OperationProducer insertRowOperationProducer;
|
final OperationProducer insertRowOperationProducer;
|
||||||
final RowMutationOperations.Values insertRowValues;
|
final RowMutationOperations.Values insertRowValues;
|
||||||
|
@ -295,6 +350,11 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeMapping.getElementDescriptor().forEachInsertable( insertBuilder );
|
attributeMapping.getElementDescriptor().forEachInsertable( insertBuilder );
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getAttributeMapping().getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
insertBuilder.addValueColumn( softDeleteMapping );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JdbcMutationOperation buildGeneratedInsertRowOperation(MutatingTableReference tableReference) {
|
private JdbcMutationOperation buildGeneratedInsertRowOperation(MutatingTableReference tableReference) {
|
||||||
|
@ -597,6 +657,11 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
||||||
final PluralAttributeMapping pluralAttribute = getAttributeMapping();
|
final PluralAttributeMapping pluralAttribute = getAttributeMapping();
|
||||||
assert pluralAttribute != null;
|
assert pluralAttribute != null;
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = pluralAttribute.getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
return generateSoftDeleteRowsAst( tableReference );
|
||||||
|
}
|
||||||
|
|
||||||
final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor();
|
final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor();
|
||||||
assert fkDescriptor != null;
|
assert fkDescriptor != null;
|
||||||
|
|
||||||
|
@ -627,6 +692,50 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
||||||
return (RestrictedTableMutation) deleteBuilder.buildMutation();
|
return (RestrictedTableMutation) deleteBuilder.buildMutation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected RestrictedTableMutation<JdbcMutationOperation> generateSoftDeleteRowsAst(MutatingTableReference tableReference) {
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getAttributeMapping().getSoftDeleteMapping();
|
||||||
|
assert softDeleteMapping != null;
|
||||||
|
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = getAttributeMapping().getKeyDescriptor();
|
||||||
|
assert fkDescriptor != null;
|
||||||
|
|
||||||
|
final TableUpdateBuilderStandard<JdbcMutationOperation> updateBuilder = new TableUpdateBuilderStandard<>(
|
||||||
|
this,
|
||||||
|
tableReference,
|
||||||
|
getFactory(),
|
||||||
|
sqlWhereString
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( getAttributeMapping().getIdentifierDescriptor() != null ) {
|
||||||
|
updateBuilder.addKeyRestrictionsLeniently( getAttributeMapping().getIdentifierDescriptor() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateBuilder.addKeyRestrictionsLeniently( getAttributeMapping().getKeyDescriptor().getKeyPart() );
|
||||||
|
|
||||||
|
if ( hasIndex() && !indexContainsFormula ) {
|
||||||
|
assert getAttributeMapping().getIndexDescriptor() != null;
|
||||||
|
updateBuilder.addKeyRestrictionsLeniently( getAttributeMapping().getIndexDescriptor() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateBuilder.addKeyRestrictions( getAttributeMapping().getElementDescriptor() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBuilder.addLiteralRestriction(
|
||||||
|
softDeleteMapping.getColumnName(),
|
||||||
|
softDeleteMapping.getNonDeletedLiteralText(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
|
||||||
|
updateBuilder.addValueColumn(
|
||||||
|
softDeleteMapping.getColumnName(),
|
||||||
|
softDeleteMapping.getDeletedLiteralText(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
|
||||||
|
return updateBuilder.buildMutation();
|
||||||
|
}
|
||||||
|
|
||||||
private void applyDeleteRowRestrictions(
|
private void applyDeleteRowRestrictions(
|
||||||
PersistentCollection<?> collection,
|
PersistentCollection<?> collection,
|
||||||
Object keyValue,
|
Object keyValue,
|
||||||
|
|
|
@ -46,6 +46,7 @@ import org.hibernate.Remove;
|
||||||
import org.hibernate.StaleObjectStateException;
|
import org.hibernate.StaleObjectStateException;
|
||||||
import org.hibernate.StaleStateException;
|
import org.hibernate.StaleStateException;
|
||||||
import org.hibernate.boot.Metadata;
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||||
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
||||||
import org.hibernate.boot.spi.MetadataImplementor;
|
import org.hibernate.boot.spi.MetadataImplementor;
|
||||||
import org.hibernate.boot.spi.SessionFactoryOptions;
|
import org.hibernate.boot.spi.SessionFactoryOptions;
|
||||||
|
@ -147,11 +148,13 @@ import org.hibernate.mapping.Formula;
|
||||||
import org.hibernate.mapping.Join;
|
import org.hibernate.mapping.Join;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
|
import org.hibernate.mapping.RootClass;
|
||||||
import org.hibernate.mapping.Selectable;
|
import org.hibernate.mapping.Selectable;
|
||||||
import org.hibernate.mapping.Subclass;
|
import org.hibernate.mapping.Subclass;
|
||||||
import org.hibernate.mapping.Table;
|
import org.hibernate.mapping.Table;
|
||||||
import org.hibernate.mapping.Value;
|
import org.hibernate.mapping.Value;
|
||||||
import org.hibernate.metadata.ClassMetadata;
|
import org.hibernate.metadata.ClassMetadata;
|
||||||
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
import org.hibernate.metamodel.mapping.Association;
|
import org.hibernate.metamodel.mapping.Association;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
||||||
|
@ -179,6 +182,8 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
|
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
||||||
import org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl;
|
import org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl;
|
||||||
import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping;
|
import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping;
|
||||||
|
@ -203,6 +208,8 @@ import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.entity.mutation.DeleteCoordinator;
|
import org.hibernate.persister.entity.mutation.DeleteCoordinator;
|
||||||
|
import org.hibernate.persister.entity.mutation.DeleteCoordinatorSoft;
|
||||||
|
import org.hibernate.persister.entity.mutation.DeleteCoordinatorStandard;
|
||||||
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
||||||
import org.hibernate.persister.entity.mutation.EntityTableMapping;
|
import org.hibernate.persister.entity.mutation.EntityTableMapping;
|
||||||
import org.hibernate.persister.entity.mutation.InsertCoordinator;
|
import org.hibernate.persister.entity.mutation.InsertCoordinator;
|
||||||
|
@ -262,6 +269,7 @@ import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||||
import org.hibernate.sql.model.MutationOperation;
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
import org.hibernate.sql.model.MutationOperationGroup;
|
import org.hibernate.sql.model.MutationOperationGroup;
|
||||||
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
import org.hibernate.sql.results.graph.DomainResult;
|
||||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||||
import org.hibernate.sql.results.graph.Fetch;
|
import org.hibernate.sql.results.graph.Fetch;
|
||||||
|
@ -438,6 +446,7 @@ public abstract class AbstractEntityPersister
|
||||||
private EntityVersionMapping versionMapping;
|
private EntityVersionMapping versionMapping;
|
||||||
private EntityRowIdMapping rowIdMapping;
|
private EntityRowIdMapping rowIdMapping;
|
||||||
private EntityDiscriminatorMapping discriminatorMapping;
|
private EntityDiscriminatorMapping discriminatorMapping;
|
||||||
|
private SoftDeleteMapping softDeleteMapping;
|
||||||
|
|
||||||
private AttributeMappingsList attributeMappings;
|
private AttributeMappingsList attributeMappings;
|
||||||
protected AttributeMappingsMap declaredAttributeMappings = AttributeMappingsMap.builder().build();
|
protected AttributeMappingsMap declaredAttributeMappings = AttributeMappingsMap.builder().build();
|
||||||
|
@ -3092,10 +3101,22 @@ public abstract class AbstractEntityPersister
|
||||||
getFactory()
|
getFactory()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( additionalPredicateCollectorAccess != null && needsDiscriminator() ) {
|
if ( additionalPredicateCollectorAccess != null ) {
|
||||||
final String alias = tableGroup.getPrimaryTableReference().getIdentificationVariable();
|
if ( needsDiscriminator() ) {
|
||||||
final Predicate discriminatorPredicate = createDiscriminatorPredicate( alias, tableGroup, creationState );
|
final String alias = tableGroup.getPrimaryTableReference().getIdentificationVariable();
|
||||||
additionalPredicateCollectorAccess.get().accept( discriminatorPredicate );
|
final Predicate discriminatorPredicate = createDiscriminatorPredicate( alias, tableGroup, creationState );
|
||||||
|
additionalPredicateCollectorAccess.get().accept( discriminatorPredicate );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final TableReference tableReference = tableGroup.resolveTableReference( getSoftDeleteTableDetails().getTableName() );
|
||||||
|
final Predicate softDeletePredicate = SoftDeleteHelper.createNonSoftDeletedRestriction(
|
||||||
|
tableReference,
|
||||||
|
softDeleteMapping,
|
||||||
|
creationState.getSqlExpressionResolver()
|
||||||
|
);
|
||||||
|
additionalPredicateCollectorAccess.get().accept( softDeletePredicate );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableGroup;
|
return tableGroup;
|
||||||
|
@ -3363,6 +3384,15 @@ public abstract class AbstractEntityPersister
|
||||||
initPropertyPaths( mapping );
|
initPropertyPaths( mapping );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareLoaders() {
|
||||||
|
// Hibernate Reactive needs to override the loaders
|
||||||
|
singleIdLoader = buildSingleIdEntityLoader();
|
||||||
|
multiIdLoader = buildMultiIdLoader();
|
||||||
|
|
||||||
|
lazyLoadPlanByFetchGroup = getLazyLoadPlanByFetchGroup();
|
||||||
|
}
|
||||||
|
|
||||||
private void doLateInit() {
|
private void doLateInit() {
|
||||||
if ( isIdentifierAssignedByInsert() ) {
|
if ( isIdentifierAssignedByInsert() ) {
|
||||||
final OnExecutionGenerator generator = (OnExecutionGenerator) getGenerator();
|
final OnExecutionGenerator generator = (OnExecutionGenerator) getGenerator();
|
||||||
|
@ -3386,7 +3416,6 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
//select SQL
|
//select SQL
|
||||||
lazyLoadPlanByFetchGroup = getLazyLoadPlanByFetchGroup();
|
|
||||||
sqlVersionSelectString = generateSelectVersionString();
|
sqlVersionSelectString = generateSelectVersionString();
|
||||||
|
|
||||||
logStaticSQL();
|
logStaticSQL();
|
||||||
|
@ -3617,12 +3646,24 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DeleteCoordinator buildDeleteCoordinator() {
|
protected DeleteCoordinator buildDeleteCoordinator() {
|
||||||
return new DeleteCoordinator( this, factory );
|
if ( softDeleteMapping == null ) {
|
||||||
|
return new DeleteCoordinatorStandard( this, factory );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new DeleteCoordinatorSoft( this, factory );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDiscriminatorToInsertGroup(MutationGroupBuilder insertGroupBuilder) {
|
public void addDiscriminatorToInsertGroup(MutationGroupBuilder insertGroupBuilder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addSoftDeleteToInsertGroup(MutationGroupBuilder insertGroupBuilder) {
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final TableInsertBuilder insertBuilder = insertGroupBuilder.getTableDetailsBuilder( getIdentifierTableName() );
|
||||||
|
insertBuilder.addValueColumn( softDeleteMapping );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected String substituteBrackets(String sql) {
|
protected String substituteBrackets(String sql) {
|
||||||
return new SQLQueryParser( sql, null, getFactory() ).process();
|
return new SQLQueryParser( sql, null, getFactory() ).process();
|
||||||
}
|
}
|
||||||
|
@ -3630,9 +3671,6 @@ public abstract class AbstractEntityPersister
|
||||||
@Override
|
@Override
|
||||||
public final void postInstantiate() throws MappingException {
|
public final void postInstantiate() throws MappingException {
|
||||||
doLateInit();
|
doLateInit();
|
||||||
// Hibernate Reactive needs to override the loaders
|
|
||||||
singleIdLoader = buildSingleIdEntityLoader();
|
|
||||||
multiIdLoader = buildMultiIdLoader();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4883,6 +4921,7 @@ public abstract class AbstractEntityPersister
|
||||||
naturalIdMapping = superMappingType.getNaturalIdMapping();
|
naturalIdMapping = superMappingType.getNaturalIdMapping();
|
||||||
versionMapping = superMappingType.getVersionMapping();
|
versionMapping = superMappingType.getVersionMapping();
|
||||||
rowIdMapping = superMappingType.getRowIdMapping();
|
rowIdMapping = superMappingType.getRowIdMapping();
|
||||||
|
softDeleteMapping = superMappingType.getSoftDeleteMapping();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
prepareMappingModel( creationProcess, bootEntityDescriptor );
|
prepareMappingModel( creationProcess, bootEntityDescriptor );
|
||||||
|
@ -5068,6 +5107,27 @@ public abstract class AbstractEntityPersister
|
||||||
}
|
}
|
||||||
|
|
||||||
discriminatorMapping = generateDiscriminatorMapping( bootEntityDescriptor, creationProcess );
|
discriminatorMapping = generateDiscriminatorMapping( bootEntityDescriptor, creationProcess );
|
||||||
|
softDeleteMapping = resolveSoftDeleteMapping( this, bootEntityDescriptor, getIdentifierTableName(), creationProcess );
|
||||||
|
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
if ( bootEntityDescriptor.getRootClass().getCustomSQLDelete() != null ) {
|
||||||
|
throw new UnsupportedMappingException( "Entity may not define both @SoftDelete and @SQLDelete" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SoftDeleteMapping resolveSoftDeleteMapping(
|
||||||
|
AbstractEntityPersister persister,
|
||||||
|
PersistentClass bootEntityDescriptor,
|
||||||
|
String identifierTableName,
|
||||||
|
MappingModelCreationProcess creationProcess) {
|
||||||
|
final RootClass rootClass = bootEntityDescriptor.getRootClass();
|
||||||
|
return SoftDeleteHelper.resolveSoftDeleteMapping(
|
||||||
|
persister,
|
||||||
|
rootClass,
|
||||||
|
identifierTableName,
|
||||||
|
creationProcess.getCreationContext().getJdbcServices().getDialect()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postProcessAttributeMappings(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
|
private void postProcessAttributeMappings(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
|
||||||
|
@ -5795,6 +5855,16 @@ public abstract class AbstractEntityPersister
|
||||||
return discriminatorMapping;
|
return discriminatorMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||||
|
return softDeleteMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDetails getSoftDeleteTableDetails() {
|
||||||
|
return getIdentifierTableDetails();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeMappingsList getAttributeMappings() {
|
public AttributeMappingsList getAttributeMappings() {
|
||||||
if ( attributeMappings == null ) {
|
if ( attributeMappings == null ) {
|
||||||
|
@ -6307,7 +6377,7 @@ public abstract class AbstractEntityPersister
|
||||||
/**
|
/**
|
||||||
* Generate the SQL that deletes a row by id (and version)
|
* Generate the SQL that deletes a row by id (and version)
|
||||||
*
|
*
|
||||||
* @deprecated No longer used. See {@link DeleteCoordinator}
|
* @deprecated No longer used. See {@link DeleteCoordinatorStandard}
|
||||||
*/
|
*/
|
||||||
@Deprecated(forRemoval = true)
|
@Deprecated(forRemoval = true)
|
||||||
@Remove
|
@Remove
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.hibernate.metadata.ClassMetadata;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
|
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||||
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
||||||
import org.hibernate.persister.walking.spi.AttributeSource;
|
import org.hibernate.persister.walking.spi.AttributeSource;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
||||||
|
@ -119,6 +120,17 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
|
||||||
*/
|
*/
|
||||||
void postInstantiate() throws MappingException;
|
void postInstantiate() throws MappingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare loaders associated with the persister. Distinct "phase"
|
||||||
|
* in building the persister after {@linkplain InFlightEntityMappingType#prepareMappingModel}
|
||||||
|
* and {@linkplain #postInstantiate()} have occurred.
|
||||||
|
* <p/>
|
||||||
|
* The distinct phase is used to ensure that all {@linkplain org.hibernate.metamodel.mapping.TableDetails}
|
||||||
|
* are available across the entire model
|
||||||
|
*/
|
||||||
|
default void prepareLoaders() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the {@link org.hibernate.SessionFactory} to which this persister
|
* Return the {@link org.hibernate.SessionFactory} to which this persister
|
||||||
* belongs.
|
* belongs.
|
||||||
|
@ -1110,5 +1122,4 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
|
||||||
*/
|
*/
|
||||||
@Incubating
|
@Incubating
|
||||||
Iterable<UniqueKeyEntry> uniqueKeyEntries();
|
Iterable<UniqueKeyEntry> uniqueKeyEntries();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.persister.entity.mutation;
|
||||||
|
|
||||||
|
import org.hibernate.engine.OptimisticLockStyle;
|
||||||
|
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
|
||||||
|
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
|
||||||
|
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
|
||||||
|
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
|
||||||
|
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
|
||||||
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityVersionMapping;
|
||||||
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
import org.hibernate.sql.model.MutationOperationGroup;
|
||||||
|
|
||||||
|
import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template support for DeleteCoordinator implementations. Mainly
|
||||||
|
* centers around delegation via {@linkplain #generateOperationGroup}.
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDeleteCoordinator
|
||||||
|
extends AbstractMutationCoordinator
|
||||||
|
implements DeleteCoordinator {
|
||||||
|
private final BasicBatchKey batchKey;
|
||||||
|
private final MutationOperationGroup staticOperationGroup;
|
||||||
|
|
||||||
|
private MutationOperationGroup noVersionDeleteGroup;
|
||||||
|
|
||||||
|
public AbstractDeleteCoordinator(
|
||||||
|
AbstractEntityPersister entityPersister,
|
||||||
|
SessionFactoryImplementor factory) {
|
||||||
|
super( entityPersister, factory );
|
||||||
|
|
||||||
|
this.batchKey = new BasicBatchKey( entityPersister.getEntityName() + "#DELETE" );
|
||||||
|
this.staticOperationGroup = generateOperationGroup( "", null, true, null );
|
||||||
|
if ( !entityPersister.isVersioned() ) {
|
||||||
|
noVersionDeleteGroup = staticOperationGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutationOperationGroup getStaticDeleteGroup() {
|
||||||
|
return staticOperationGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasicBatchKey getBatchKey() {
|
||||||
|
return batchKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract MutationOperationGroup generateOperationGroup(
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
boolean applyVersion,
|
||||||
|
SharedSessionContractImplementor session);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void coordinateDelete(
|
||||||
|
Object entity,
|
||||||
|
Object id,
|
||||||
|
Object version,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
boolean isImpliedOptimisticLocking = entityPersister().optimisticLockStyle().isAllOrDirty();
|
||||||
|
|
||||||
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
|
final EntityEntry entry = persistenceContext.getEntry( entity );
|
||||||
|
final Object[] loadedState = entry != null && isImpliedOptimisticLocking ? entry.getLoadedState() : null;
|
||||||
|
final Object rowId = entry != null ? entry.getRowId() : null;
|
||||||
|
|
||||||
|
if ( ( isImpliedOptimisticLocking && loadedState != null ) || ( rowId == null && entityPersister().hasRowId() ) ) {
|
||||||
|
doDynamicDelete( entity, id, rowId, loadedState, session );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doStaticDelete( entity, id, rowId, entry == null ? null : entry.getLoadedState(), version, session );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doDynamicDelete(
|
||||||
|
Object entity,
|
||||||
|
Object id,
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final MutationOperationGroup operationGroup = generateOperationGroup( null, loadedState, true, session );
|
||||||
|
final MutationExecutor mutationExecutor = executor( session, operationGroup );
|
||||||
|
|
||||||
|
for ( int i = 0; i < operationGroup.getNumberOfOperations(); i++ ) {
|
||||||
|
final MutationOperation mutation = operationGroup.getOperation( i );
|
||||||
|
if ( mutation != null ) {
|
||||||
|
final String tableName = mutation.getTableDetails().getTableName();
|
||||||
|
mutationExecutor.getPreparedStatementDetails( tableName );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDynamicDeleteTableDetails(
|
||||||
|
id,
|
||||||
|
rowId,
|
||||||
|
loadedState,
|
||||||
|
mutationExecutor,
|
||||||
|
operationGroup,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutationExecutor.execute(
|
||||||
|
entity,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
||||||
|
statementDetails,
|
||||||
|
affectedRowCount,
|
||||||
|
batchPosition,
|
||||||
|
entityPersister(),
|
||||||
|
id,
|
||||||
|
factory()
|
||||||
|
),
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
mutationExecutor.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyDynamicDeleteTableDetails(
|
||||||
|
Object id,
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
MutationExecutor mutationExecutor,
|
||||||
|
MutationOperationGroup operationGroup,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
applyLocking( null, loadedState, mutationExecutor, session );
|
||||||
|
applyId( id, null, mutationExecutor, operationGroup, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyLocking(
|
||||||
|
Object version,
|
||||||
|
Object[] loadedState,
|
||||||
|
MutationExecutor mutationExecutor,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||||
|
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||||
|
switch ( optimisticLockStyle ) {
|
||||||
|
case VERSION:
|
||||||
|
applyVersionLocking( version, jdbcValueBindings );
|
||||||
|
break;
|
||||||
|
case ALL:
|
||||||
|
case DIRTY:
|
||||||
|
applyAllOrDirtyLocking( loadedState, session, jdbcValueBindings );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyAllOrDirtyLocking(
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
JdbcValueBindings jdbcValueBindings) {
|
||||||
|
if ( loadedState != null ) {
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
final boolean[] versionability = persister.getPropertyVersionability();
|
||||||
|
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||||
|
final AttributeMapping attribute;
|
||||||
|
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||||
|
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||||
|
final Object loadedValue = loadedState[attributeIndex];
|
||||||
|
if ( loadedValue != null ) {
|
||||||
|
final String mutationTableName = persister.getAttributeMutationTableName( attributeIndex );
|
||||||
|
attribute.breakDownJdbcValues(
|
||||||
|
loadedValue,
|
||||||
|
0,
|
||||||
|
jdbcValueBindings,
|
||||||
|
mutationTableName,
|
||||||
|
(valueIndex, bindings, tableName, jdbcValue, jdbcValueMapping) -> {
|
||||||
|
if ( jdbcValue == null ) {
|
||||||
|
// presumably the SQL was generated with `is null`
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bindings.bindValue(
|
||||||
|
jdbcValue,
|
||||||
|
tableName,
|
||||||
|
jdbcValueMapping.getSelectionExpression(),
|
||||||
|
ParameterUsage.RESTRICT
|
||||||
|
);
|
||||||
|
},
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyVersionLocking(
|
||||||
|
Object version,
|
||||||
|
JdbcValueBindings jdbcValueBindings) {
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
final EntityVersionMapping versionMapping = persister.getVersionMapping();
|
||||||
|
if ( version != null && versionMapping != null ) {
|
||||||
|
jdbcValueBindings.bindValue(
|
||||||
|
version,
|
||||||
|
persister.physicalTableNameForMutation( versionMapping ),
|
||||||
|
versionMapping.getSelectionExpression(),
|
||||||
|
ParameterUsage.RESTRICT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyId(
|
||||||
|
Object id,
|
||||||
|
Object rowId,
|
||||||
|
MutationExecutor mutationExecutor,
|
||||||
|
MutationOperationGroup operationGroup,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||||
|
final EntityRowIdMapping rowIdMapping = entityPersister().getRowIdMapping();
|
||||||
|
|
||||||
|
for ( int position = 0; position < operationGroup.getNumberOfOperations(); position++ ) {
|
||||||
|
final MutationOperation jdbcMutation = operationGroup.getOperation( position );
|
||||||
|
final EntityTableMapping tableDetails = (EntityTableMapping) jdbcMutation.getTableDetails();
|
||||||
|
breakDownKeyJdbcValues( id, rowId, session, jdbcValueBindings, tableDetails );
|
||||||
|
final PreparedStatementDetails statementDetails = mutationExecutor.getPreparedStatementDetails( tableDetails.getTableName() );
|
||||||
|
if ( statementDetails != null ) {
|
||||||
|
// force creation of the PreparedStatement
|
||||||
|
//noinspection resource
|
||||||
|
statementDetails.resolveStatement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doStaticDelete(
|
||||||
|
Object entity,
|
||||||
|
Object id,
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
Object version,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final boolean applyVersion;
|
||||||
|
final MutationOperationGroup operationGroupToUse;
|
||||||
|
if ( entity == null ) {
|
||||||
|
applyVersion = false;
|
||||||
|
operationGroupToUse = resolveNoVersionDeleteGroup( session );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
applyVersion = true;
|
||||||
|
operationGroupToUse = staticOperationGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MutationExecutor mutationExecutor = executor( session, operationGroupToUse );
|
||||||
|
for ( int position = 0; position < staticOperationGroup.getNumberOfOperations(); position++ ) {
|
||||||
|
final MutationOperation mutation = staticOperationGroup.getOperation( position );
|
||||||
|
if ( mutation != null ) {
|
||||||
|
mutationExecutor.getPreparedStatementDetails( mutation.getTableDetails().getTableName() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStaticDeleteTableDetails(
|
||||||
|
id,
|
||||||
|
rowId,
|
||||||
|
loadedState,
|
||||||
|
version,
|
||||||
|
applyVersion,
|
||||||
|
mutationExecutor,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
|
||||||
|
mutationExecutor.execute(
|
||||||
|
entity,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
||||||
|
statementDetails,
|
||||||
|
affectedRowCount,
|
||||||
|
batchPosition,
|
||||||
|
entityPersister(),
|
||||||
|
id,
|
||||||
|
factory()
|
||||||
|
),
|
||||||
|
session
|
||||||
|
);
|
||||||
|
|
||||||
|
mutationExecutor.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyStaticDeleteTableDetails(
|
||||||
|
Object id,
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
Object version,
|
||||||
|
boolean applyVersion,
|
||||||
|
MutationExecutor mutationExecutor,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
if ( applyVersion ) {
|
||||||
|
applyLocking( version, null, mutationExecutor, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||||
|
bindPartitionColumnValueBindings( loadedState, session, jdbcValueBindings );
|
||||||
|
|
||||||
|
applyId( id, rowId, mutationExecutor, staticOperationGroup, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
private MutationExecutor executor(SharedSessionContractImplementor session, MutationOperationGroup group) {
|
||||||
|
return mutationExecutorService.createExecutor( resolveBatchKeyAccess( false, session ), group, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MutationOperationGroup resolveNoVersionDeleteGroup(SharedSessionContractImplementor session) {
|
||||||
|
if ( noVersionDeleteGroup == null ) {
|
||||||
|
noVersionDeleteGroup = generateOperationGroup( "", null, false, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
return noVersionDeleteGroup;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,32 +6,8 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.persister.entity.mutation;
|
package org.hibernate.persister.entity.mutation;
|
||||||
|
|
||||||
import org.hibernate.engine.OptimisticLockStyle;
|
|
||||||
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
|
|
||||||
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
|
|
||||||
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
|
|
||||||
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
|
|
||||||
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
|
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
|
||||||
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
|
||||||
import org.hibernate.metamodel.mapping.EntityVersionMapping;
|
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
|
||||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
|
||||||
import org.hibernate.sql.model.MutationOperation;
|
|
||||||
import org.hibernate.sql.model.MutationOperationGroup;
|
import org.hibernate.sql.model.MutationOperationGroup;
|
||||||
import org.hibernate.sql.model.MutationType;
|
|
||||||
import org.hibernate.sql.model.ast.ColumnValueBindingList;
|
|
||||||
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
|
||||||
import org.hibernate.sql.model.ast.builder.RestrictedTableMutationBuilder;
|
|
||||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilder;
|
|
||||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderSkipped;
|
|
||||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderStandard;
|
|
||||||
|
|
||||||
import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinates the deleting of an entity.
|
* Coordinates the deleting of an entity.
|
||||||
|
@ -40,389 +16,19 @@ import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.id
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class DeleteCoordinator extends AbstractMutationCoordinator {
|
public interface DeleteCoordinator {
|
||||||
private final MutationOperationGroup staticOperationGroup;
|
/**
|
||||||
private final BasicBatchKey batchKey;
|
* The operation group used to perform the deletion unless some form
|
||||||
|
* of dynamic delete is necessary
|
||||||
|
*/
|
||||||
|
MutationOperationGroup getStaticDeleteGroup();
|
||||||
|
|
||||||
private MutationOperationGroup noVersionDeleteGroup;
|
/**
|
||||||
|
* Perform the deletions
|
||||||
public DeleteCoordinator(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
*/
|
||||||
super( entityPersister, factory );
|
void coordinateDelete(
|
||||||
|
|
||||||
this.staticOperationGroup = generateOperationGroup( "", null, true, null );
|
|
||||||
this.batchKey = new BasicBatchKey( entityPersister.getEntityName() + "#DELETE" );
|
|
||||||
|
|
||||||
if ( !entityPersister.isVersioned() ) {
|
|
||||||
noVersionDeleteGroup = staticOperationGroup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MutationOperationGroup getStaticDeleteGroup() {
|
|
||||||
return staticOperationGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public BasicBatchKey getBatchKey() {
|
|
||||||
return batchKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void coordinateDelete(
|
|
||||||
Object entity,
|
Object entity,
|
||||||
Object id,
|
Object id,
|
||||||
Object version,
|
Object version,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session);
|
||||||
|
|
||||||
boolean isImpliedOptimisticLocking = entityPersister().optimisticLockStyle().isAllOrDirty();
|
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
|
||||||
final EntityEntry entry = persistenceContext.getEntry( entity );
|
|
||||||
final Object[] loadedState = entry != null ? entry.getLoadedState() : null;
|
|
||||||
final Object rowId = entry != null ? entry.getRowId() : null;
|
|
||||||
|
|
||||||
if ( ( isImpliedOptimisticLocking && loadedState != null ) || ( rowId == null && entityPersister().hasRowId() ) ) {
|
|
||||||
doDynamicDelete( entity, id, loadedState, session );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
doStaticDelete( entity, id, rowId, loadedState, version, session );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doDynamicDelete(
|
|
||||||
Object entity,
|
|
||||||
Object id,
|
|
||||||
Object[] loadedState,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final MutationOperationGroup operationGroup = generateOperationGroup( null, loadedState, true, session );
|
|
||||||
|
|
||||||
final MutationExecutor mutationExecutor = executor( session, operationGroup );
|
|
||||||
|
|
||||||
for ( int i = 0; i < operationGroup.getNumberOfOperations(); i++ ) {
|
|
||||||
final MutationOperation mutation = operationGroup.getOperation( i );
|
|
||||||
if ( mutation != null ) {
|
|
||||||
final String tableName = mutation.getTableDetails().getTableName();
|
|
||||||
mutationExecutor.getPreparedStatementDetails( tableName );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyLocking( null, loadedState, mutationExecutor, session );
|
|
||||||
|
|
||||||
applyId( id, null, mutationExecutor, operationGroup, session );
|
|
||||||
|
|
||||||
try {
|
|
||||||
mutationExecutor.execute(
|
|
||||||
entity,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
|
||||||
statementDetails,
|
|
||||||
affectedRowCount,
|
|
||||||
batchPosition,
|
|
||||||
entityPersister(),
|
|
||||||
id,
|
|
||||||
factory()
|
|
||||||
),
|
|
||||||
session
|
|
||||||
);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
mutationExecutor.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MutationExecutor executor(SharedSessionContractImplementor session, MutationOperationGroup group) {
|
|
||||||
return mutationExecutorService
|
|
||||||
.createExecutor( resolveBatchKeyAccess( false, session ), group, session );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyLocking(
|
|
||||||
Object version,
|
|
||||||
Object[] loadedState,
|
|
||||||
MutationExecutor mutationExecutor,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
|
||||||
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
|
||||||
switch ( optimisticLockStyle ) {
|
|
||||||
case VERSION:
|
|
||||||
applyVersionLocking( version, session, jdbcValueBindings );
|
|
||||||
break;
|
|
||||||
case ALL:
|
|
||||||
case DIRTY:
|
|
||||||
applyAllOrDirtyLocking( loadedState, session, jdbcValueBindings );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyAllOrDirtyLocking(
|
|
||||||
Object[] loadedState,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
JdbcValueBindings jdbcValueBindings) {
|
|
||||||
if ( loadedState != null ) {
|
|
||||||
final AbstractEntityPersister persister = entityPersister();
|
|
||||||
final boolean[] versionability = persister.getPropertyVersionability();
|
|
||||||
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
|
||||||
final AttributeMapping attribute;
|
|
||||||
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
|
||||||
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
|
||||||
final Object loadedValue = loadedState[attributeIndex];
|
|
||||||
if ( loadedValue != null ) {
|
|
||||||
final String mutationTableName = persister.getAttributeMutationTableName( attributeIndex );
|
|
||||||
attribute.breakDownJdbcValues(
|
|
||||||
loadedValue,
|
|
||||||
0,
|
|
||||||
jdbcValueBindings,
|
|
||||||
mutationTableName,
|
|
||||||
(valueIndex, bindings, tableName, jdbcValue, jdbcValueMapping) -> {
|
|
||||||
if ( jdbcValue == null ) {
|
|
||||||
// presumably the SQL was generated with `is null`
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bindings.bindValue(
|
|
||||||
jdbcValue,
|
|
||||||
tableName,
|
|
||||||
jdbcValueMapping.getSelectionExpression(),
|
|
||||||
ParameterUsage.RESTRICT
|
|
||||||
);
|
|
||||||
},
|
|
||||||
session
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyVersionLocking(
|
|
||||||
Object version,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
JdbcValueBindings jdbcValueBindings) {
|
|
||||||
final AbstractEntityPersister persister = entityPersister();
|
|
||||||
final EntityVersionMapping versionMapping = persister.getVersionMapping();
|
|
||||||
if ( version != null && versionMapping != null ) {
|
|
||||||
jdbcValueBindings.bindValue(
|
|
||||||
version,
|
|
||||||
persister.physicalTableNameForMutation( versionMapping ),
|
|
||||||
versionMapping.getSelectionExpression(),
|
|
||||||
ParameterUsage.RESTRICT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyId(
|
|
||||||
Object id,
|
|
||||||
Object rowId,
|
|
||||||
MutationExecutor mutationExecutor,
|
|
||||||
MutationOperationGroup operationGroup,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
|
||||||
for ( int position = 0; position < operationGroup.getNumberOfOperations(); position++ ) {
|
|
||||||
final MutationOperation jdbcMutation = operationGroup.getOperation( position );
|
|
||||||
final EntityTableMapping tableDetails = (EntityTableMapping) jdbcMutation.getTableDetails();
|
|
||||||
breakDownKeyJdbcValues( id, rowId, session, jdbcValueBindings, tableDetails );
|
|
||||||
final PreparedStatementDetails statementDetails = mutationExecutor.getPreparedStatementDetails( tableDetails.getTableName() );
|
|
||||||
if ( statementDetails != null ) {
|
|
||||||
// force creation of the PreparedStatement
|
|
||||||
//noinspection resource
|
|
||||||
statementDetails.resolveStatement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doStaticDelete(
|
|
||||||
Object entity,
|
|
||||||
Object id,
|
|
||||||
Object rowId,
|
|
||||||
Object[] loadedState,
|
|
||||||
Object version,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
|
|
||||||
final boolean applyVersion;
|
|
||||||
final MutationOperationGroup operationGroupToUse;
|
|
||||||
if ( entity == null ) {
|
|
||||||
applyVersion = false;
|
|
||||||
operationGroupToUse = resolveNoVersionDeleteGroup( session );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
applyVersion = true;
|
|
||||||
operationGroupToUse = staticOperationGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
final MutationExecutor mutationExecutor = executor( session, operationGroupToUse );
|
|
||||||
|
|
||||||
for ( int position = 0; position < staticOperationGroup.getNumberOfOperations(); position++ ) {
|
|
||||||
final MutationOperation mutation = staticOperationGroup.getOperation( position );
|
|
||||||
if ( mutation != null ) {
|
|
||||||
mutationExecutor.getPreparedStatementDetails( mutation.getTableDetails().getTableName() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( applyVersion ) {
|
|
||||||
applyLocking( version, null, mutationExecutor, session );
|
|
||||||
}
|
|
||||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
|
||||||
|
|
||||||
bindPartitionColumnValueBindings( loadedState, session, jdbcValueBindings );
|
|
||||||
|
|
||||||
applyId( id, rowId, mutationExecutor, staticOperationGroup, session );
|
|
||||||
|
|
||||||
mutationExecutor.execute(
|
|
||||||
entity,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
|
||||||
statementDetails,
|
|
||||||
affectedRowCount,
|
|
||||||
batchPosition,
|
|
||||||
entityPersister(),
|
|
||||||
id,
|
|
||||||
factory()
|
|
||||||
),
|
|
||||||
session
|
|
||||||
);
|
|
||||||
|
|
||||||
mutationExecutor.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MutationOperationGroup resolveNoVersionDeleteGroup(SharedSessionContractImplementor session) {
|
|
||||||
if ( noVersionDeleteGroup == null ) {
|
|
||||||
noVersionDeleteGroup = generateOperationGroup( "", null, false, session );
|
|
||||||
}
|
|
||||||
|
|
||||||
return noVersionDeleteGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MutationOperationGroup generateOperationGroup(
|
|
||||||
Object rowId,
|
|
||||||
Object[] loadedState,
|
|
||||||
boolean applyVersion,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final MutationGroupBuilder deleteGroupBuilder = new MutationGroupBuilder( MutationType.DELETE, entityPersister() );
|
|
||||||
|
|
||||||
entityPersister().forEachMutableTableReverse( (tableMapping) -> {
|
|
||||||
final TableDeleteBuilder tableDeleteBuilder = tableMapping.isCascadeDeleteEnabled()
|
|
||||||
? new TableDeleteBuilderSkipped( tableMapping )
|
|
||||||
: new TableDeleteBuilderStandard( entityPersister(), tableMapping, factory() );
|
|
||||||
deleteGroupBuilder.addTableDetailsBuilder( tableDeleteBuilder );
|
|
||||||
} );
|
|
||||||
|
|
||||||
applyTableDeleteDetails( deleteGroupBuilder, rowId, loadedState, applyVersion, session );
|
|
||||||
|
|
||||||
return createOperationGroup( null, deleteGroupBuilder.buildMutationGroup() );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyTableDeleteDetails(
|
|
||||||
MutationGroupBuilder deleteGroupBuilder,
|
|
||||||
Object rowId,
|
|
||||||
Object[] loadedState,
|
|
||||||
boolean applyVersion,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
// first, the table key column(s)
|
|
||||||
deleteGroupBuilder.forEachTableMutationBuilder( (builder) -> {
|
|
||||||
final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping();
|
|
||||||
final TableDeleteBuilder tableDeleteBuilder = (TableDeleteBuilder) builder;
|
|
||||||
applyKeyRestriction( rowId, entityPersister(), tableDeleteBuilder, tableMapping );
|
|
||||||
} );
|
|
||||||
|
|
||||||
if ( applyVersion ) {
|
|
||||||
// apply any optimistic locking
|
|
||||||
applyOptimisticLocking( deleteGroupBuilder, loadedState, session );
|
|
||||||
final AbstractEntityPersister persister = entityPersister();
|
|
||||||
if ( persister.hasPartitionedSelectionMapping() ) {
|
|
||||||
final AttributeMappingsList attributeMappings = persister.getAttributeMappings();
|
|
||||||
for ( int m = 0; m < attributeMappings.size(); m++ ) {
|
|
||||||
final AttributeMapping attributeMapping = attributeMappings.get( m );
|
|
||||||
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
|
||||||
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
|
||||||
final SelectableMapping selectableMapping = attributeMapping.getSelectable( i );
|
|
||||||
if ( selectableMapping.isPartitioned() ) {
|
|
||||||
final String tableNameForMutation =
|
|
||||||
persister.physicalTableNameForMutation( selectableMapping );
|
|
||||||
final RestrictedTableMutationBuilder<?, ?> rootTableMutationBuilder =
|
|
||||||
deleteGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
|
||||||
rootTableMutationBuilder.addKeyRestrictionLeniently( selectableMapping );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// todo (6.2) : apply where + where-fragments
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyOptimisticLocking(
|
|
||||||
MutationGroupBuilder mutationGroupBuilder,
|
|
||||||
Object[] loadedState,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
|
||||||
if ( optimisticLockStyle.isVersion() && entityPersister().getVersionMapping() != null ) {
|
|
||||||
applyVersionBasedOptLocking( mutationGroupBuilder );
|
|
||||||
}
|
|
||||||
else if ( loadedState != null && entityPersister().optimisticLockStyle().isAllOrDirty() ) {
|
|
||||||
applyNonVersionOptLocking(
|
|
||||||
optimisticLockStyle,
|
|
||||||
mutationGroupBuilder,
|
|
||||||
loadedState,
|
|
||||||
session
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyVersionBasedOptLocking(MutationGroupBuilder mutationGroupBuilder) {
|
|
||||||
assert entityPersister().optimisticLockStyle() == OptimisticLockStyle.VERSION;
|
|
||||||
assert entityPersister().getVersionMapping() != null;
|
|
||||||
|
|
||||||
final String tableNameForMutation = entityPersister().physicalTableNameForMutation( entityPersister().getVersionMapping() );
|
|
||||||
final RestrictedTableMutationBuilder<?,?> rootTableMutationBuilder = mutationGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
|
||||||
rootTableMutationBuilder.addOptimisticLockRestriction( entityPersister().getVersionMapping() );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyNonVersionOptLocking(
|
|
||||||
OptimisticLockStyle lockStyle,
|
|
||||||
MutationGroupBuilder mutationGroupBuilder,
|
|
||||||
Object[] loadedState,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final AbstractEntityPersister persister = entityPersister();
|
|
||||||
assert loadedState != null;
|
|
||||||
assert lockStyle.isAllOrDirty();
|
|
||||||
assert persister.optimisticLockStyle().isAllOrDirty();
|
|
||||||
assert session != null;
|
|
||||||
|
|
||||||
final boolean[] versionability = persister.getPropertyVersionability();
|
|
||||||
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
|
||||||
final AttributeMapping attribute;
|
|
||||||
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
|
||||||
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
|
||||||
breakDownJdbcValues( mutationGroupBuilder, session, attribute, loadedState[attributeIndex] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void breakDownJdbcValues(
|
|
||||||
MutationGroupBuilder mutationGroupBuilder,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
AttributeMapping attribute,
|
|
||||||
Object loadedValue) {
|
|
||||||
final RestrictedTableMutationBuilder<?, ?> tableMutationBuilder =
|
|
||||||
mutationGroupBuilder.findTableDetailsBuilder( attribute.getContainingTableExpression() );
|
|
||||||
if ( tableMutationBuilder != null ) {
|
|
||||||
final ColumnValueBindingList optimisticLockBindings = tableMutationBuilder.getOptimisticLockBindings();
|
|
||||||
if ( optimisticLockBindings != null ) {
|
|
||||||
attribute.breakDownJdbcValues(
|
|
||||||
loadedValue,
|
|
||||||
(valueIndex, value, jdbcValueMapping) -> {
|
|
||||||
if ( !tableMutationBuilder.getKeyRestrictionBindings()
|
|
||||||
.containsColumn(
|
|
||||||
jdbcValueMapping.getSelectableName(),
|
|
||||||
jdbcValueMapping.getJdbcMapping()
|
|
||||||
) ) {
|
|
||||||
optimisticLockBindings.consume( valueIndex, value, jdbcValueMapping );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
,
|
|
||||||
session
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else there is no actual delete statement for that table,
|
|
||||||
// generally indicates we have an on-delete=cascade situation
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.persister.entity.mutation;
|
||||||
|
|
||||||
|
import org.hibernate.engine.OptimisticLockStyle;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
||||||
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
import org.hibernate.sql.model.MutationOperationGroup;
|
||||||
|
import org.hibernate.sql.model.MutationType;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnValueBindingList;
|
||||||
|
import org.hibernate.sql.model.ast.RestrictedTableMutation;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableUpdateBuilder;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
|
||||||
|
import org.hibernate.sql.model.internal.MutationGroupSingle;
|
||||||
|
import org.hibernate.sql.model.internal.MutationOperationGroupFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeleteCoordinator for soft-deletes
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class DeleteCoordinatorSoft extends AbstractDeleteCoordinator {
|
||||||
|
public DeleteCoordinatorSoft(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
||||||
|
super( entityPersister, factory );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MutationOperationGroup generateOperationGroup(
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
boolean applyVersion,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final EntityTableMapping rootTableMapping = entityPersister().getIdentifierTableMapping();
|
||||||
|
final TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder = new TableUpdateBuilderStandard<>(
|
||||||
|
entityPersister(),
|
||||||
|
rootTableMapping,
|
||||||
|
factory()
|
||||||
|
);
|
||||||
|
|
||||||
|
applyKeyRestriction( rowId, entityPersister(), tableUpdateBuilder, rootTableMapping );
|
||||||
|
applySoftDelete( entityPersister().getSoftDeleteMapping(), tableUpdateBuilder );
|
||||||
|
applyPartitionKeyRestriction( tableUpdateBuilder );
|
||||||
|
applyOptimisticLocking( tableUpdateBuilder, loadedState, session );
|
||||||
|
|
||||||
|
final RestrictedTableMutation<MutationOperation> tableMutation = tableUpdateBuilder.buildMutation();
|
||||||
|
final MutationGroupSingle mutationGroup = new MutationGroupSingle(
|
||||||
|
MutationType.DELETE,
|
||||||
|
entityPersister(),
|
||||||
|
tableMutation
|
||||||
|
);
|
||||||
|
|
||||||
|
final MutationOperation mutationOperation = tableMutation.createMutationOperation( null, factory() );
|
||||||
|
return MutationOperationGroupFactory.singleOperation( mutationGroup, mutationOperation );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPartitionKeyRestriction(TableUpdateBuilder<?> tableUpdateBuilder) {
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
if ( persister.hasPartitionedSelectionMapping() ) {
|
||||||
|
final AttributeMappingsList attributeMappings = persister.getAttributeMappings();
|
||||||
|
for ( int m = 0; m < attributeMappings.size(); m++ ) {
|
||||||
|
final AttributeMapping attributeMapping = attributeMappings.get( m );
|
||||||
|
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
||||||
|
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
||||||
|
final SelectableMapping selectableMapping = attributeMapping.getSelectable( i );
|
||||||
|
if ( selectableMapping.isPartitioned() ) {
|
||||||
|
tableUpdateBuilder.addKeyRestrictionLeniently( selectableMapping );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applySoftDelete(
|
||||||
|
SoftDeleteMapping softDeleteMapping,
|
||||||
|
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder) {
|
||||||
|
tableUpdateBuilder.addLiteralRestriction(
|
||||||
|
softDeleteMapping.getSelectionExpression(),
|
||||||
|
softDeleteMapping.getNonDeletedLiteralText(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
tableUpdateBuilder.addValueColumn(
|
||||||
|
softDeleteMapping.getSelectionExpression(),
|
||||||
|
softDeleteMapping.getDeletedLiteralText(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyOptimisticLocking(
|
||||||
|
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||||
|
if ( optimisticLockStyle.isVersion() && entityPersister().getVersionMapping() != null ) {
|
||||||
|
applyVersionBasedOptLocking( tableUpdateBuilder );
|
||||||
|
}
|
||||||
|
else if ( loadedState != null && entityPersister().optimisticLockStyle().isAllOrDirty() ) {
|
||||||
|
applyNonVersionOptLocking(
|
||||||
|
optimisticLockStyle,
|
||||||
|
tableUpdateBuilder,
|
||||||
|
loadedState,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyVersionBasedOptLocking(TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder) {
|
||||||
|
assert entityPersister().optimisticLockStyle() == OptimisticLockStyle.VERSION;
|
||||||
|
assert entityPersister().getVersionMapping() != null;
|
||||||
|
|
||||||
|
tableUpdateBuilder.addOptimisticLockRestriction( entityPersister().getVersionMapping() );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyNonVersionOptLocking(
|
||||||
|
OptimisticLockStyle lockStyle,
|
||||||
|
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
assert loadedState != null;
|
||||||
|
assert lockStyle.isAllOrDirty();
|
||||||
|
assert persister.optimisticLockStyle().isAllOrDirty();
|
||||||
|
assert session != null;
|
||||||
|
|
||||||
|
final boolean[] versionability = persister.getPropertyVersionability();
|
||||||
|
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||||
|
final AttributeMapping attribute;
|
||||||
|
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||||
|
if ( versionability[attributeIndex]
|
||||||
|
&& !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||||
|
breakDownJdbcValues( tableUpdateBuilder, session, attribute, loadedState[attributeIndex] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void breakDownJdbcValues(
|
||||||
|
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
AttributeMapping attribute,
|
||||||
|
Object loadedValue) {
|
||||||
|
if ( !tableUpdateBuilder.getMutatingTable()
|
||||||
|
.getTableName()
|
||||||
|
.equals( attribute.getContainingTableExpression() ) ) {
|
||||||
|
// it is not on the root table, skip it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ColumnValueBindingList optimisticLockBindings = tableUpdateBuilder.getOptimisticLockBindings();
|
||||||
|
if ( optimisticLockBindings != null ) {
|
||||||
|
attribute.breakDownJdbcValues(
|
||||||
|
loadedValue,
|
||||||
|
(valueIndex, value, jdbcValueMapping) -> {
|
||||||
|
if ( !tableUpdateBuilder.getKeyRestrictionBindings()
|
||||||
|
.containsColumn(
|
||||||
|
jdbcValueMapping.getSelectableName(),
|
||||||
|
jdbcValueMapping.getJdbcMapping()
|
||||||
|
) ) {
|
||||||
|
optimisticLockBindings.consume( valueIndex, value, jdbcValueMapping );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.persister.entity.mutation;
|
||||||
|
|
||||||
|
import org.hibernate.engine.OptimisticLockStyle;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
||||||
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
|
import org.hibernate.sql.model.MutationOperationGroup;
|
||||||
|
import org.hibernate.sql.model.MutationType;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnValueBindingList;
|
||||||
|
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
||||||
|
import org.hibernate.sql.model.ast.builder.RestrictedTableMutationBuilder;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableDeleteBuilder;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderSkipped;
|
||||||
|
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderStandard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates standard deleting of an entity.
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class DeleteCoordinatorStandard extends AbstractDeleteCoordinator {
|
||||||
|
|
||||||
|
public DeleteCoordinatorStandard(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
||||||
|
super( entityPersister, factory );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MutationOperationGroup generateOperationGroup(
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
boolean applyVersion,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final MutationGroupBuilder deleteGroupBuilder = new MutationGroupBuilder( MutationType.DELETE, entityPersister() );
|
||||||
|
|
||||||
|
entityPersister().forEachMutableTableReverse( (tableMapping) -> {
|
||||||
|
final TableDeleteBuilder tableDeleteBuilder = tableMapping.isCascadeDeleteEnabled()
|
||||||
|
? new TableDeleteBuilderSkipped( tableMapping )
|
||||||
|
: new TableDeleteBuilderStandard( entityPersister(), tableMapping, factory() );
|
||||||
|
deleteGroupBuilder.addTableDetailsBuilder( tableDeleteBuilder );
|
||||||
|
} );
|
||||||
|
|
||||||
|
applyTableDeleteDetails( deleteGroupBuilder, rowId, loadedState, applyVersion, session );
|
||||||
|
|
||||||
|
return createOperationGroup( null, deleteGroupBuilder.buildMutationGroup() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTableDeleteDetails(
|
||||||
|
MutationGroupBuilder deleteGroupBuilder,
|
||||||
|
Object rowId,
|
||||||
|
Object[] loadedState,
|
||||||
|
boolean applyVersion,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
// first, the table key column(s)
|
||||||
|
deleteGroupBuilder.forEachTableMutationBuilder( (builder) -> {
|
||||||
|
final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping();
|
||||||
|
final TableDeleteBuilder tableDeleteBuilder = (TableDeleteBuilder) builder;
|
||||||
|
applyKeyRestriction( rowId, entityPersister(), tableDeleteBuilder, tableMapping );
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( applyVersion ) {
|
||||||
|
// apply any optimistic locking
|
||||||
|
applyOptimisticLocking( deleteGroupBuilder, loadedState, session );
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
if ( persister.hasPartitionedSelectionMapping() ) {
|
||||||
|
final AttributeMappingsList attributeMappings = persister.getAttributeMappings();
|
||||||
|
for ( int m = 0; m < attributeMappings.size(); m++ ) {
|
||||||
|
final AttributeMapping attributeMapping = attributeMappings.get( m );
|
||||||
|
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
||||||
|
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
||||||
|
final SelectableMapping selectableMapping = attributeMapping.getSelectable( i );
|
||||||
|
if ( selectableMapping.isPartitioned() ) {
|
||||||
|
final String tableNameForMutation =
|
||||||
|
persister.physicalTableNameForMutation( selectableMapping );
|
||||||
|
final RestrictedTableMutationBuilder<?, ?> rootTableMutationBuilder =
|
||||||
|
deleteGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
||||||
|
rootTableMutationBuilder.addKeyRestrictionLeniently( selectableMapping );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyOptimisticLocking(
|
||||||
|
MutationGroupBuilder mutationGroupBuilder,
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||||
|
if ( optimisticLockStyle.isVersion() && entityPersister().getVersionMapping() != null ) {
|
||||||
|
applyVersionBasedOptLocking( mutationGroupBuilder );
|
||||||
|
}
|
||||||
|
else if ( loadedState != null && entityPersister().optimisticLockStyle().isAllOrDirty() ) {
|
||||||
|
applyNonVersionOptLocking(
|
||||||
|
optimisticLockStyle,
|
||||||
|
mutationGroupBuilder,
|
||||||
|
loadedState,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyVersionBasedOptLocking(MutationGroupBuilder mutationGroupBuilder) {
|
||||||
|
assert entityPersister().optimisticLockStyle() == OptimisticLockStyle.VERSION;
|
||||||
|
assert entityPersister().getVersionMapping() != null;
|
||||||
|
|
||||||
|
final String tableNameForMutation = entityPersister().physicalTableNameForMutation( entityPersister().getVersionMapping() );
|
||||||
|
final RestrictedTableMutationBuilder<?,?> rootTableMutationBuilder = mutationGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
||||||
|
rootTableMutationBuilder.addOptimisticLockRestriction( entityPersister().getVersionMapping() );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyNonVersionOptLocking(
|
||||||
|
OptimisticLockStyle lockStyle,
|
||||||
|
MutationGroupBuilder mutationGroupBuilder,
|
||||||
|
Object[] loadedState,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final AbstractEntityPersister persister = entityPersister();
|
||||||
|
assert loadedState != null;
|
||||||
|
assert lockStyle.isAllOrDirty();
|
||||||
|
assert persister.optimisticLockStyle().isAllOrDirty();
|
||||||
|
assert session != null;
|
||||||
|
|
||||||
|
final boolean[] versionability = persister.getPropertyVersionability();
|
||||||
|
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||||
|
final AttributeMapping attribute;
|
||||||
|
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||||
|
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||||
|
breakDownJdbcValues( mutationGroupBuilder, session, attribute, loadedState[attributeIndex] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void breakDownJdbcValues(
|
||||||
|
MutationGroupBuilder mutationGroupBuilder,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
AttributeMapping attribute,
|
||||||
|
Object loadedValue) {
|
||||||
|
final RestrictedTableMutationBuilder<?, ?> tableMutationBuilder =
|
||||||
|
mutationGroupBuilder.findTableDetailsBuilder( attribute.getContainingTableExpression() );
|
||||||
|
if ( tableMutationBuilder != null ) {
|
||||||
|
final ColumnValueBindingList optimisticLockBindings = tableMutationBuilder.getOptimisticLockBindings();
|
||||||
|
if ( optimisticLockBindings != null ) {
|
||||||
|
attribute.breakDownJdbcValues(
|
||||||
|
loadedValue,
|
||||||
|
(valueIndex, value, jdbcValueMapping) -> {
|
||||||
|
if ( !tableMutationBuilder.getKeyRestrictionBindings()
|
||||||
|
.containsColumn(
|
||||||
|
jdbcValueMapping.getSelectableName(),
|
||||||
|
jdbcValueMapping.getJdbcMapping()
|
||||||
|
) ) {
|
||||||
|
optimisticLockBindings.consume( valueIndex, value, jdbcValueMapping );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -285,7 +285,7 @@ public class EntityTableMapping implements TableMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class KeyColumn implements SelectableMapping, TableDetails.KeyColumn {
|
public static class KeyColumn implements TableDetails.KeyColumn {
|
||||||
private final String tableName;
|
private final String tableName;
|
||||||
private final String columnName;
|
private final String columnName;
|
||||||
private final String writeExpression;
|
private final String writeExpression;
|
||||||
|
|
|
@ -390,6 +390,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
||||||
|
|
||||||
// add the discriminator
|
// add the discriminator
|
||||||
entityPersister().addDiscriminatorToInsertGroup( insertGroupBuilder );
|
entityPersister().addDiscriminatorToInsertGroup( insertGroupBuilder );
|
||||||
|
entityPersister().addSoftDeleteToInsertGroup( insertGroupBuilder );
|
||||||
|
|
||||||
// add the keys
|
// add the keys
|
||||||
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
|
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
import org.hibernate.metamodel.mapping.TableDetails;
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
|
import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
|
||||||
|
@ -681,6 +682,16 @@ public class AnonymousTupleEntityValuedModelPart
|
||||||
return delegate.getEntityMappingType().getRowIdMapping();
|
return delegate.getEntityMappingType().getRowIdMapping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||||
|
return delegate.getEntityMappingType().getSoftDeleteMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDetails getSoftDeleteTableDetails() {
|
||||||
|
return delegate.getEntityMappingType().getSoftDeleteTableDetails();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer) {
|
public void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer) {
|
||||||
delegate.getEntityMappingType().visitConstraintOrderedTables( consumer );
|
delegate.getEntityMappingType().visitConstraintOrderedTables( consumer );
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.internal;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.action.internal.BulkOperationCleanupAction;
|
||||||
|
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
|
||||||
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.internal.util.MutableObject;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
|
||||||
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
|
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||||
|
import org.hibernate.query.spi.QueryParameterImplementor;
|
||||||
|
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
||||||
|
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||||
|
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||||
|
import org.hibernate.query.sqm.sql.SqmTranslator;
|
||||||
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||||
|
import org.hibernate.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
|
||||||
|
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||||
|
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDeleteQueryPlan<S extends AbstractUpdateOrDeleteStatement, O extends JdbcOperationQueryMutation>
|
||||||
|
implements NonSelectQueryPlan {
|
||||||
|
private final EntityMappingType entityDescriptor;
|
||||||
|
private final SqmDeleteStatement<?> sqmDelete;
|
||||||
|
private final DomainParameterXref domainParameterXref;
|
||||||
|
|
||||||
|
private O jdbcOperation;
|
||||||
|
|
||||||
|
private SqmTranslation<DeleteStatement> sqmInterpretation;
|
||||||
|
private Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref;
|
||||||
|
|
||||||
|
public AbstractDeleteQueryPlan(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
|
DomainParameterXref domainParameterXref) {
|
||||||
|
assert entityDescriptor.getEntityName().equals( sqmDelete.getTarget().getEntityName() );
|
||||||
|
|
||||||
|
this.entityDescriptor = entityDescriptor;
|
||||||
|
this.sqmDelete = sqmDelete;
|
||||||
|
this.domainParameterXref = domainParameterXref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return entityDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(DomainQueryExecutionContext executionContext) {
|
||||||
|
BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete );
|
||||||
|
|
||||||
|
final SharedSessionContractImplementor session = executionContext.getSession();
|
||||||
|
final SessionFactoryImplementor factory = session.getFactory();
|
||||||
|
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||||
|
SqlAstTranslator<O> sqlAstTranslator = null;
|
||||||
|
if ( jdbcOperation == null ) {
|
||||||
|
sqlAstTranslator = createTranslator( executionContext );
|
||||||
|
}
|
||||||
|
|
||||||
|
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||||
|
executionContext.getQueryParameterBindings(),
|
||||||
|
domainParameterXref,
|
||||||
|
jdbcParamsXref,
|
||||||
|
factory.getRuntimeMetamodels().getMappingMetamodel(),
|
||||||
|
sqmInterpretation.getFromClauseAccess()::findTableGroup,
|
||||||
|
new SqmParameterMappingModelResolutionAccess() {
|
||||||
|
@Override @SuppressWarnings("unchecked")
|
||||||
|
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
|
||||||
|
return (MappingModelExpressible<T>) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
session
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( jdbcOperation != null
|
||||||
|
&& ! jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
|
||||||
|
sqlAstTranslator = createTranslator( executionContext );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( sqlAstTranslator != null ) {
|
||||||
|
jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean missingRestriction = sqmInterpretation.getSqlAst().getRestriction() == null;
|
||||||
|
if ( missingRestriction ) {
|
||||||
|
assert domainParameterXref.getSqmParameterCount() == 0;
|
||||||
|
assert jdbcParamsXref.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext );
|
||||||
|
|
||||||
|
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||||
|
entityDescriptor,
|
||||||
|
(tableReference, attributeMapping) -> {
|
||||||
|
final TableGroup collectionTableGroup = new MutatingTableReferenceGroupWrapper(
|
||||||
|
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||||
|
attributeMapping,
|
||||||
|
(NamedTableReference) tableReference
|
||||||
|
);
|
||||||
|
|
||||||
|
final MutableObject<Predicate> additionalPredicate = new MutableObject<>();
|
||||||
|
attributeMapping.applyBaseRestrictions(
|
||||||
|
p -> additionalPredicate.set( Predicate.combinePredicates( additionalPredicate.get(), p ) ),
|
||||||
|
collectionTableGroup,
|
||||||
|
factory.getJdbcServices().getDialect().getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS,
|
||||||
|
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( missingRestriction ) {
|
||||||
|
return additionalPredicate.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||||
|
final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||||
|
collectionTableGroup,
|
||||||
|
fkDescriptor.getKeyPart(),
|
||||||
|
null,
|
||||||
|
factory
|
||||||
|
);
|
||||||
|
|
||||||
|
final QuerySpec matchingIdSubQuery = new QuerySpec( false );
|
||||||
|
|
||||||
|
final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper(
|
||||||
|
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||||
|
attributeMapping,
|
||||||
|
sqmInterpretation.getSqlAst().getTargetTable()
|
||||||
|
);
|
||||||
|
final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||||
|
tableGroup,
|
||||||
|
fkDescriptor.getTargetPart(),
|
||||||
|
sqmInterpretation.getSqlExpressionResolver(),
|
||||||
|
factory
|
||||||
|
);
|
||||||
|
matchingIdSubQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) );
|
||||||
|
|
||||||
|
matchingIdSubQuery.getFromClause().addRoot(
|
||||||
|
tableGroup
|
||||||
|
);
|
||||||
|
|
||||||
|
matchingIdSubQuery.applyPredicate( SqmMutationStrategyHelper.getIdSubqueryPredicate(
|
||||||
|
sqmInterpretation.getSqlAst().getRestriction(),
|
||||||
|
entityDescriptor,
|
||||||
|
tableGroup,
|
||||||
|
session
|
||||||
|
) );
|
||||||
|
|
||||||
|
return Predicate.combinePredicates(
|
||||||
|
additionalPredicate.get(),
|
||||||
|
new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ),
|
||||||
|
executionContextAdapter
|
||||||
|
);
|
||||||
|
|
||||||
|
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||||
|
jdbcOperation,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
sql -> session
|
||||||
|
.getJdbcCoordinator()
|
||||||
|
.getStatementPreparer()
|
||||||
|
.prepareStatement( sql ),
|
||||||
|
(integer, preparedStatement) -> {},
|
||||||
|
executionContextAdapter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SqlAstTranslator<O> createTranslator(DomainQueryExecutionContext executionContext) {
|
||||||
|
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||||
|
final SqmTranslator<DeleteStatement> translator = factory.getQueryEngine().getSqmTranslatorFactory().createSimpleDeleteTranslator(
|
||||||
|
sqmDelete,
|
||||||
|
executionContext.getQueryOptions(),
|
||||||
|
domainParameterXref,
|
||||||
|
executionContext.getQueryParameterBindings(),
|
||||||
|
executionContext.getSession().getLoadQueryInfluencers(),
|
||||||
|
factory
|
||||||
|
);
|
||||||
|
sqmInterpretation = translator.translate();
|
||||||
|
|
||||||
|
this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
|
||||||
|
domainParameterXref,
|
||||||
|
sqmInterpretation::getJdbcParamsBySqmParam
|
||||||
|
);
|
||||||
|
|
||||||
|
final S ast = buildAst( sqmInterpretation, executionContext );
|
||||||
|
return createTranslator( ast, executionContext );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract S buildAst(
|
||||||
|
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||||
|
DomainQueryExecutionContext executionContext);
|
||||||
|
|
||||||
|
protected abstract SqlAstTranslator<O> createTranslator(
|
||||||
|
S ast,
|
||||||
|
DomainQueryExecutionContext executionContext);
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
|
|
@ -759,12 +759,17 @@ public class QuerySqmImpl<R>
|
||||||
private NonSelectQueryPlan buildConcreteDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement sqmDelete) {
|
private NonSelectQueryPlan buildConcreteDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement sqmDelete) {
|
||||||
final EntityDomainType<?> entityDomainType = sqmDelete.getTarget().getModel();
|
final EntityDomainType<?> entityDomainType = sqmDelete.getTarget().getModel();
|
||||||
final String entityNameToDelete = entityDomainType.getHibernateEntityName();
|
final String entityNameToDelete = entityDomainType.getHibernateEntityName();
|
||||||
final EntityPersister persister =
|
final EntityPersister persister = getSessionFactory().getMappingMetamodel().getEntityDescriptor( entityNameToDelete );
|
||||||
getSessionFactory().getMappingMetamodel().getEntityDescriptor( entityNameToDelete );
|
|
||||||
final SqmMultiTableMutationStrategy multiTableStrategy = persister.getSqmMultiTableMutationStrategy();
|
final SqmMultiTableMutationStrategy multiTableStrategy = persister.getSqmMultiTableMutationStrategy();
|
||||||
return multiTableStrategy == null
|
if ( multiTableStrategy != null ) {
|
||||||
? new SimpleDeleteQueryPlan( persister, sqmDelete, domainParameterXref )
|
// NOTE : MultiTableDeleteQueryPlan and SqmMultiTableMutationStrategy already handle soft-deletes internally
|
||||||
: new MultiTableDeleteQueryPlan( sqmDelete, domainParameterXref, multiTableStrategy );
|
return new MultiTableDeleteQueryPlan( sqmDelete, domainParameterXref, multiTableStrategy );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return persister.getSoftDeleteMapping() != null
|
||||||
|
? new SoftDeleteQueryPlan( persister, sqmDelete, domainParameterXref )
|
||||||
|
: new SimpleDeleteQueryPlan( persister, sqmDelete, domainParameterXref );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NonSelectQueryPlan buildAggregatedDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement[] concreteSqmStatements) {
|
private NonSelectQueryPlan buildAggregatedDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement[] concreteSqmStatements) {
|
||||||
|
|
|
@ -6,207 +6,42 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.query.sqm.internal;
|
package org.hibernate.query.sqm.internal;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.hibernate.action.internal.BulkOperationCleanupAction;
|
|
||||||
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
|
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.internal.util.MutableObject;
|
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
|
||||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
|
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
|
||||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
|
||||||
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
|
||||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
|
||||||
import org.hibernate.query.sqm.sql.SqmTranslation;
|
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
|
||||||
import org.hibernate.spi.NavigablePath;
|
|
||||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
|
||||||
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
|
|
||||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
|
||||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
|
||||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
|
||||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
|
||||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
|
||||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
|
||||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
|
public class SimpleDeleteQueryPlan extends AbstractDeleteQueryPlan<DeleteStatement, JdbcOperationQueryDelete> {
|
||||||
private final EntityMappingType entityDescriptor;
|
|
||||||
private final SqmDeleteStatement<?> sqmDelete;
|
|
||||||
private final DomainParameterXref domainParameterXref;
|
|
||||||
|
|
||||||
private JdbcOperationQueryDelete jdbcDelete;
|
|
||||||
private SqmTranslation<DeleteStatement> sqmInterpretation;
|
|
||||||
private Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref;
|
|
||||||
|
|
||||||
public SimpleDeleteQueryPlan(
|
public SimpleDeleteQueryPlan(
|
||||||
EntityMappingType entityDescriptor,
|
EntityMappingType entityDescriptor,
|
||||||
SqmDeleteStatement<?> sqmDelete,
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
DomainParameterXref domainParameterXref) {
|
DomainParameterXref domainParameterXref) {
|
||||||
assert entityDescriptor.getEntityName().equals( sqmDelete.getTarget().getEntityName() );
|
super( entityDescriptor, sqmDelete, domainParameterXref );
|
||||||
|
|
||||||
this.entityDescriptor = entityDescriptor;
|
|
||||||
this.sqmDelete = sqmDelete;
|
|
||||||
this.domainParameterXref = domainParameterXref;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SqlAstTranslator<JdbcOperationQueryDelete> createDeleteTranslator(DomainQueryExecutionContext executionContext) {
|
|
||||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
|
||||||
|
|
||||||
sqmInterpretation =
|
|
||||||
factory.getQueryEngine().getSqmTranslatorFactory().
|
|
||||||
createSimpleDeleteTranslator(
|
|
||||||
sqmDelete,
|
|
||||||
executionContext.getQueryOptions(),
|
|
||||||
domainParameterXref,
|
|
||||||
executionContext.getQueryParameterBindings(),
|
|
||||||
executionContext.getSession().getLoadQueryInfluencers(),
|
|
||||||
factory
|
|
||||||
)
|
|
||||||
.translate();
|
|
||||||
|
|
||||||
this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
|
|
||||||
domainParameterXref,
|
|
||||||
sqmInterpretation::getJdbcParamsBySqmParam
|
|
||||||
);
|
|
||||||
|
|
||||||
return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
|
|
||||||
.buildDeleteTranslator( factory, sqmInterpretation.getSqlAst() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int executeUpdate(DomainQueryExecutionContext executionContext) {
|
protected DeleteStatement buildAst(
|
||||||
BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete );
|
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||||
final SharedSessionContractImplementor session = executionContext.getSession();
|
DomainQueryExecutionContext executionContext) {
|
||||||
final SessionFactoryImplementor factory = session.getFactory();
|
return sqmInterpretation.getSqlAst();
|
||||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
|
||||||
SqlAstTranslator<JdbcOperationQueryDelete> deleteTranslator = null;
|
|
||||||
if ( jdbcDelete == null ) {
|
|
||||||
deleteTranslator = createDeleteTranslator( executionContext );
|
|
||||||
}
|
|
||||||
|
|
||||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
|
||||||
executionContext.getQueryParameterBindings(),
|
|
||||||
domainParameterXref,
|
|
||||||
jdbcParamsXref,
|
|
||||||
factory.getRuntimeMetamodels().getMappingMetamodel(),
|
|
||||||
sqmInterpretation.getFromClauseAccess()::findTableGroup,
|
|
||||||
new SqmParameterMappingModelResolutionAccess() {
|
|
||||||
@Override @SuppressWarnings("unchecked")
|
|
||||||
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
|
|
||||||
return (MappingModelExpressible<T>) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
session
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( jdbcDelete != null
|
|
||||||
&& ! jdbcDelete.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
|
|
||||||
deleteTranslator = createDeleteTranslator( executionContext );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( deleteTranslator != null ) {
|
|
||||||
jdbcDelete = deleteTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean missingRestriction = sqmInterpretation.getSqlAst().getRestriction() == null;
|
|
||||||
if ( missingRestriction ) {
|
|
||||||
assert domainParameterXref.getSqmParameterCount() == 0;
|
|
||||||
assert jdbcParamsXref.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext );
|
|
||||||
|
|
||||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
|
||||||
entityDescriptor,
|
|
||||||
(tableReference, attributeMapping) -> {
|
|
||||||
final TableGroup collectionTableGroup = new MutatingTableReferenceGroupWrapper(
|
|
||||||
new NavigablePath( attributeMapping.getRootPathName() ),
|
|
||||||
attributeMapping,
|
|
||||||
(NamedTableReference) tableReference
|
|
||||||
);
|
|
||||||
|
|
||||||
final MutableObject<Predicate> additionalPredicate = new MutableObject<>();
|
|
||||||
attributeMapping.applyBaseRestrictions(
|
|
||||||
p -> additionalPredicate.set( Predicate.combinePredicates( additionalPredicate.get(), p ) ),
|
|
||||||
collectionTableGroup,
|
|
||||||
factory.getJdbcServices().getDialect().getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS,
|
|
||||||
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( missingRestriction ) {
|
|
||||||
return additionalPredicate.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
|
||||||
final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
|
||||||
collectionTableGroup,
|
|
||||||
fkDescriptor.getKeyPart(),
|
|
||||||
null,
|
|
||||||
factory
|
|
||||||
);
|
|
||||||
|
|
||||||
final QuerySpec matchingIdSubQuery = new QuerySpec( false );
|
|
||||||
|
|
||||||
final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper(
|
|
||||||
new NavigablePath( attributeMapping.getRootPathName() ),
|
|
||||||
attributeMapping,
|
|
||||||
sqmInterpretation.getSqlAst().getTargetTable()
|
|
||||||
);
|
|
||||||
final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
|
||||||
tableGroup,
|
|
||||||
fkDescriptor.getTargetPart(),
|
|
||||||
sqmInterpretation.getSqlExpressionResolver(),
|
|
||||||
factory
|
|
||||||
);
|
|
||||||
matchingIdSubQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) );
|
|
||||||
|
|
||||||
matchingIdSubQuery.getFromClause().addRoot(
|
|
||||||
tableGroup
|
|
||||||
);
|
|
||||||
|
|
||||||
matchingIdSubQuery.applyPredicate( SqmMutationStrategyHelper.getIdSubqueryPredicate(
|
|
||||||
sqmInterpretation.getSqlAst().getRestriction(),
|
|
||||||
entityDescriptor,
|
|
||||||
tableGroup,
|
|
||||||
session
|
|
||||||
) );
|
|
||||||
|
|
||||||
return Predicate.combinePredicates(
|
|
||||||
additionalPredicate.get(),
|
|
||||||
new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false )
|
|
||||||
);
|
|
||||||
},
|
|
||||||
( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ),
|
|
||||||
executionContextAdapter
|
|
||||||
);
|
|
||||||
|
|
||||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
|
||||||
jdbcDelete,
|
|
||||||
jdbcParameterBindings,
|
|
||||||
sql -> session
|
|
||||||
.getJdbcCoordinator()
|
|
||||||
.getStatementPreparer()
|
|
||||||
.prepareStatement( sql ),
|
|
||||||
(integer, preparedStatement) -> {},
|
|
||||||
executionContextAdapter
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SqlAstTranslator<JdbcOperationQueryDelete> createTranslator(
|
||||||
|
DeleteStatement ast,
|
||||||
|
DomainQueryExecutionContext executionContext) {
|
||||||
|
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||||
|
return factory.getJdbcServices()
|
||||||
|
.getJdbcEnvironment()
|
||||||
|
.getSqlAstTranslatorFactory()
|
||||||
|
.buildDeleteTranslator( factory, ast );
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.internal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
|
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||||
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||||
|
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||||
|
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NonSelectQueryPlan for handling DELETE queries against an entity with soft-delete
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SoftDeleteQueryPlan extends AbstractDeleteQueryPlan<UpdateStatement,JdbcOperationQueryUpdate> {
|
||||||
|
public SoftDeleteQueryPlan(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
|
DomainParameterXref domainParameterXref) {
|
||||||
|
super( entityDescriptor, sqmDelete, domainParameterXref );
|
||||||
|
assert entityDescriptor.getSoftDeleteMapping() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UpdateStatement buildAst(
|
||||||
|
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||||
|
DomainQueryExecutionContext executionContext) {
|
||||||
|
final DeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst();
|
||||||
|
final NamedTableReference targetTable = sqlDeleteAst.getTargetTable();
|
||||||
|
final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping();
|
||||||
|
final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping );
|
||||||
|
//noinspection rawtypes,unchecked
|
||||||
|
final JdbcLiteral jdbcLiteral = new JdbcLiteral( columnMapping.getDeletedLiteralValue(), columnMapping.getJdbcMapping() );
|
||||||
|
final Assignment assignment = new Assignment( columnReference, jdbcLiteral );
|
||||||
|
|
||||||
|
return new UpdateStatement(
|
||||||
|
targetTable,
|
||||||
|
Collections.singletonList( assignment ),
|
||||||
|
sqlDeleteAst.getRestriction()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SqlAstTranslator<JdbcOperationQueryUpdate> createTranslator(
|
||||||
|
UpdateStatement sqlUpdateAst,
|
||||||
|
DomainQueryExecutionContext executionContext) {
|
||||||
|
final SharedSessionContractImplementor session = executionContext.getSession();
|
||||||
|
final SessionFactoryImplementor factory = session.getFactory();
|
||||||
|
return factory.getJdbcServices()
|
||||||
|
.getJdbcEnvironment()
|
||||||
|
.getSqlAstTranslatorFactory()
|
||||||
|
.buildUpdateTranslator( factory, sqlUpdateAst );
|
||||||
|
}
|
||||||
|
}
|
|
@ -185,7 +185,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
|
||||||
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
||||||
|
|
||||||
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
|
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
|
||||||
return null;
|
return discriminatorPredicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SqlAstProcessingState rootProcessingState = getCurrentProcessingState();
|
final SqlAstProcessingState rootProcessingState = getCurrentProcessingState();
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.mutation.internal;
|
||||||
|
|
||||||
|
import org.hibernate.Internal;
|
||||||
|
import org.hibernate.internal.log.SubSystemLogging;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@SubSystemLogging(
|
||||||
|
name = "MutationQueryLogging.MUTATION_QUERY_LOGGER_NAME",
|
||||||
|
description = "Logging for multi-table mutation queries"
|
||||||
|
)
|
||||||
|
@Internal
|
||||||
|
public interface MutationQueryLogging {
|
||||||
|
String MUTATION_QUERY_LOGGER_NAME = SubSystemLogging.BASE + ".query.mutation";
|
||||||
|
|
||||||
|
Logger MUTATION_QUERY_LOGGER = Logger.getLogger( MUTATION_QUERY_LOGGER_NAME );
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
|
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
|
||||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||||
|
@ -42,7 +43,6 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
*/
|
*/
|
||||||
public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler {
|
public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler {
|
||||||
|
|
||||||
private static final String DELETE_RESULT_TABLE_NAME_PREFIX = "delete_cte_";
|
private static final String DELETE_RESULT_TABLE_NAME_PREFIX = "delete_cte_";
|
||||||
|
|
||||||
protected CteDeleteHandler(
|
protected CteDeleteHandler(
|
||||||
|
@ -61,7 +61,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
||||||
MultiTableSqmMutationConverter sqmConverter,
|
MultiTableSqmMutationConverter sqmConverter,
|
||||||
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
||||||
SessionFactoryImplementor factory) {
|
SessionFactoryImplementor factory) {
|
||||||
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
|
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||||
final SelectStatement idSelectStatement = (SelectStatement) idSelectCte.getCteDefinition();
|
final SelectStatement idSelectStatement = (SelectStatement) idSelectCte.getCteDefinition();
|
||||||
sqmConverter.getProcessingStateStack().push(
|
sqmConverter.getProcessingStateStack().push(
|
||||||
new SqlAstQueryPartProcessingStateImpl(
|
new SqlAstQueryPartProcessingStateImpl(
|
||||||
|
@ -73,14 +73,13 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
SqmMutationStrategyHelper.visitCollectionTables(
|
SqmMutationStrategyHelper.visitCollectionTables(
|
||||||
(EntityMappingType) updatingTableGroup.getModelPart(),
|
(EntityMappingType) mutatingTableGroup.getModelPart(),
|
||||||
pluralAttribute -> {
|
pluralAttribute -> {
|
||||||
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
|
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
|
||||||
// Ensure that the FK target columns are available
|
// Ensure that the FK target columns are available
|
||||||
final boolean useFkTarget = !pluralAttribute.getKeyDescriptor()
|
final boolean useFkTarget = !pluralAttribute.getKeyDescriptor()
|
||||||
.getTargetPart().isEntityIdentifierMapping();
|
.getTargetPart().isEntityIdentifierMapping();
|
||||||
if ( useFkTarget ) {
|
if ( useFkTarget ) {
|
||||||
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
|
|
||||||
pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections(
|
pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections(
|
||||||
mutatingTableGroup.getNavigablePath(),
|
mutatingTableGroup.getNavigablePath(),
|
||||||
mutatingTableGroup,
|
mutatingTableGroup,
|
||||||
|
@ -141,6 +140,14 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
||||||
|
|
||||||
sqmConverter.getProcessingStateStack().pop();
|
sqmConverter.getProcessingStateStack().pop();
|
||||||
|
|
||||||
|
applyDmlOperations( statement, idSelectCte, factory, mutatingTableGroup );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyDmlOperations(
|
||||||
|
CteContainer statement,
|
||||||
|
CteStatement idSelectCte,
|
||||||
|
SessionFactoryImplementor factory,
|
||||||
|
TableGroup updatingTableGroup) {
|
||||||
getEntityDescriptor().visitConstraintOrderedTables(
|
getEntityDescriptor().visitConstraintOrderedTables(
|
||||||
(tableExpression, tableColumnsVisitationSupplier) -> {
|
(tableExpression, tableColumnsVisitationSupplier) -> {
|
||||||
final String cteTableName = getCteTableName( tableExpression );
|
final String cteTableName = getCteTableName( tableExpression );
|
||||||
|
|
|
@ -94,7 +94,27 @@ public class CteMutationStrategy implements SqmMultiTableMutationStrategy {
|
||||||
DomainParameterXref domainParameterXref,
|
DomainParameterXref domainParameterXref,
|
||||||
DomainQueryExecutionContext context) {
|
DomainQueryExecutionContext context) {
|
||||||
checkMatch( sqmDelete );
|
checkMatch( sqmDelete );
|
||||||
return new CteDeleteHandler( idCteTable, sqmDelete, domainParameterXref, this, sessionFactory ).execute( context );
|
|
||||||
|
final CteDeleteHandler deleteHandler;
|
||||||
|
if ( rootDescriptor.getSoftDeleteMapping() != null ) {
|
||||||
|
deleteHandler = new CteSoftDeleteHandler(
|
||||||
|
idCteTable,
|
||||||
|
sqmDelete,
|
||||||
|
domainParameterXref,
|
||||||
|
this,
|
||||||
|
sessionFactory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deleteHandler = new CteDeleteHandler(
|
||||||
|
idCteTable,
|
||||||
|
sqmDelete,
|
||||||
|
domainParameterXref,
|
||||||
|
this,
|
||||||
|
sessionFactory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return deleteHandler.execute( context );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.mutation.internal.cte;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.cte.CteContainer;
|
||||||
|
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.cte.CteTable;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||||
|
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||||
|
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized CteDeleteHandler for soft-delete handling
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class CteSoftDeleteHandler extends CteDeleteHandler {
|
||||||
|
protected CteSoftDeleteHandler(
|
||||||
|
CteTable cteTable,
|
||||||
|
SqmDeleteStatement<?> sqmDeleteStatement,
|
||||||
|
DomainParameterXref domainParameterXref,
|
||||||
|
CteMutationStrategy strategy,
|
||||||
|
SessionFactoryImplementor sessionFactory) {
|
||||||
|
super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyDmlOperations(
|
||||||
|
CteContainer statement,
|
||||||
|
CteStatement idSelectCte,
|
||||||
|
SessionFactoryImplementor factory,
|
||||||
|
TableGroup updatingTableGroup) {
|
||||||
|
final SoftDeleteMapping softDeleteMapping = getEntityDescriptor().getSoftDeleteMapping();
|
||||||
|
final TableDetails softDeleteTable = getEntityDescriptor().getSoftDeleteTableDetails();
|
||||||
|
final CteTable dmlResultCte = new CteTable(
|
||||||
|
getCteTableName( softDeleteTable.getTableName() ),
|
||||||
|
idSelectCte.getCteTable().getCteColumns()
|
||||||
|
);
|
||||||
|
final TableReference updatingTableReference = updatingTableGroup.getTableReference(
|
||||||
|
updatingTableGroup.getNavigablePath(),
|
||||||
|
softDeleteTable.getTableName(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
final NamedTableReference dmlTableReference = resolveUnionTableReference(
|
||||||
|
updatingTableReference,
|
||||||
|
softDeleteTable.getTableName()
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<ColumnReference> columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() );
|
||||||
|
final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails();
|
||||||
|
keyDetails.forEachKeyColumn( (position, selectable) -> columnReferences.add(
|
||||||
|
new ColumnReference( dmlTableReference, selectable )
|
||||||
|
) );
|
||||||
|
|
||||||
|
final ColumnReference softDeleteColumnReference = new ColumnReference(
|
||||||
|
dmlTableReference,
|
||||||
|
softDeleteMapping
|
||||||
|
);
|
||||||
|
final JdbcLiteral<?> deletedIndicator = new JdbcLiteral<>(
|
||||||
|
softDeleteMapping.getDeletedLiteralValue(),
|
||||||
|
softDeleteMapping.getJdbcMapping()
|
||||||
|
);
|
||||||
|
final Assignment assignment = new Assignment(
|
||||||
|
softDeleteColumnReference,
|
||||||
|
deletedIndicator
|
||||||
|
);
|
||||||
|
|
||||||
|
final MutationStatement dmlStatement = new UpdateStatement(
|
||||||
|
dmlTableReference,
|
||||||
|
Collections.singletonList( assignment ),
|
||||||
|
createIdSubQueryPredicate( columnReferences, idSelectCte, factory ),
|
||||||
|
columnReferences
|
||||||
|
);
|
||||||
|
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,16 +7,18 @@
|
||||||
package org.hibernate.query.sqm.mutation.internal.inline;
|
package org.hibernate.query.sqm.mutation.internal.inline;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.internal.util.MutableInteger;
|
import org.hibernate.internal.util.MutableInteger;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
|
||||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.ModelPart;
|
import org.hibernate.metamodel.mapping.ModelPart;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
||||||
|
@ -28,12 +30,18 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||||
|
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||||
import org.hibernate.sql.exec.spi.JdbcMutationExecutor;
|
import org.hibernate.sql.exec.spi.JdbcMutationExecutor;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
import org.hibernate.sql.exec.spi.StatementCreatorHelper;
|
import org.hibernate.sql.exec.spi.StatementCreatorHelper;
|
||||||
|
|
||||||
|
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||||
|
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createSoftDeleteAssignment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DeleteHandler for the in-line strategy
|
* DeleteHandler for the in-line strategy
|
||||||
*
|
*
|
||||||
|
@ -128,9 +136,18 @@ public class InlineDeleteHandler implements DeleteHandler {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
entityDescriptor.visitConstraintOrderedTables(
|
final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping();
|
||||||
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
|
if ( softDeleteMapping != null ) {
|
||||||
executeDelete(
|
performSoftDelete(
|
||||||
|
entityDescriptor,
|
||||||
|
idsAndFks,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entityDescriptor.visitConstraintOrderedTables(
|
||||||
|
(tableExpression, tableKeyColumnsVisitationSupplier) -> executeDelete(
|
||||||
tableExpression,
|
tableExpression,
|
||||||
entityDescriptor,
|
entityDescriptor,
|
||||||
tableKeyColumnsVisitationSupplier,
|
tableKeyColumnsVisitationSupplier,
|
||||||
|
@ -139,13 +156,71 @@ public class InlineDeleteHandler implements DeleteHandler {
|
||||||
null,
|
null,
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext
|
executionContext
|
||||||
);
|
)
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
return idsAndFks.size();
|
return idsAndFks.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a soft-delete, which just needs to update the root table
|
||||||
|
*/
|
||||||
|
private void performSoftDelete(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
List<Object> idsAndFks,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
DomainQueryExecutionContext executionContext) {
|
||||||
|
final TableDetails softDeleteTable = entityDescriptor.getSoftDeleteTableDetails();
|
||||||
|
final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping();
|
||||||
|
assert softDeleteMapping != null;
|
||||||
|
|
||||||
|
final NamedTableReference targetTableReference = new NamedTableReference(
|
||||||
|
softDeleteTable.getTableName(),
|
||||||
|
DeleteStatement.DEFAULT_ALIAS
|
||||||
|
);
|
||||||
|
|
||||||
|
final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext );
|
||||||
|
|
||||||
|
final Predicate matchingIdsPredicate = matchingIdsPredicateProducer.produceRestriction(
|
||||||
|
idsAndFks,
|
||||||
|
entityDescriptor,
|
||||||
|
0,
|
||||||
|
entityDescriptor.getIdentifierMapping(),
|
||||||
|
targetTableReference,
|
||||||
|
null,
|
||||||
|
executionContextAdapter
|
||||||
|
);
|
||||||
|
|
||||||
|
final Predicate predicate = Predicate.combinePredicates(
|
||||||
|
matchingIdsPredicate,
|
||||||
|
createNonSoftDeletedRestriction( targetTableReference, softDeleteMapping )
|
||||||
|
);
|
||||||
|
|
||||||
|
final Assignment softDeleteAssignment = createSoftDeleteAssignment(
|
||||||
|
targetTableReference,
|
||||||
|
softDeleteMapping
|
||||||
|
);
|
||||||
|
|
||||||
|
final UpdateStatement updateStatement = new UpdateStatement(
|
||||||
|
targetTableReference,
|
||||||
|
Collections.singletonList( softDeleteAssignment ),
|
||||||
|
predicate
|
||||||
|
);
|
||||||
|
|
||||||
|
final JdbcOperationQueryUpdate jdbcOperation = sqlAstTranslatorFactory
|
||||||
|
.buildUpdateTranslator( sessionFactory, updateStatement )
|
||||||
|
.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||||
|
|
||||||
|
jdbcMutationExecutor.execute(
|
||||||
|
jdbcOperation,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
this::prepareQueryStatement,
|
||||||
|
(integer, preparedStatement) -> {},
|
||||||
|
executionContextAdapter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void executeDelete(
|
private void executeDelete(
|
||||||
String targetTableExpression,
|
String targetTableExpression,
|
||||||
EntityMappingType entityDescriptor,
|
EntityMappingType entityDescriptor,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
|
@ -28,6 +29,7 @@ import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.persister.entity.Joinable;
|
import org.hibernate.persister.entity.Joinable;
|
||||||
|
@ -166,7 +168,7 @@ public class InlineUpdateHandler implements UpdateHandler {
|
||||||
|
|
||||||
final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup();
|
final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup();
|
||||||
|
|
||||||
final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference(
|
final NamedTableReference hierarchyRootTableReference = (NamedTableReference) updatingTableGroup.resolveTableReference(
|
||||||
updatingTableGroup.getNavigablePath(),
|
updatingTableGroup.getNavigablePath(),
|
||||||
hierarchyRootTableName
|
hierarchyRootTableName
|
||||||
);
|
);
|
||||||
|
@ -207,7 +209,16 @@ public class InlineUpdateHandler implements UpdateHandler {
|
||||||
final Predicate providedPredicate;
|
final Predicate providedPredicate;
|
||||||
final SqmWhereClause whereClause = sqmUpdate.getWhereClause();
|
final SqmWhereClause whereClause = sqmUpdate.getWhereClause();
|
||||||
if ( whereClause == null || whereClause.getPredicate() == null ) {
|
if ( whereClause == null || whereClause.getPredicate() == null ) {
|
||||||
providedPredicate = null;
|
final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
providedPredicate = SoftDeleteHelper.createNonSoftDeletedRestriction(
|
||||||
|
hierarchyRootTableReference,
|
||||||
|
softDeleteMapping
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
providedPredicate = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
providedPredicate = converterDelegate.visitWhereClause(
|
providedPredicate = converterDelegate.visitWhereClause(
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||||
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
|
import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
|
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||||
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
|
||||||
|
private final EntityMappingType entityDescriptor;
|
||||||
|
private final TemporaryTable idTable;
|
||||||
|
private final AfterUseAction afterUseAction;
|
||||||
|
private final SqmDeleteStatement<?> sqmDelete;
|
||||||
|
private final DomainParameterXref domainParameterXref;
|
||||||
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
|
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||||
|
|
||||||
|
private final MultiTableSqmMutationConverter converter;
|
||||||
|
|
||||||
|
public AbstractDeleteExecutionDelegate(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
TemporaryTable idTable,
|
||||||
|
AfterUseAction afterUseAction,
|
||||||
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
|
DomainParameterXref domainParameterXref,
|
||||||
|
QueryOptions queryOptions,
|
||||||
|
LoadQueryInfluencers loadQueryInfluencers,
|
||||||
|
QueryParameterBindings queryParameterBindings,
|
||||||
|
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||||
|
SessionFactoryImplementor sessionFactory) {
|
||||||
|
this.entityDescriptor = entityDescriptor;
|
||||||
|
this.idTable = idTable;
|
||||||
|
this.afterUseAction = afterUseAction;
|
||||||
|
this.sqmDelete = sqmDelete;
|
||||||
|
this.domainParameterXref = domainParameterXref;
|
||||||
|
this.sessionFactory = sessionFactory;
|
||||||
|
this.sessionUidAccess = sessionUidAccess;
|
||||||
|
|
||||||
|
this.converter = new MultiTableSqmMutationConverter(
|
||||||
|
entityDescriptor,
|
||||||
|
getSqmDelete(),
|
||||||
|
getSqmDelete().getTarget(),
|
||||||
|
getDomainParameterXref(),
|
||||||
|
queryOptions,
|
||||||
|
loadQueryInfluencers,
|
||||||
|
queryParameterBindings,
|
||||||
|
getSessionFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return entityDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TemporaryTable getIdTable() {
|
||||||
|
return idTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AfterUseAction getAfterUseAction() {
|
||||||
|
return afterUseAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqmDeleteStatement<?> getSqmDelete() {
|
||||||
|
return sqmDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DomainParameterXref getDomainParameterXref() {
|
||||||
|
return domainParameterXref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionFactoryImplementor getSessionFactory() {
|
||||||
|
return sessionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<SharedSessionContractImplementor, String> getSessionUidAccess() {
|
||||||
|
return sessionUidAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiTableSqmMutationConverter getConverter() {
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
||||||
import org.hibernate.engine.config.spi.StandardConverters;
|
import org.hibernate.engine.config.spi.StandardConverters;
|
||||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -52,6 +53,10 @@ public class GlobalTemporaryTableStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return temporaryTable.getEntityDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
public void prepare(
|
public void prepare(
|
||||||
MappingModelCreationProcess mappingModelCreationProcess,
|
MappingModelCreationProcess mappingModelCreationProcess,
|
||||||
JdbcConnectionAccess connectionAccess) {
|
JdbcConnectionAccess connectionAccess) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||||
|
|
||||||
import org.hibernate.dialect.temptable.TemporaryTable;
|
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||||
|
@ -51,7 +52,7 @@ public class LocalTemporaryTableMutationStrategy extends LocalTemporaryTableStra
|
||||||
SqmDeleteStatement<?> sqmDelete,
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
DomainParameterXref domainParameterXref,
|
DomainParameterXref domainParameterXref,
|
||||||
DomainQueryExecutionContext context) {
|
DomainQueryExecutionContext context) {
|
||||||
return new TableBasedDeleteHandler(
|
final TableBasedDeleteHandler deleteHandler = new TableBasedDeleteHandler(
|
||||||
sqmDelete,
|
sqmDelete,
|
||||||
domainParameterXref,
|
domainParameterXref,
|
||||||
getTemporaryTable(),
|
getTemporaryTable(),
|
||||||
|
@ -62,7 +63,8 @@ public class LocalTemporaryTableMutationStrategy extends LocalTemporaryTableStra
|
||||||
throw new UnsupportedOperationException( "Unexpected call to access Session uid" );
|
throw new UnsupportedOperationException( "Unexpected call to access Session uid" );
|
||||||
},
|
},
|
||||||
getSessionFactory()
|
getSessionFactory()
|
||||||
).execute( context );
|
);
|
||||||
|
return deleteHandler.execute( context );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
||||||
import org.hibernate.engine.config.spi.StandardConverters;
|
import org.hibernate.engine.config.spi.StandardConverters;
|
||||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,6 +57,10 @@ public class LocalTemporaryTableStrategy {
|
||||||
return temporaryTable;
|
return temporaryTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return getTemporaryTable().getEntityDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDropIdTables() {
|
public boolean isDropIdTables() {
|
||||||
return dropIdTables;
|
return dropIdTables;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
||||||
import org.hibernate.engine.config.spi.StandardConverters;
|
import org.hibernate.engine.config.spi.StandardConverters;
|
||||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -40,7 +41,6 @@ public abstract class PersistentTableStrategy {
|
||||||
public static final String CATALOG = "hibernate.hql.bulk_id_strategy.persistent.catalog";
|
public static final String CATALOG = "hibernate.hql.bulk_id_strategy.persistent.catalog";
|
||||||
|
|
||||||
private final TemporaryTable temporaryTable;
|
private final TemporaryTable temporaryTable;
|
||||||
|
|
||||||
private final SessionFactoryImplementor sessionFactory;
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
|
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
|
@ -58,6 +58,10 @@ public abstract class PersistentTableStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EntityMappingType getEntityDescriptor() {
|
||||||
|
return getTemporaryTable().getEntityDescriptor();
|
||||||
|
}
|
||||||
|
|
||||||
public void prepare(
|
public void prepare(
|
||||||
MappingModelCreationProcess mappingModelCreationProcess,
|
MappingModelCreationProcess mappingModelCreationProcess,
|
||||||
JdbcConnectionAccess connectionAccess) {
|
JdbcConnectionAccess connectionAccess) {
|
||||||
|
|
|
@ -36,7 +36,6 @@ import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
||||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
|
||||||
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
||||||
import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector;
|
import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector;
|
||||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||||
|
@ -62,62 +61,45 @@ import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import static org.hibernate.query.sqm.mutation.internal.MutationQueryLogging.MUTATION_QUERY_LOGGER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
|
public class RestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate {
|
||||||
private static final Logger log = Logger.getLogger( RestrictedDeleteExecutionDelegate.class );
|
|
||||||
|
|
||||||
private final EntityMappingType entityDescriptor;
|
|
||||||
private final TemporaryTable idTable;
|
|
||||||
private final AfterUseAction afterUseAction;
|
|
||||||
private final SqmDeleteStatement<?> sqmDelete;
|
|
||||||
private final DomainParameterXref domainParameterXref;
|
|
||||||
private final SessionFactoryImplementor sessionFactory;
|
|
||||||
|
|
||||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
|
||||||
private final MultiTableSqmMutationConverter converter;
|
|
||||||
|
|
||||||
public RestrictedDeleteExecutionDelegate(
|
public RestrictedDeleteExecutionDelegate(
|
||||||
EntityMappingType entityDescriptor,
|
EntityMappingType entityDescriptor,
|
||||||
TemporaryTable idTable,
|
TemporaryTable idTable,
|
||||||
AfterUseAction afterUseAction,
|
AfterUseAction afterUseAction,
|
||||||
SqmDeleteStatement<?> sqmDelete,
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
DomainParameterXref domainParameterXref,
|
DomainParameterXref domainParameterXref,
|
||||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
|
||||||
QueryOptions queryOptions,
|
QueryOptions queryOptions,
|
||||||
LoadQueryInfluencers loadQueryInfluencers,
|
LoadQueryInfluencers loadQueryInfluencers,
|
||||||
QueryParameterBindings queryParameterBindings,
|
QueryParameterBindings queryParameterBindings,
|
||||||
|
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||||
SessionFactoryImplementor sessionFactory) {
|
SessionFactoryImplementor sessionFactory) {
|
||||||
this.entityDescriptor = entityDescriptor;
|
super(
|
||||||
this.idTable = idTable;
|
|
||||||
this.afterUseAction = afterUseAction;
|
|
||||||
this.sqmDelete = sqmDelete;
|
|
||||||
this.domainParameterXref = domainParameterXref;
|
|
||||||
this.sessionUidAccess = sessionUidAccess;
|
|
||||||
this.sessionFactory = sessionFactory;
|
|
||||||
this.converter = new MultiTableSqmMutationConverter(
|
|
||||||
entityDescriptor,
|
entityDescriptor,
|
||||||
|
idTable,
|
||||||
|
afterUseAction,
|
||||||
sqmDelete,
|
sqmDelete,
|
||||||
sqmDelete.getTarget(),
|
|
||||||
domainParameterXref,
|
domainParameterXref,
|
||||||
queryOptions,
|
queryOptions,
|
||||||
loadQueryInfluencers,
|
loadQueryInfluencers,
|
||||||
queryParameterBindings,
|
queryParameterBindings,
|
||||||
|
sessionUidAccess,
|
||||||
sessionFactory
|
sessionFactory
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int execute(DomainQueryExecutionContext executionContext) {
|
public int execute(DomainQueryExecutionContext executionContext) {
|
||||||
final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels()
|
final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels()
|
||||||
.getMappingMetamodel()
|
.getMappingMetamodel()
|
||||||
.getEntityDescriptor( sqmDelete.getTarget().getEntityName() );
|
.getEntityDescriptor( getSqmDelete().getTarget().getEntityName() );
|
||||||
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
|
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
|
||||||
|
|
||||||
final TableGroup deletingTableGroup = converter.getMutatingTableGroup();
|
final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup();
|
||||||
|
|
||||||
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference(
|
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference(
|
||||||
deletingTableGroup.getNavigablePath(),
|
deletingTableGroup.getNavigablePath(),
|
||||||
|
@ -128,7 +110,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
final Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions;
|
final Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions;
|
||||||
final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
|
final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
|
||||||
|
|
||||||
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
|
if ( getDomainParameterXref().getSqmParameterCount() == 0 ) {
|
||||||
parameterResolutions = Collections.emptyMap();
|
parameterResolutions = Collections.emptyMap();
|
||||||
paramTypeResolutions = Collections.emptyMap();
|
paramTypeResolutions = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
@ -143,8 +125,8 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
// table it comes from. if all of the referenced columns (if any at all) are from the root table
|
// table it comes from. if all of the referenced columns (if any at all) are from the root table
|
||||||
// we can perform all of the deletes without using an id-table
|
// we can perform all of the deletes without using an id-table
|
||||||
final MutableBoolean needsIdTableWrapper = new MutableBoolean( false );
|
final MutableBoolean needsIdTableWrapper = new MutableBoolean( false );
|
||||||
final Predicate specifiedRestriction = converter.visitWhereClause(
|
final Predicate specifiedRestriction = getConverter().visitWhereClause(
|
||||||
sqmDelete.getWhereClause(),
|
getSqmDelete().getWhereClause(),
|
||||||
columnReference -> {
|
columnReference -> {
|
||||||
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
|
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
|
||||||
needsIdTableWrapper.setValue( true );
|
needsIdTableWrapper.setValue( true );
|
||||||
|
@ -169,10 +151,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
true,
|
true,
|
||||||
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||||
null,
|
null,
|
||||||
converter
|
getConverter()
|
||||||
);
|
);
|
||||||
|
|
||||||
converter.pruneTableGroupJoins();
|
getConverter().pruneTableGroupJoins();
|
||||||
|
|
||||||
// We need an id table if we want to delete from an intermediate table to avoid FK violations
|
// We need an id table if we want to delete from an intermediate table to avoid FK violations
|
||||||
// The intermediate table has a FK to the root table, so we can't delete from the root table first
|
// The intermediate table has a FK to the root table, so we can't delete from the root table first
|
||||||
|
@ -198,7 +180,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
deletingTableGroup,
|
deletingTableGroup,
|
||||||
parameterResolutions,
|
parameterResolutions,
|
||||||
paramTypeResolutions,
|
paramTypeResolutions,
|
||||||
converter.getSqlExpressionResolver(),
|
getConverter().getSqlExpressionResolver(),
|
||||||
executionContextAdapter
|
executionContextAdapter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -211,9 +193,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions,
|
Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions,
|
||||||
SqlExpressionResolver sqlExpressionResolver,
|
SqlExpressionResolver sqlExpressionResolver,
|
||||||
ExecutionContext executionContext) {
|
ExecutionContext executionContext) {
|
||||||
assert entityDescriptor == entityDescriptor.getRootEntityDescriptor();
|
assert getEntityDescriptor() == getEntityDescriptor().getRootEntityDescriptor();
|
||||||
|
|
||||||
final EntityPersister rootEntityPersister = entityDescriptor.getEntityPersister();
|
final EntityPersister rootEntityPersister = getEntityDescriptor().getEntityPersister();
|
||||||
final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName();
|
final String rootTableName = ( (Joinable) rootEntityPersister ).getTableName();
|
||||||
final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference(
|
final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference(
|
||||||
tableGroup.getNavigablePath(),
|
tableGroup.getNavigablePath(),
|
||||||
|
@ -226,17 +208,17 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
suppliedPredicate,
|
suppliedPredicate,
|
||||||
rootEntityPersister,
|
rootEntityPersister,
|
||||||
sqlExpressionResolver,
|
sqlExpressionResolver,
|
||||||
sessionFactory
|
getSessionFactory()
|
||||||
);
|
);
|
||||||
|
|
||||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||||
executionContext.getQueryParameterBindings(),
|
executionContext.getQueryParameterBindings(),
|
||||||
domainParameterXref,
|
getDomainParameterXref(),
|
||||||
SqmUtil.generateJdbcParamsXref(
|
SqmUtil.generateJdbcParamsXref(
|
||||||
domainParameterXref,
|
getDomainParameterXref(),
|
||||||
() -> restrictionSqmParameterResolutions
|
() -> restrictionSqmParameterResolutions
|
||||||
),
|
),
|
||||||
sessionFactory.getRuntimeMetamodels().getMappingMetamodel(),
|
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||||
navigablePath -> tableGroup,
|
navigablePath -> tableGroup,
|
||||||
new SqmParameterMappingModelResolutionAccess() {
|
new SqmParameterMappingModelResolutionAccess() {
|
||||||
@Override @SuppressWarnings("unchecked")
|
@Override @SuppressWarnings("unchecked")
|
||||||
|
@ -248,7 +230,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
);
|
);
|
||||||
|
|
||||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||||
entityDescriptor,
|
getEntityDescriptor(),
|
||||||
(tableReference, attributeMapping) -> {
|
(tableReference, attributeMapping) -> {
|
||||||
// No need for a predicate if there is no supplied predicate i.e. this is a full cleanup
|
// No need for a predicate if there is no supplied predicate i.e. this is a full cleanup
|
||||||
if ( suppliedPredicate == null ) {
|
if ( suppliedPredicate == null ) {
|
||||||
|
@ -267,7 +249,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
suppliedPredicate,
|
suppliedPredicate,
|
||||||
rootEntityPersister,
|
rootEntityPersister,
|
||||||
sqlExpressionResolver,
|
sqlExpressionResolver,
|
||||||
sessionFactory
|
getSessionFactory()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new InSubQueryPredicate(
|
return new InSubQueryPredicate(
|
||||||
|
@ -279,7 +261,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
),
|
),
|
||||||
fkDescriptor,
|
fkDescriptor,
|
||||||
null,
|
null,
|
||||||
sessionFactory
|
getSessionFactory()
|
||||||
),
|
),
|
||||||
idSelectFkSubQuery,
|
idSelectFkSubQuery,
|
||||||
false
|
false
|
||||||
|
@ -292,7 +274,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
|
|
||||||
if ( rootTableReference instanceof UnionTableReference ) {
|
if ( rootTableReference instanceof UnionTableReference ) {
|
||||||
final MutableInteger rows = new MutableInteger();
|
final MutableInteger rows = new MutableInteger();
|
||||||
entityDescriptor.visitConstraintOrderedTables(
|
getEntityDescriptor().visitConstraintOrderedTables(
|
||||||
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
||||||
final NamedTableReference tableReference = new NamedTableReference(
|
final NamedTableReference tableReference = new NamedTableReference(
|
||||||
tableExpression,
|
tableExpression,
|
||||||
|
@ -322,7 +304,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
return rows.get();
|
return rows.get();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
entityDescriptor.visitConstraintOrderedTables(
|
getEntityDescriptor().visitConstraintOrderedTables(
|
||||||
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
||||||
if ( !tableExpression.equals( rootTableName ) ) {
|
if ( !tableExpression.equals( rootTableName ) ) {
|
||||||
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference(
|
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference(
|
||||||
|
@ -381,7 +363,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
JdbcParameterBindings jdbcParameterBindings,
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
ExecutionContext executionContext) {
|
ExecutionContext executionContext) {
|
||||||
assert targetTableReference != null;
|
assert targetTableReference != null;
|
||||||
log.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() );
|
MUTATION_QUERY_LOGGER.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() );
|
||||||
|
|
||||||
final NamedTableReference deleteTableReference = new NamedTableReference(
|
final NamedTableReference deleteTableReference = new NamedTableReference(
|
||||||
targetTableReference.getTableExpression(),
|
targetTableReference.getTableExpression(),
|
||||||
|
@ -423,7 +405,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 );
|
deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() );
|
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() );
|
||||||
}
|
}
|
||||||
|
|
||||||
tableDeletePredicate = new InSubQueryPredicate(
|
tableDeletePredicate = new InSubQueryPredicate(
|
||||||
|
@ -439,7 +421,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
log.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
|
MUTATION_QUERY_LOGGER.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,12 +459,12 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
ExecutionContext executionContext) {
|
ExecutionContext executionContext) {
|
||||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||||
executionContext.getQueryParameterBindings(),
|
executionContext.getQueryParameterBindings(),
|
||||||
domainParameterXref,
|
getDomainParameterXref(),
|
||||||
SqmUtil.generateJdbcParamsXref(
|
SqmUtil.generateJdbcParamsXref(
|
||||||
domainParameterXref,
|
getDomainParameterXref(),
|
||||||
() -> restrictionSqmParameterResolutions
|
() -> restrictionSqmParameterResolutions
|
||||||
),
|
),
|
||||||
sessionFactory.getRuntimeMetamodels().getMappingMetamodel(),
|
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||||
navigablePath -> deletingTableGroup,
|
navigablePath -> deletingTableGroup,
|
||||||
new SqmParameterMappingModelResolutionAccess() {
|
new SqmParameterMappingModelResolutionAccess() {
|
||||||
@Override @SuppressWarnings("unchecked")
|
@Override @SuppressWarnings("unchecked")
|
||||||
|
@ -494,7 +476,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
);
|
);
|
||||||
|
|
||||||
ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions(
|
ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions(
|
||||||
idTable,
|
getIdTable(),
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -503,9 +485,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions(
|
ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions(
|
||||||
idTable,
|
getIdTable(),
|
||||||
sessionUidAccess,
|
getSessionUidAccess(),
|
||||||
afterUseAction,
|
getAfterUseAction(),
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -516,23 +498,23 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
ExecutionContext executionContext,
|
ExecutionContext executionContext,
|
||||||
JdbcParameterBindings jdbcParameterBindings) {
|
JdbcParameterBindings jdbcParameterBindings) {
|
||||||
final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable(
|
final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable(
|
||||||
converter,
|
getConverter(),
|
||||||
predicate,
|
predicate,
|
||||||
idTable,
|
getIdTable(),
|
||||||
sessionUidAccess,
|
getSessionUidAccess(),
|
||||||
jdbcParameterBindings,
|
jdbcParameterBindings,
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
|
|
||||||
final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||||
idTable,
|
getIdTable(),
|
||||||
sessionUidAccess,
|
getSessionUidAccess(),
|
||||||
entityDescriptor,
|
getEntityDescriptor(),
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
|
|
||||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||||
entityDescriptor,
|
getEntityDescriptor(),
|
||||||
(tableReference, attributeMapping) -> {
|
(tableReference, attributeMapping) -> {
|
||||||
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||||
final QuerySpec idTableFkSubQuery;
|
final QuerySpec idTableFkSubQuery;
|
||||||
|
@ -541,10 +523,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||||
idTable,
|
getIdTable(),
|
||||||
fkDescriptor.getTargetPart(),
|
fkDescriptor.getTargetPart(),
|
||||||
sessionUidAccess,
|
getSessionUidAccess(),
|
||||||
entityDescriptor,
|
getEntityDescriptor(),
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -557,7 +539,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
),
|
),
|
||||||
fkDescriptor,
|
fkDescriptor,
|
||||||
null,
|
null,
|
||||||
sessionFactory
|
getSessionFactory()
|
||||||
),
|
),
|
||||||
idTableFkSubQuery,
|
idTableFkSubQuery,
|
||||||
false
|
false
|
||||||
|
@ -568,7 +550,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
executionContext
|
executionContext
|
||||||
);
|
);
|
||||||
|
|
||||||
entityDescriptor.visitConstraintOrderedTables(
|
getEntityDescriptor().visitConstraintOrderedTables(
|
||||||
(tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
|
(tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
|
||||||
tableExpression,
|
tableExpression,
|
||||||
tableKeyColumnVisitationSupplier,
|
tableKeyColumnVisitationSupplier,
|
||||||
|
@ -585,11 +567,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
||||||
Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier,
|
Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier,
|
||||||
QuerySpec idTableSubQuery,
|
QuerySpec idTableSubQuery,
|
||||||
ExecutionContext executionContext) {
|
ExecutionContext executionContext) {
|
||||||
log.tracef( "deleteFromTableUsingIdTable - %s", tableExpression );
|
MUTATION_QUERY_LOGGER.tracef( "deleteFromTableUsingIdTable - %s", tableExpression );
|
||||||
|
|
||||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
|
||||||
|
|
||||||
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor );
|
|
||||||
final NamedTableReference targetTable = new NamedTableReference(
|
final NamedTableReference targetTable = new NamedTableReference(
|
||||||
tableExpression,
|
tableExpression,
|
||||||
DeleteStatement.DEFAULT_ALIAS,
|
DeleteStatement.DEFAULT_ALIAS,
|
||||||
|
|
|
@ -0,0 +1,432 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||||
|
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||||
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.internal.util.MutableBoolean;
|
||||||
|
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
|
import org.hibernate.metamodel.mapping.TableDetails;
|
||||||
|
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||||
|
import org.hibernate.query.spi.QueryOptions;
|
||||||
|
import org.hibernate.query.spi.QueryParameterBindings;
|
||||||
|
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||||
|
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
||||||
|
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||||
|
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||||
|
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
||||||
|
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||||
|
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||||
|
import org.hibernate.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||||
|
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
|
||||||
|
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.PredicateCollector;
|
||||||
|
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
|
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||||
|
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||||
|
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
|
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||||
|
|
||||||
|
import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class SoftDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate {
|
||||||
|
public SoftDeleteExecutionDelegate(
|
||||||
|
EntityMappingType entityDescriptor,
|
||||||
|
TemporaryTable idTable,
|
||||||
|
AfterUseAction afterUseAction,
|
||||||
|
SqmDeleteStatement<?> sqmDelete,
|
||||||
|
DomainParameterXref domainParameterXref,
|
||||||
|
QueryOptions queryOptions,
|
||||||
|
LoadQueryInfluencers loadQueryInfluencers,
|
||||||
|
QueryParameterBindings queryParameterBindings,
|
||||||
|
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||||
|
SessionFactoryImplementor sessionFactory) {
|
||||||
|
super(
|
||||||
|
entityDescriptor,
|
||||||
|
idTable,
|
||||||
|
afterUseAction,
|
||||||
|
sqmDelete,
|
||||||
|
domainParameterXref,
|
||||||
|
queryOptions,
|
||||||
|
loadQueryInfluencers,
|
||||||
|
queryParameterBindings,
|
||||||
|
sessionUidAccess,
|
||||||
|
sessionFactory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int execute(DomainQueryExecutionContext domainQueryExecutionContext) {
|
||||||
|
final String targetEntityName = getSqmDelete().getTarget().getEntityName();
|
||||||
|
final EntityPersister targetEntityDescriptor = getSessionFactory()
|
||||||
|
.getRuntimeMetamodels()
|
||||||
|
.getMappingMetamodel()
|
||||||
|
.getEntityDescriptor( targetEntityName );
|
||||||
|
|
||||||
|
final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor();
|
||||||
|
final boolean targetIsHierarchyRoot = rootEntityDescriptor == targetEntityDescriptor;
|
||||||
|
|
||||||
|
// determine if we need to use a sub-query for matching ids -
|
||||||
|
// 1. if the target is not the root we will
|
||||||
|
// 2. if the supplied predicate (if any) refers to columns from a table
|
||||||
|
// other than the identifier table we will
|
||||||
|
final MutableBoolean needsSubQueryRef = new MutableBoolean( !targetIsHierarchyRoot );
|
||||||
|
|
||||||
|
final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( domainQueryExecutionContext );
|
||||||
|
|
||||||
|
final Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions;
|
||||||
|
final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
|
||||||
|
if ( getDomainParameterXref().getSqmParameterCount() == 0 ) {
|
||||||
|
parameterResolutions = Collections.emptyMap();
|
||||||
|
paramTypeResolutions = Collections.emptyMap();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parameterResolutions = new IdentityHashMap<>();
|
||||||
|
paramTypeResolutions = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup();
|
||||||
|
final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails();
|
||||||
|
final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference(
|
||||||
|
deletingTableGroup.getNavigablePath(),
|
||||||
|
softDeleteTable.getTableName()
|
||||||
|
);
|
||||||
|
assert rootTableReference != null;
|
||||||
|
|
||||||
|
// NOTE : `converter.visitWhereClause` already applies the soft-delete restriction
|
||||||
|
final Predicate specifiedRestriction = getConverter().visitWhereClause(
|
||||||
|
getSqmDelete().getWhereClause(),
|
||||||
|
columnReference -> {
|
||||||
|
if ( !rootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
|
||||||
|
// the predicate referred to a column from a table other than hierarchy identifier table
|
||||||
|
needsSubQueryRef.setValue( true );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(sqmParameter, mappingType, jdbcParameters) -> {
|
||||||
|
parameterResolutions.computeIfAbsent(
|
||||||
|
sqmParameter,
|
||||||
|
k -> new ArrayList<>( 1 )
|
||||||
|
).add( jdbcParameters );
|
||||||
|
paramTypeResolutions.put( sqmParameter, mappingType );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction );
|
||||||
|
targetEntityDescriptor.applyBaseRestrictions(
|
||||||
|
(filterPredicate) -> {
|
||||||
|
needsSubQueryRef.setValue( true );
|
||||||
|
predicateCollector.applyPredicate( filterPredicate );
|
||||||
|
},
|
||||||
|
deletingTableGroup,
|
||||||
|
true,
|
||||||
|
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||||
|
null,
|
||||||
|
getConverter()
|
||||||
|
);
|
||||||
|
|
||||||
|
getConverter().pruneTableGroupJoins();
|
||||||
|
|
||||||
|
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||||
|
executionContext.getQueryParameterBindings(),
|
||||||
|
getDomainParameterXref(),
|
||||||
|
SqmUtil.generateJdbcParamsXref(
|
||||||
|
getDomainParameterXref(),
|
||||||
|
() -> parameterResolutions
|
||||||
|
),
|
||||||
|
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||||
|
navigablePath -> deletingTableGroup,
|
||||||
|
new SqmParameterMappingModelResolutionAccess() {
|
||||||
|
@Override @SuppressWarnings("unchecked")
|
||||||
|
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
|
||||||
|
return (MappingModelExpressible<T>) paramTypeResolutions.get(parameter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
executionContext.getSession()
|
||||||
|
);
|
||||||
|
|
||||||
|
final boolean needsSubQuery = needsSubQueryRef.getValue();
|
||||||
|
if ( needsSubQuery ) {
|
||||||
|
if ( getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() ) {
|
||||||
|
return performDeleteWithSubQuery(
|
||||||
|
targetEntityDescriptor,
|
||||||
|
rootEntityDescriptor,
|
||||||
|
deletingTableGroup,
|
||||||
|
rootTableReference,
|
||||||
|
predicateCollector,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
getConverter(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return performDeleteWithIdTable(
|
||||||
|
rootEntityDescriptor,
|
||||||
|
rootTableReference,
|
||||||
|
predicateCollector,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return performDirectDelete(
|
||||||
|
targetEntityDescriptor,
|
||||||
|
rootEntityDescriptor,
|
||||||
|
deletingTableGroup,
|
||||||
|
rootTableReference,
|
||||||
|
predicateCollector,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
getConverter(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int performDeleteWithIdTable(
|
||||||
|
EntityMappingType rootEntityDescriptor,
|
||||||
|
NamedTableReference targetTableReference,
|
||||||
|
PredicateCollector predicateCollector,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
SqmJdbcExecutionContextAdapter executionContext) {
|
||||||
|
ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions(
|
||||||
|
getIdTable(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return deleteUsingIdTable(
|
||||||
|
rootEntityDescriptor,
|
||||||
|
targetTableReference,
|
||||||
|
predicateCollector,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions(
|
||||||
|
getIdTable(),
|
||||||
|
getSessionUidAccess(),
|
||||||
|
getAfterUseAction(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int deleteUsingIdTable(
|
||||||
|
EntityMappingType rootEntityDescriptor,
|
||||||
|
NamedTableReference targetTableReference,
|
||||||
|
PredicateCollector predicateCollector,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
SqmJdbcExecutionContextAdapter executionContext) {
|
||||||
|
final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable(
|
||||||
|
getConverter(),
|
||||||
|
predicateCollector.getPredicate(),
|
||||||
|
getIdTable(),
|
||||||
|
getSessionUidAccess(),
|
||||||
|
jdbcParameterBindings,
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
|
||||||
|
final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||||
|
getIdTable(),
|
||||||
|
getSessionUidAccess(),
|
||||||
|
getEntityDescriptor(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
|
||||||
|
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||||
|
getEntityDescriptor(),
|
||||||
|
(tableReference, attributeMapping) -> {
|
||||||
|
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||||
|
final QuerySpec idTableFkSubQuery;
|
||||||
|
if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) {
|
||||||
|
idTableFkSubQuery = idTableIdentifierSubQuery;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||||
|
getIdTable(),
|
||||||
|
fkDescriptor.getTargetPart(),
|
||||||
|
getSessionUidAccess(),
|
||||||
|
getEntityDescriptor(),
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new InSubQueryPredicate(
|
||||||
|
MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||||
|
new MutatingTableReferenceGroupWrapper(
|
||||||
|
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||||
|
attributeMapping,
|
||||||
|
(NamedTableReference) tableReference
|
||||||
|
),
|
||||||
|
fkDescriptor,
|
||||||
|
null,
|
||||||
|
getSessionFactory()
|
||||||
|
),
|
||||||
|
idTableFkSubQuery,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
JdbcParameterBindings.NO_BINDINGS,
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
|
||||||
|
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||||
|
targetTableReference,
|
||||||
|
rootEntityDescriptor.getSoftDeleteMapping()
|
||||||
|
);
|
||||||
|
|
||||||
|
final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails();
|
||||||
|
final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails();
|
||||||
|
final List<Expression> idExpressions = new ArrayList<>( keyDetails.getColumnCount() );
|
||||||
|
keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add(
|
||||||
|
new ColumnReference( targetTableReference, column )
|
||||||
|
) );
|
||||||
|
final Expression idExpression = idExpressions.size() == 1
|
||||||
|
? idExpressions.get( 0 )
|
||||||
|
: new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() );
|
||||||
|
|
||||||
|
final UpdateStatement updateStatement = new UpdateStatement(
|
||||||
|
targetTableReference,
|
||||||
|
Collections.singletonList( softDeleteAssignment ),
|
||||||
|
new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false )
|
||||||
|
);
|
||||||
|
|
||||||
|
executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int performDeleteWithSubQuery(
|
||||||
|
EntityMappingType targetEntityDescriptor,
|
||||||
|
EntityMappingType rootEntityDescriptor,
|
||||||
|
TableGroup deletingTableGroup,
|
||||||
|
NamedTableReference rootTableReference,
|
||||||
|
PredicateCollector predicateCollector,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
MultiTableSqmMutationConverter converter,
|
||||||
|
SqmJdbcExecutionContextAdapter executionContext) {
|
||||||
|
final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 );
|
||||||
|
matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup );
|
||||||
|
|
||||||
|
final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails();
|
||||||
|
final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails();
|
||||||
|
|
||||||
|
final NamedTableReference targetTable = new NamedTableReference(
|
||||||
|
identifierTableDetails.getTableName(),
|
||||||
|
DeleteStatement.DEFAULT_ALIAS,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<Expression> idExpressions = new ArrayList<>( keyDetails.getColumnCount() );
|
||||||
|
keyDetails.forEachKeyColumn( (position, column) -> {
|
||||||
|
final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression(
|
||||||
|
rootTableReference,
|
||||||
|
column
|
||||||
|
);
|
||||||
|
matchingIdSubQuery.getSelectClause().addSqlSelection(
|
||||||
|
new SqlSelectionImpl( position, columnReference )
|
||||||
|
);
|
||||||
|
idExpressions.add( new ColumnReference( targetTable, column ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() );
|
||||||
|
final Expression idExpression = idExpressions.size() == 1
|
||||||
|
? idExpressions.get( 0 )
|
||||||
|
: new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() );
|
||||||
|
|
||||||
|
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||||
|
targetTable,
|
||||||
|
rootEntityDescriptor.getSoftDeleteMapping()
|
||||||
|
);
|
||||||
|
|
||||||
|
final UpdateStatement updateStatement = new UpdateStatement(
|
||||||
|
targetTable,
|
||||||
|
Collections.singletonList( softDeleteAssignment ),
|
||||||
|
new InSubQueryPredicate( idExpression, matchingIdSubQuery, false )
|
||||||
|
);
|
||||||
|
|
||||||
|
return executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||||
|
}
|
||||||
|
|
||||||
|
private int performDirectDelete(
|
||||||
|
EntityMappingType targetEntityDescriptor,
|
||||||
|
EntityMappingType rootEntityDescriptor,
|
||||||
|
TableGroup deletingTableGroup,
|
||||||
|
NamedTableReference rootTableReference,
|
||||||
|
PredicateCollector predicateCollector,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
MultiTableSqmMutationConverter converter,
|
||||||
|
SqmJdbcExecutionContextAdapter executionContext) {
|
||||||
|
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||||
|
rootTableReference,
|
||||||
|
rootEntityDescriptor.getSoftDeleteMapping()
|
||||||
|
);
|
||||||
|
|
||||||
|
final UpdateStatement updateStatement = new UpdateStatement(
|
||||||
|
rootTableReference,
|
||||||
|
Collections.singletonList( softDeleteAssignment ),
|
||||||
|
predicateCollector.getPredicate()
|
||||||
|
);
|
||||||
|
|
||||||
|
return executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||||
|
}
|
||||||
|
|
||||||
|
private int executeUpdate(
|
||||||
|
UpdateStatement updateStatement,
|
||||||
|
JdbcParameterBindings jdbcParameterBindings,
|
||||||
|
ExecutionContext executionContext) {
|
||||||
|
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||||
|
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||||
|
|
||||||
|
final JdbcOperationQueryUpdate jdbcUpdate = jdbcServices.getJdbcEnvironment()
|
||||||
|
.getSqlAstTranslatorFactory()
|
||||||
|
.buildUpdateTranslator( factory, updateStatement )
|
||||||
|
.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||||
|
|
||||||
|
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||||
|
jdbcUpdate,
|
||||||
|
jdbcParameterBindings,
|
||||||
|
sql -> executionContext.getSession()
|
||||||
|
.getJdbcCoordinator()
|
||||||
|
.getStatementPreparer()
|
||||||
|
.prepareStatement( sql ),
|
||||||
|
(integer, preparedStatement) -> {},
|
||||||
|
executionContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,16 +65,31 @@ public class TableBasedDeleteHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) {
|
protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) {
|
||||||
|
if ( getEntityDescriptor().getSoftDeleteMapping() != null ) {
|
||||||
|
return new SoftDeleteExecutionDelegate(
|
||||||
|
getEntityDescriptor(),
|
||||||
|
idTable,
|
||||||
|
afterUseAction,
|
||||||
|
getSqmDeleteOrUpdateStatement(),
|
||||||
|
domainParameterXref,
|
||||||
|
executionContext.getQueryOptions(),
|
||||||
|
executionContext.getSession().getLoadQueryInfluencers(),
|
||||||
|
executionContext.getQueryParameterBindings(),
|
||||||
|
sessionUidAccess,
|
||||||
|
getSessionFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new RestrictedDeleteExecutionDelegate(
|
return new RestrictedDeleteExecutionDelegate(
|
||||||
getEntityDescriptor(),
|
getEntityDescriptor(),
|
||||||
idTable,
|
idTable,
|
||||||
afterUseAction,
|
afterUseAction,
|
||||||
getSqmDeleteOrUpdateStatement(),
|
getSqmDeleteOrUpdateStatement(),
|
||||||
domainParameterXref,
|
domainParameterXref,
|
||||||
sessionUidAccess,
|
|
||||||
executionContext.getQueryOptions(),
|
executionContext.getQueryOptions(),
|
||||||
executionContext.getSession().getLoadQueryInfluencers(),
|
executionContext.getSession().getLoadQueryInfluencers(),
|
||||||
executionContext.getQueryParameterBindings(),
|
executionContext.getQueryParameterBindings(),
|
||||||
|
sessionUidAccess,
|
||||||
getSessionFactory()
|
getSessionFactory()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||||
import org.hibernate.dialect.temptable.TemporaryTable;
|
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
@ -23,6 +24,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
import org.hibernate.query.results.TableGroupImpl;
|
import org.hibernate.query.results.TableGroupImpl;
|
||||||
|
@ -97,15 +99,29 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
||||||
this.afterUseAction = afterUseAction;
|
this.afterUseAction = afterUseAction;
|
||||||
this.sessionUidAccess = sessionUidAccess;
|
this.sessionUidAccess = sessionUidAccess;
|
||||||
this.updatingTableGroup = updatingTableGroup;
|
this.updatingTableGroup = updatingTableGroup;
|
||||||
this.suppliedPredicate = suppliedPredicate;
|
|
||||||
|
|
||||||
this.sessionFactory = executionContext.getSession().getFactory();
|
this.sessionFactory = executionContext.getSession().getFactory();
|
||||||
|
|
||||||
final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart();
|
final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart();
|
||||||
assert updatingModelPart instanceof EntityMappingType;
|
assert updatingModelPart instanceof EntityMappingType;
|
||||||
|
|
||||||
this.entityDescriptor = (EntityMappingType) updatingModelPart;
|
this.entityDescriptor = (EntityMappingType) updatingModelPart;
|
||||||
|
|
||||||
|
final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping();
|
||||||
|
if ( softDeleteMapping != null ) {
|
||||||
|
final NamedTableReference rootTableReference = (NamedTableReference) updatingTableGroup.resolveTableReference(
|
||||||
|
updatingTableGroup.getNavigablePath(),
|
||||||
|
entityDescriptor.getIdentifierTableDetails().getTableName()
|
||||||
|
);
|
||||||
|
this.suppliedPredicate = Predicate.combinePredicates(
|
||||||
|
suppliedPredicate,
|
||||||
|
SoftDeleteHelper.createNonSoftDeletedRestriction( rootTableReference, softDeleteMapping )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.suppliedPredicate = suppliedPredicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.assignmentsByTable = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 );
|
this.assignmentsByTable = CollectionHelper.mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 );
|
||||||
|
|
||||||
jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||||
|
@ -513,4 +529,5 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
||||||
protected SessionFactoryImplementor getSessionFactory() {
|
protected SessionFactoryImplementor getSessionFactory() {
|
||||||
return sessionFactory;
|
return sessionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6127,7 +6127,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
|
public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
|
||||||
// nothing to do... handled within TableGroup#render
|
// nothing to do... handled within TableGroupTableGroup#render
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -224,6 +224,15 @@ public class ColumnReference implements Expression, Assignable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
if ( StringHelper.isNotEmpty( qualifier ) ) {
|
||||||
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"%s(%s.%s)",
|
||||||
|
getClass().getSimpleName(),
|
||||||
|
qualifier,
|
||||||
|
getExpressionText()
|
||||||
|
);
|
||||||
|
}
|
||||||
return String.format(
|
return String.format(
|
||||||
Locale.ROOT,
|
Locale.ROOT,
|
||||||
"%s(%s)",
|
"%s(%s)",
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class ColumnWriteFragment implements Expression {
|
||||||
|
|
||||||
public ColumnWriteFragment(String fragment, ColumnValueParameter parameter, JdbcMapping jdbcMapping) {
|
public ColumnWriteFragment(String fragment, ColumnValueParameter parameter, JdbcMapping jdbcMapping) {
|
||||||
this( fragment, Collections.singletonList( parameter ), jdbcMapping );
|
this( fragment, Collections.singletonList( parameter ), jdbcMapping );
|
||||||
assert parameter != null;
|
assert !fragment.contains( "?" ) || parameter != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColumnWriteFragment(String fragment, List<ColumnValueParameter> parameters, JdbcMapping jdbcMapping) {
|
public ColumnWriteFragment(String fragment, List<ColumnValueParameter> parameters, JdbcMapping jdbcMapping) {
|
||||||
|
|
|
@ -76,6 +76,11 @@ public abstract class AbstractRestrictedTableMutationBuilder<O extends MutationO
|
||||||
optimisticLockBindings.addRestriction( columnName, columnWriteFragment, jdbcMapping );
|
optimisticLockBindings.addRestriction( columnName, columnWriteFragment, jdbcMapping );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||||
|
keyRestrictionBindings.addRestriction( columnName, sqlLiteralText, jdbcMapping );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWhere(String fragment) {
|
public void setWhere(String fragment) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
|
@ -100,6 +100,8 @@ public interface RestrictedTableMutationBuilder<O extends MutationOperation, M e
|
||||||
*/
|
*/
|
||||||
void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping);
|
void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping);
|
||||||
|
|
||||||
|
void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping);
|
||||||
|
|
||||||
ColumnValueBindingList getKeyRestrictionBindings();
|
ColumnValueBindingList getKeyRestrictionBindings();
|
||||||
|
|
||||||
ColumnValueBindingList getOptimisticLockBindings();
|
ColumnValueBindingList getOptimisticLockBindings();
|
||||||
|
|
|
@ -35,6 +35,10 @@ public class TableDeleteBuilderSkipped implements TableDeleteBuilder {
|
||||||
public void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping) {
|
public void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnValueBindingList getKeyRestrictionBindings() {
|
public ColumnValueBindingList getKeyRestrictionBindings() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.sql.model.ast.builder;
|
package org.hibernate.sql.model.ast.builder;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
import org.hibernate.sql.model.ast.TableInsert;
|
import org.hibernate.sql.model.ast.TableInsert;
|
||||||
|
|
|
@ -48,6 +48,10 @@ public class TableUpdateBuilderSkipped implements TableUpdateBuilder {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnValueBindingList getKeyRestrictionBindings() {
|
public ColumnValueBindingList getKeyRestrictionBindings() {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||||
import org.hibernate.sql.model.MutationOperation;
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
import org.hibernate.sql.model.MutationTarget;
|
import org.hibernate.sql.model.MutationTarget;
|
||||||
import org.hibernate.sql.model.TableMapping;
|
import org.hibernate.sql.model.TableMapping;
|
||||||
|
|
|
@ -103,8 +103,8 @@ class ColumnDefinitions {
|
||||||
SqlStringGenerationContext context) {
|
SqlStringGenerationContext context) {
|
||||||
statement.append( column.getQuotedName( dialect ) );
|
statement.append( column.getQuotedName( dialect ) );
|
||||||
appendColumnDefinition( statement, column, table, metadata, dialect );
|
appendColumnDefinition( statement, column, table, metadata, dialect );
|
||||||
appendConstraints( statement, column, table, dialect, context );
|
|
||||||
appendComment( statement, column, dialect );
|
appendComment( statement, column, dialect );
|
||||||
|
appendConstraints( statement, column, table, dialect, context );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendConstraints(
|
private static void appendConstraints(
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.type;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
||||||
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple pass-through boolean value converter.
|
||||||
|
* Useful in {@linkplain SoftDelete#converter() certain scenarios}.
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class BooleanAsBooleanConverter implements StandardBooleanConverter<Boolean> {
|
||||||
|
public static final BooleanAsBooleanConverter INSTANCE = new BooleanAsBooleanConverter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean convertToDatabaseColumn(Boolean attribute) {
|
||||||
|
return toRelationalValue( attribute );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean convertToEntityAttribute(Boolean dbData) {
|
||||||
|
return toDomainValue( dbData );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean toDomainValue(Boolean relationalForm) {
|
||||||
|
return relationalForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean toRelationalValue(Boolean domainForm) {
|
||||||
|
return domainForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType<Boolean> getDomainJavaType() {
|
||||||
|
return BooleanJavaType.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JavaType<Boolean> getRelationalJavaType() {
|
||||||
|
return BooleanJavaType.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.type;
|
package org.hibernate.type;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeConverter;
|
|
||||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
|
||||||
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
||||||
import org.hibernate.type.descriptor.java.CharacterJavaType;
|
import org.hibernate.type.descriptor.java.CharacterJavaType;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
@ -18,11 +16,7 @@ import org.hibernate.type.descriptor.java.JavaType;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
*/
|
*/
|
||||||
public abstract class CharBooleanConverter
|
public abstract class CharBooleanConverter implements StandardBooleanConverter<Character> {
|
||||||
implements AttributeConverter<Boolean, Character>, BasicValueConverter<Boolean, Character> {
|
|
||||||
/**
|
|
||||||
* Singleton access
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Character convertToDatabaseColumn(Boolean attribute) {
|
public Character convertToDatabaseColumn(Boolean attribute) {
|
||||||
return toRelationalValue( attribute );
|
return toRelationalValue( attribute );
|
||||||
|
|
|
@ -6,12 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.type;
|
package org.hibernate.type;
|
||||||
|
|
||||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
|
||||||
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
||||||
import org.hibernate.type.descriptor.java.IntegerJavaType;
|
import org.hibernate.type.descriptor.java.IntegerJavaType;
|
||||||
import org.hibernate.type.descriptor.java.JavaType;
|
import org.hibernate.type.descriptor.java.JavaType;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeConverter;
|
|
||||||
import jakarta.persistence.Converter;
|
import jakarta.persistence.Converter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,8 +18,7 @@ import jakarta.persistence.Converter;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
@Converter
|
@Converter
|
||||||
public class NumericBooleanConverter implements AttributeConverter<Boolean, Integer>,
|
public class NumericBooleanConverter implements StandardBooleanConverter<Integer> {
|
||||||
BasicValueConverter<Boolean, Integer> {
|
|
||||||
/**
|
/**
|
||||||
* Singleton access
|
* Singleton access
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker for Hibernate defined converters of Boolean-typed domain values
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface StandardBooleanConverter<R> extends StandardConverter<Boolean,R> {
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.type;
|
||||||
|
|
||||||
|
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker for Hibernate supplied {@linkplain AttributeConverter converter} classes.
|
||||||
|
* <p/>
|
||||||
|
* Also implements the Hibernate-specific BasicValueConverter contract
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface StandardConverter<O,R> extends AttributeConverter<O,R>, BasicValueConverter<O,R> {
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ public class TrueFalseConverter extends CharBooleanConverter {
|
||||||
* Singleton access
|
* Singleton access
|
||||||
*/
|
*/
|
||||||
public static final TrueFalseConverter INSTANCE = new TrueFalseConverter();
|
public static final TrueFalseConverter INSTANCE = new TrueFalseConverter();
|
||||||
|
|
||||||
private static final String[] VALUES = {"F", "T"};
|
private static final String[] VALUES = {"F", "T"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class YesNoConverter extends CharBooleanConverter {
|
||||||
* Singleton access
|
* Singleton access
|
||||||
*/
|
*/
|
||||||
public static final YesNoConverter INSTANCE = new YesNoConverter();
|
public static final YesNoConverter INSTANCE = new YesNoConverter();
|
||||||
|
|
||||||
private static final String[] VALUES = {"N", "Y"};
|
private static final String[] VALUES = {"N", "Y"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class FailingAddToBatchTest extends AbstractBatchingTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NotImplementedYet( reason = "Still need to work on entity update executors", strict = false )
|
@NotImplementedYet( reason = "Still need to work on entity update executors" )
|
||||||
public void testUpdate(EntityManagerFactoryScope scope) {
|
public void testUpdate(EntityManagerFactoryScope scope) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
// final Long id = scope.fromTransaction( (em) -> {
|
// final Long id = scope.fromTransaction( (em) -> {
|
||||||
|
@ -80,7 +80,7 @@ public class FailingAddToBatchTest extends AbstractBatchingTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NotImplementedYet( reason = "Still need to work on entity delete executors", strict = false )
|
@NotImplementedYet( reason = "Still need to work on entity delete executors" )
|
||||||
public void testRemove(EntityManagerFactoryScope scope) {
|
public void testRemove(EntityManagerFactoryScope scope) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
// Long id = scope.fromTransaction( em -> {
|
// Long id = scope.fromTransaction( em -> {
|
||||||
|
|
|
@ -41,7 +41,7 @@ import static org.hamcrest.Matchers.nullValue;
|
||||||
@SessionFactory
|
@SessionFactory
|
||||||
public class EntityHidingTests {
|
public class EntityHidingTests {
|
||||||
@Test
|
@Test
|
||||||
@NotImplementedYet( reason = "Contributed entity hiding is not yet implemented", strict = false )
|
@NotImplementedYet( reason = "Contributed entity hiding is not yet implemented" )
|
||||||
public void testModel(SessionFactoryScope scope) {
|
public void testModel(SessionFactoryScope scope) {
|
||||||
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||||
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||||
|
|
|
@ -95,7 +95,6 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected( jiraKey = "HHH-14975", message = "Not yet implemented" )
|
@FailureExpected( jiraKey = "HHH-14975", message = "Not yet implemented" )
|
||||||
@NotImplementedYet
|
|
||||||
@JiraKey( "HHH-14975" )
|
@JiraKey( "HHH-14975" )
|
||||||
public void testAutoAppliedConverterAsNativeQueryResult() {
|
public void testAutoAppliedConverterAsNativeQueryResult() {
|
||||||
inTransaction( (session) -> {
|
inTransaction( (session) -> {
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for collections which contain soft-deletable entities
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel( annotatedClasses = { CollectionOfSoftDeleteTests.Shelf.class, CollectionOfSoftDeleteTests.Book.class } )
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
public class CollectionOfSoftDeleteTests {
|
||||||
|
@BeforeEach
|
||||||
|
void createTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Shelf horror = new Shelf( 1, "Horror" );
|
||||||
|
session.persist( horror );
|
||||||
|
|
||||||
|
final Book theShining = new Book( 1, "The Shining" );
|
||||||
|
horror.addBook( theShining );
|
||||||
|
session.persist( theShining );
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
|
||||||
|
session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "update books set deleted = 'Y' where id = 1" );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void dropTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "delete from books" );
|
||||||
|
statement.execute( "delete from shelves" );
|
||||||
|
} ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Shelf loaded = session.get( Shelf.class, 1 );
|
||||||
|
assertThat( loaded ).isNotNull();
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||||
|
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
assertThat( loaded.getBooks() ).isNotNull();
|
||||||
|
assertThat( loaded.getBooks() ).isEmpty();
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".deleted='N'" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testQueryJoin(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Shelf shelf = session
|
||||||
|
.createSelectionQuery( "from Shelf join fetch books", Shelf.class )
|
||||||
|
.uniqueResult();
|
||||||
|
assertThat( shelf ).isNotNull();
|
||||||
|
assertThat( shelf.getBooks() ).isNotNull();
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".deleted='N'" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="Shelf")
|
||||||
|
@Table(name="shelves")
|
||||||
|
public static class Shelf {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
@OneToMany
|
||||||
|
@JoinColumn(name = "shelf_fk")
|
||||||
|
private Collection<Book> books;
|
||||||
|
|
||||||
|
public Shelf() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Shelf(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Book> getBooks() {
|
||||||
|
return books;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBooks(Collection<Book> books) {
|
||||||
|
this.books = books;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBook(Book book) {
|
||||||
|
if ( books == null ) {
|
||||||
|
books = new ArrayList<>();
|
||||||
|
}
|
||||||
|
books.add( book );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="Book")
|
||||||
|
@Table(name="books")
|
||||||
|
@SoftDelete(converter = YesNoConverter.class)
|
||||||
|
public static class Book {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Book() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Book(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.type.BooleanAsBooleanConverter;
|
||||||
|
import org.hibernate.type.NumericBooleanConverter;
|
||||||
|
import org.hibernate.type.TrueFalseConverter;
|
||||||
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralizes the checks about column names, values, etc.
|
||||||
|
* to avoid problems across dialects
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = {
|
||||||
|
MappingTests.BooleanEntity.class,
|
||||||
|
MappingTests.NumericEntity.class,
|
||||||
|
MappingTests.TrueFalseEntity.class,
|
||||||
|
MappingTests.YesNoEntity.class,
|
||||||
|
MappingTests.ReversedYesNoEntity.class
|
||||||
|
})
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class MappingTests {
|
||||||
|
@Test
|
||||||
|
void verifyEntityMappings(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( BooleanEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"boolean_entity",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( NumericEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"numeric_entity",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( TrueFalseEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"true_false_entity",
|
||||||
|
'T'
|
||||||
|
);
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( YesNoEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"yes_no_entity",
|
||||||
|
'Y'
|
||||||
|
);
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( ReversedYesNoEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"active",
|
||||||
|
"reversed_yes_no_entity",
|
||||||
|
'N'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="BooleanEntity")
|
||||||
|
@Table(name="boolean_entity")
|
||||||
|
@SoftDelete(converter = BooleanAsBooleanConverter.class)
|
||||||
|
public static class BooleanEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="NumericEntity")
|
||||||
|
@Table(name="numeric_entity")
|
||||||
|
@SoftDelete(converter = NumericBooleanConverter.class)
|
||||||
|
public static class NumericEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="TrueFalseEntity")
|
||||||
|
@Table(name="true_false_entity")
|
||||||
|
@SoftDelete(converter = TrueFalseConverter.class)
|
||||||
|
public static class TrueFalseEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="YesNoEntity")
|
||||||
|
@Table(name="yes_no_entity")
|
||||||
|
@SoftDelete(converter = YesNoConverter.class)
|
||||||
|
public static class YesNoEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="ReversedYesNoEntity")
|
||||||
|
@Table(name="reversed_yes_no_entity")
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
public static class ReversedYesNoEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class MappingVerifier {
|
||||||
|
public static void verifyMapping(
|
||||||
|
SoftDeleteMapping mapping,
|
||||||
|
String expectedColumnName,
|
||||||
|
String expectedTableName,
|
||||||
|
Object expectedDeletedLiteralValue) {
|
||||||
|
assertThat( mapping ).isNotNull();
|
||||||
|
assertThat( mapping.getColumnName() ).isEqualTo( expectedColumnName );
|
||||||
|
assertThat( mapping.getTableName() ).isEqualTo( expectedTableName );
|
||||||
|
assertThat( mapping.getDeletedLiteralValue() ).isEqualTo( expectedDeletedLiteralValue );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class ReverseYesNoConverter implements AttributeConverter<Boolean,Character> {
|
||||||
|
@Override
|
||||||
|
public Character convertToDatabaseColumn(Boolean attribute) {
|
||||||
|
return attribute ? 'N' : 'Y';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean convertToEntityAttribute(Character dbData) {
|
||||||
|
if ( dbData == 'Y' ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( dbData == 'N' ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException( "Illegal value [" + dbData + "]; expecting 'Y' or 'N'" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.ObjectNotFoundException;
|
||||||
|
import org.hibernate.annotations.BatchSize;
|
||||||
|
import org.hibernate.annotations.NaturalId;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = { SimpleSoftDeleteTests.SimpleEntity.class, SimpleSoftDeleteTests.BatchLoadable.class })
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
public class SimpleSoftDeleteTests {
|
||||||
|
@BeforeEach
|
||||||
|
void createTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
session.persist( new SimpleEntity( 1, "first" ) );
|
||||||
|
session.persist( new SimpleEntity( 2, "second" ) );
|
||||||
|
session.persist( new SimpleEntity( 3, "third" ) );
|
||||||
|
|
||||||
|
session.persist( new BatchLoadable( 1, "first" ) );
|
||||||
|
session.persist( new BatchLoadable( 2, "second" ) );
|
||||||
|
|
||||||
|
session.flush();
|
||||||
|
|
||||||
|
session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "update simple set removed = 'Y' where id = 1" );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "delete from simple" );
|
||||||
|
statement.execute( "delete from batch_loadable" );
|
||||||
|
} ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSelectionQuery(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
// should not return #1
|
||||||
|
assertThat( session.createQuery( "from SimpleEntity" ).list() ).hasSize( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLoading(SessionFactoryScope scope) {
|
||||||
|
// Load
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
assertThat( session.get( SimpleEntity.class, 1 ) ).isNull();
|
||||||
|
assertThat( session.get( SimpleEntity.class, 2 ) ).isNotNull();
|
||||||
|
assertThat( session.get( SimpleEntity.class, 3 ) ).isNotNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final SimpleEntity reference = session.getReference( SimpleEntity.class, 1 );
|
||||||
|
try {
|
||||||
|
reference.getName();
|
||||||
|
fail( "Expecting to fail" );
|
||||||
|
}
|
||||||
|
catch (ObjectNotFoundException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
final SimpleEntity reference2 = session.getReference( SimpleEntity.class, 2 );
|
||||||
|
reference2.getName();
|
||||||
|
|
||||||
|
final SimpleEntity reference3 = session.getReference( SimpleEntity.class, 3 );
|
||||||
|
reference3.getName();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
final List<SimpleEntity> results = session
|
||||||
|
.byMultipleIds( SimpleEntity.class )
|
||||||
|
// otherwise the first position would contain a null for #1
|
||||||
|
.enableOrderedReturn( false )
|
||||||
|
.multiLoad( 1, 2, 3 );
|
||||||
|
assertThat( results ).hasSize( 2 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "removed='N'" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNaturalIdLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
session.bySimpleNaturalId( SimpleEntity.class ).load( "second" );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "removed='N'" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
final BatchLoadable first = session.getReference( BatchLoadable.class, 1 );
|
||||||
|
final BatchLoadable second = session.getReference( BatchLoadable.class, 2 );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 0 );
|
||||||
|
|
||||||
|
assertThat( Hibernate.isInitialized( first ) ).isFalse();
|
||||||
|
assertThat( Hibernate.isInitialized( second ) ).isFalse();
|
||||||
|
|
||||||
|
// trigger load
|
||||||
|
first.getName();
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||||
|
|
||||||
|
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeletion(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final SimpleEntity reference = session.getReference( SimpleEntity.class, 2 );
|
||||||
|
session.remove( reference );
|
||||||
|
session.flush();
|
||||||
|
|
||||||
|
final List<SimpleEntity> active = session
|
||||||
|
.createSelectionQuery( "from SimpleEntity", SimpleEntity.class )
|
||||||
|
.list();
|
||||||
|
// #1 was "deleted" up front and we just "deleted" #2... only #3 should be active
|
||||||
|
assertThat( active ).hasSize( 1 );
|
||||||
|
assertThat( active.get(0).getId() ).isEqualTo( 3 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFullUpdateMutationQuery(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final int affected = session.createMutationQuery( "update SimpleEntity set name = null" ).executeUpdate();
|
||||||
|
assertThat( affected ).isEqualTo( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRestrictedUpdateMutationQuery(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final int affected = session
|
||||||
|
.createMutationQuery( "update SimpleEntity set name = null where name = 'second'" )
|
||||||
|
.executeUpdate();
|
||||||
|
assertThat( affected ).isEqualTo( 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFullDeleteMutationQuery(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
final int affected = session.createMutationQuery( "delete SimpleEntity" ).executeUpdate();
|
||||||
|
// only #2 and #3
|
||||||
|
assertThat( affected ).isEqualTo( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRestrictedDeleteMutationQuery(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
statementInspector.clear();
|
||||||
|
final int affected = session
|
||||||
|
.createMutationQuery( "delete SimpleEntity where name = 'second'" )
|
||||||
|
.executeUpdate();
|
||||||
|
// only #2
|
||||||
|
assertThat( affected ).isEqualTo( 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implNote Uses YesNoConverter to work across all databases, even those
|
||||||
|
* not supporting an actual BOOLEAN datatype
|
||||||
|
*/
|
||||||
|
@Entity(name="SimpleEntity")
|
||||||
|
@Table(name="simple")
|
||||||
|
@SoftDelete(columnName = "removed", converter = YesNoConverter.class)
|
||||||
|
public static class SimpleEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@NaturalId
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public SimpleEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleEntity(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="BatchLoadable")
|
||||||
|
@Table(name="batch_loadable")
|
||||||
|
@BatchSize(size = 5)
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
public static class BatchLoadable {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public BatchLoadable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchLoadable(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel( annotatedClasses = { ToOneTests.Issue.class, ToOneTests.User.class } )
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
public class ToOneTests {
|
||||||
|
@BeforeEach
|
||||||
|
void createTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final User steve = new User( 1, "Steve" );
|
||||||
|
final User john = new User( 2, "John" );
|
||||||
|
final User jacob = new User( 3, "Jacob" );
|
||||||
|
final User bobby = new User( 4, "Bobby" );
|
||||||
|
session.persist( steve );
|
||||||
|
session.persist( john );
|
||||||
|
session.persist( jacob );
|
||||||
|
session.persist( bobby );
|
||||||
|
|
||||||
|
final Issue first = new Issue( 1, "first", jacob, steve );
|
||||||
|
final Issue second = new Issue( 2, "second", bobby, steve );
|
||||||
|
final Issue third = new Issue( 3, "third", jacob, john );
|
||||||
|
session.persist( first );
|
||||||
|
session.persist( second );
|
||||||
|
session.persist( third );
|
||||||
|
|
||||||
|
// soft-delete John and Bobby
|
||||||
|
session.createMutationQuery( "delete User where id in (2,4)" ).executeUpdate();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void dropTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "delete from issues" );
|
||||||
|
statement.execute( "delete from users" );
|
||||||
|
} ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void basicBaselineTest(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Issue issue1 = session.get( Issue.class, 1 );
|
||||||
|
assertThat( issue1 ).isNotNull();
|
||||||
|
assertThat( issue1.reporter ).isNotNull();
|
||||||
|
assertThat( issue1.assignee ).isNotNull();
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 2 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".reporter_fk" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".active='Y" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsOnlyOnce( "active" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).contains( ".active='Y" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).containsOnlyOnce( "active" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void basicJoinedTest(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Issue issue2 = session.get( Issue.class, 2 );
|
||||||
|
assertThat( issue2 ).isNotNull();
|
||||||
|
assertThat( issue2.reporter ).isNull();
|
||||||
|
assertThat( issue2.assignee ).isNotNull();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void basicSelectedTest(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final Issue issue3 = session.get( Issue.class, 3 );
|
||||||
|
assertThat( issue3 ).isNotNull();
|
||||||
|
assertThat( issue3.reporter ).isNotNull();
|
||||||
|
assertThat( issue3.assignee ).isNull();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="Issue")
|
||||||
|
@Table(name="issues")
|
||||||
|
public static class Issue {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String description;
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name="reporter_fk")
|
||||||
|
@Fetch( FetchMode.JOIN )
|
||||||
|
private User reporter;
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name="assignee_fk")
|
||||||
|
@Fetch( FetchMode.SELECT )
|
||||||
|
private User assignee;
|
||||||
|
|
||||||
|
public Issue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(Integer id, String description, User reporter) {
|
||||||
|
this.id = id;
|
||||||
|
this.description = description;
|
||||||
|
this.reporter = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(Integer id, String description, User reporter, User assignee) {
|
||||||
|
this.id = id;
|
||||||
|
this.description = description;
|
||||||
|
this.reporter = reporter;
|
||||||
|
this.assignee = assignee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="User")
|
||||||
|
@Table(name="users")
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
public static class User {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public User() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public User(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.annotations.SQLDelete;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class ValidationTests {
|
||||||
|
@Test
|
||||||
|
void testLazyToOne() {
|
||||||
|
final Metadata metadata = new MetadataSources().addAnnotatedClass( Person.class )
|
||||||
|
.addAnnotatedClass( Address.class )
|
||||||
|
.buildMetadata();
|
||||||
|
try (SessionFactory sessionFactory = metadata.buildSessionFactory()) {
|
||||||
|
fail( "Expecting a failure" );
|
||||||
|
}
|
||||||
|
catch (UnsupportedMappingException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCustomSql() {
|
||||||
|
final Metadata metadata = new MetadataSources().addAnnotatedClass( NoNo.class )
|
||||||
|
.buildMetadata();
|
||||||
|
try (SessionFactory sessionFactory = metadata.buildSessionFactory()) {
|
||||||
|
fail( "Expecting a failure" );
|
||||||
|
}
|
||||||
|
catch (UnsupportedMappingException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="Person")
|
||||||
|
@Table(name="persons")
|
||||||
|
public static class Person {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name="address_fk")
|
||||||
|
private Address address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="Address")
|
||||||
|
@Table(name="addresses")
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
public static class Address {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name="NoNo")
|
||||||
|
@Table(name="nonos")
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
@SQLDelete( sql = "delete from nonos" )
|
||||||
|
public static class NoNo {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "coll_owned")
|
||||||
|
public class CollectionOwned {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public CollectionOwned() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionOwned(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.NumericBooleanConverter;
|
||||||
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.ManyToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "coll_owner")
|
||||||
|
public class CollectionOwner {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name = "elements", joinColumns = @JoinColumn(name = "owner_fk"))
|
||||||
|
@Column(name = "txt")
|
||||||
|
@SoftDelete(converter = YesNoConverter.class)
|
||||||
|
private Collection<String> elements;
|
||||||
|
|
||||||
|
@ManyToMany
|
||||||
|
@JoinTable(
|
||||||
|
name = "m2m",
|
||||||
|
joinColumns = @JoinColumn(name = "owner_fk"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "owned_fk")
|
||||||
|
)
|
||||||
|
@SoftDelete(columnName = "gone", converter = NumericBooleanConverter.class)
|
||||||
|
private Collection<CollectionOwned> manyToMany;
|
||||||
|
|
||||||
|
protected CollectionOwner() {
|
||||||
|
// for Hibernate use
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionOwner(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getElements() {
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setElements(Collection<String> elements) {
|
||||||
|
this.elements = elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addElement(String element) {
|
||||||
|
if ( elements == null ) {
|
||||||
|
elements = new ArrayList<>();
|
||||||
|
}
|
||||||
|
elements.add( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<CollectionOwned> getManyToMany() {
|
||||||
|
return manyToMany;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManyToMany(Collection<CollectionOwned> manyToMany) {
|
||||||
|
this.manyToMany = manyToMany;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addManyToMany(CollectionOwned element) {
|
||||||
|
if ( manyToMany == null ) {
|
||||||
|
manyToMany = new ArrayList<>();
|
||||||
|
}
|
||||||
|
manyToMany.add( element );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.BatchSize;
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "coll_owner2")
|
||||||
|
public class CollectionOwner2 {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name="batch_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||||
|
@BatchSize(size = 5)
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
private Set<String> batchLoadable;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name="subselect_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||||
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
private Set<String> subSelectLoadable;
|
||||||
|
|
||||||
|
public CollectionOwner2() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionOwner2(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getBatchLoadable() {
|
||||||
|
return batchLoadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBatchLoadable(Set<String> batchLoadable) {
|
||||||
|
this.batchLoadable = batchLoadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getSubSelectLoadable() {
|
||||||
|
return subSelectLoadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubSelectLoadable(Set<String> subSelectLoadable) {
|
||||||
|
this.subSelectLoadable = subSelectLoadable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = CollectionOwner2.class)
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
public class FetchLoadableTests {
|
||||||
|
@BeforeEach
|
||||||
|
void createTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner2 owner = new CollectionOwner2( 1, "first" );
|
||||||
|
final CollectionOwner2 owner2 = new CollectionOwner2( 2, "second" );
|
||||||
|
|
||||||
|
owner.setBatchLoadable( new HashSet<>() );
|
||||||
|
owner.getBatchLoadable().add( "batchable1" );
|
||||||
|
owner.getBatchLoadable().add( "batchable2" );
|
||||||
|
owner.getBatchLoadable().add( "batchable3" );
|
||||||
|
|
||||||
|
owner.setSubSelectLoadable( new HashSet<>() );
|
||||||
|
owner.getSubSelectLoadable().add( "subselectable1" );
|
||||||
|
owner.getSubSelectLoadable().add( "subselectable2" );
|
||||||
|
owner.getSubSelectLoadable().add( "subselectable3" );
|
||||||
|
|
||||||
|
owner2.setBatchLoadable( new HashSet<>() );
|
||||||
|
owner2.getBatchLoadable().add( "batchable21" );
|
||||||
|
owner2.getBatchLoadable().add( "batchable22" );
|
||||||
|
owner2.getBatchLoadable().add( "batchable23" );
|
||||||
|
|
||||||
|
owner2.setSubSelectLoadable( new HashSet<>() );
|
||||||
|
owner2.getSubSelectLoadable().add( "subselectable21" );
|
||||||
|
owner2.getSubSelectLoadable().add( "subselectable22" );
|
||||||
|
owner2.getSubSelectLoadable().add( "subselectable23" );
|
||||||
|
|
||||||
|
session.persist( owner );
|
||||||
|
session.persist( owner2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void dropTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "delete from batch_loadables" );
|
||||||
|
statement.execute( "delete from subselect_loadables" );
|
||||||
|
statement.execute( "delete from coll_owner2" );
|
||||||
|
} ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBatchLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final List<CollectionOwner2> result = session.createQuery(
|
||||||
|
"from CollectionOwner2 order",
|
||||||
|
CollectionOwner2.class
|
||||||
|
).list();
|
||||||
|
|
||||||
|
final CollectionOwner2 first = result.get( 0 );
|
||||||
|
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( first.getBatchLoadable() ) ).isFalse();
|
||||||
|
|
||||||
|
final CollectionOwner2 second = result.get( 1 );
|
||||||
|
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( second.getBatchLoadable() ) ).isFalse();
|
||||||
|
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
// trigger loading one of the batch-loadable collections
|
||||||
|
first.getBatchLoadable().size();
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||||
|
assertThat( Hibernate.isInitialized( first.getBatchLoadable() ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( second.getBatchLoadable() ) ).isTrue();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSubSelectLoading(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final List<CollectionOwner2> result = session.createQuery(
|
||||||
|
"from CollectionOwner2 order",
|
||||||
|
CollectionOwner2.class
|
||||||
|
).list();
|
||||||
|
|
||||||
|
final CollectionOwner2 first = result.get( 0 );
|
||||||
|
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( first.getSubSelectLoadable() ) ).isFalse();
|
||||||
|
|
||||||
|
final CollectionOwner2 second = result.get( 1 );
|
||||||
|
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( second.getSubSelectLoadable() ) ).isFalse();
|
||||||
|
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
// trigger loading one of the subselect-loadable collections
|
||||||
|
first.getSubSelectLoadable().size();
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||||
|
assertThat( Hibernate.isInitialized( first.getSubSelectLoadable() ) ).isTrue();
|
||||||
|
assertThat( Hibernate.isInitialized( second.getSubSelectLoadable() ) ).isTrue();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.NumericBooleanConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "invalid_coll_owner")
|
||||||
|
public class InvalidCollectionOwner {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToMany
|
||||||
|
@JoinColumn(name="owned_fk")
|
||||||
|
@SoftDelete(columnName = "gone", converter = NumericBooleanConverter.class)
|
||||||
|
private Collection<CollectionOwned> oneToMany;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = { CollectionOwner.class, CollectionOwned.class })
|
||||||
|
@SessionFactory(exportSchema = false)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class MappingTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyCollectionMappings(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getCollectionDescriptor( CollectionOwner.class.getName() + ".elements" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"elements",
|
||||||
|
'Y'
|
||||||
|
);
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getCollectionDescriptor( CollectionOwner.class.getName() + ".manyToMany" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||||
|
"gone",
|
||||||
|
"m2m",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = { CollectionOwner.class, CollectionOwned.class })
|
||||||
|
@SessionFactory(useCollectingStatementInspector = true)
|
||||||
|
public class UsageTests {
|
||||||
|
@BeforeEach
|
||||||
|
void createTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwned owned = new CollectionOwned( 1, "owned" );
|
||||||
|
session.persist( owned );
|
||||||
|
|
||||||
|
final CollectionOwner owner = new CollectionOwner( 1, "owner" );
|
||||||
|
owner.addElement( "an element" );
|
||||||
|
owner.addElement( "another element" );
|
||||||
|
owner.addManyToMany( owned );
|
||||||
|
session.persist( owner );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void dropTestData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||||
|
final Statement statement = connection.createStatement();
|
||||||
|
statement.execute( "delete from m2m" );
|
||||||
|
statement.execute( "delete from elements" );
|
||||||
|
statement.execute( "delete from coll_owned" );
|
||||||
|
statement.execute( "delete from coll_owner" );
|
||||||
|
} ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHqlElements(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
} );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='N'" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHqlManyToMany(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
} );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=0" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveElements(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
statementInspector.clear();
|
||||||
|
owner.getElements().clear();
|
||||||
|
} );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "deleted='N'" );
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||||
|
assertThat( owner.getElements() ).hasSize( 0 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveManyToMany(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
statementInspector.clear();
|
||||||
|
owner.getManyToMany().clear();
|
||||||
|
} );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=1" );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "gone=0" );
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||||
|
statementInspector.clear();
|
||||||
|
assertThat( owner.getManyToMany() ).hasSize( 0 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteElement(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
statementInspector.clear();
|
||||||
|
owner.getElements().remove( "an element" );
|
||||||
|
} );
|
||||||
|
// this will be a "recreate"
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "deleted='N'" );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 1 ) ).startsWith( "insert " );
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||||
|
assertThat( owner.getElements() ).hasSize( 1 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteManyToMany(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||||
|
statementInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwned owned = session.get( CollectionOwned.class, 1 );
|
||||||
|
final CollectionOwner owner = session
|
||||||
|
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||||
|
.uniqueResult();
|
||||||
|
statementInspector.clear();
|
||||||
|
owner.getManyToMany().remove( owned );
|
||||||
|
} );
|
||||||
|
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=1" );
|
||||||
|
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "gone=0" );
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||||
|
assertThat( owner.getManyToMany() ).hasSize( 0 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.collections;
|
||||||
|
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class ValidationTests {
|
||||||
|
@Test
|
||||||
|
void testOneToMany() {
|
||||||
|
try {
|
||||||
|
final Metadata metadata = new MetadataSources()
|
||||||
|
.addAnnotatedClass( InvalidCollectionOwner.class )
|
||||||
|
.addAnnotatedClass( CollectionOwned.class )
|
||||||
|
.buildMetadata();
|
||||||
|
}
|
||||||
|
catch (UnsupportedMappingException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.converter;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = TheEntity.class)
|
||||||
|
@SessionFactory( useCollectingStatementInspector = true)
|
||||||
|
public class ConvertedSoftDeleteTests {
|
||||||
|
@Test
|
||||||
|
void verifySchema(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( TheEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"the_entity",
|
||||||
|
'Y'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUsage(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
session.persist( new TheEntity( 1, "it" ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "'N'" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( "'Y'" );
|
||||||
|
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final TheEntity reference = session.getReference( TheEntity.class, 1 );
|
||||||
|
session.remove( reference );
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContainIgnoringCase( "delete " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsIgnoringCase( "update " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.converter;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "the_entity")
|
||||||
|
@SoftDelete(converter = YesNoConverter.class)
|
||||||
|
public class TheEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
protected TheEntity() {
|
||||||
|
// for Hibernate use
|
||||||
|
}
|
||||||
|
|
||||||
|
public TheEntity(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests applying a custom {@linkplain org.hibernate.annotations.SoftDelete#converter() converter}
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.converter;
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||||
|
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||||
|
|
||||||
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel(annotatedClasses = { ReverseYesNoConverter.class, TheEntity.class })
|
||||||
|
@SessionFactory( useCollectingStatementInspector = true)
|
||||||
|
public class ReversedSoftDeleteTests {
|
||||||
|
@Test
|
||||||
|
void verifySchema(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( TheEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"active",
|
||||||
|
"the_entity",
|
||||||
|
'N'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUsage(SessionFactoryScope scope) {
|
||||||
|
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
session.persist( new TheEntity( 1, "it" ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "'Y'" );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( "'N'" );
|
||||||
|
|
||||||
|
sqlInspector.clear();
|
||||||
|
|
||||||
|
scope.inTransaction( (session) -> {
|
||||||
|
final TheEntity reference = session.getReference( TheEntity.class, 1 );
|
||||||
|
session.remove( reference );
|
||||||
|
} );
|
||||||
|
|
||||||
|
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContainIgnoringCase( "delete " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsIgnoringCase( "update " );
|
||||||
|
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "active='N'" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "the_entity")
|
||||||
|
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||||
|
public class TheEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
protected TheEntity() {
|
||||||
|
// for Hibernate use
|
||||||
|
}
|
||||||
|
|
||||||
|
public TheEntity(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link org.hibernate.annotations.SoftDelete} and {@link org.hibernate.annotations.SoftDeleteColumn}
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete;
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.pkg;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Basic;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "the_table")
|
||||||
|
public class AnEntity {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
@Basic
|
||||||
|
private String name;
|
||||||
|
@ElementCollection
|
||||||
|
@CollectionTable(name="elements", joinColumns = @JoinColumn(name = "owner_fk"))
|
||||||
|
@Column(name="txt")
|
||||||
|
private Collection<String> elements;
|
||||||
|
|
||||||
|
protected AnEntity() {
|
||||||
|
// for Hibernate use
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnEntity(Integer id, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.softdelete.pkg;
|
||||||
|
|
||||||
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
|
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.NotImplementedYet;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@DomainModel( annotatedClasses = AnEntity.class )
|
||||||
|
@SessionFactory
|
||||||
|
public class PackageLevelSoftDeleteTests {
|
||||||
|
@Test
|
||||||
|
public void verifyEntitySchema(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getEntityDescriptor( AnEntity.class ).getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"the_table",
|
||||||
|
'Y'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyCollectionSchema(SessionFactoryScope scope) {
|
||||||
|
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||||
|
MappingVerifier.verifyMapping(
|
||||||
|
metamodel.getCollectionDescriptor( AnEntity.class.getName() + ".elements" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||||
|
"deleted",
|
||||||
|
"elements",
|
||||||
|
'Y'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue