HHH-17164 - Proper, first-class soft-delete support

https://hibernate.atlassian.net/browse/HHH-17164
This commit is contained in:
Steve Ebersole 2023-09-06 09:29:57 -05:00
parent e323ec3d4a
commit 96a000e8ab
111 changed files with 5771 additions and 910 deletions

View File

@ -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;
}

View File

@ -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;
}
} }

View File

@ -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 ) ) {

View File

@ -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 :-/

View File

@ -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 );
}
}

View File

@ -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);
} }

View File

@ -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()

View File

@ -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;

View File

@ -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()

View File

@ -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

View File

@ -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(

View File

@ -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;
}
} }

View File

@ -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 );

View File

@ -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();
}

View File

@ -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 );

View File

@ -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

View File

@ -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();

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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
*/ */

View File

@ -678,7 +678,8 @@ public class MappingModelCreationHelper {
style, style,
cascadeStyle, cascadeStyle,
declaringType, declaringType,
collectionDescriptor collectionDescriptor,
creationProcess
) ); ) );
creationProcess.registerInitializationCallback( creationProcess.registerInitializationCallback(

View File

@ -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,

View File

@ -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 + ")";
}
}

View File

@ -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 ) {

View File

@ -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 );

View File

@ -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
);
} }

View File

@ -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,

View File

@ -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

View File

@ -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();
} }

View File

@ -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;
}
}

View File

@ -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
}
} }

View File

@ -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
);
}
}
}

View File

@ -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
);
}
}
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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 );

View File

@ -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);
}

View File

@ -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

View File

@ -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) {

View File

@ -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 );
}
} }

View File

@ -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 );
}
}

View File

@ -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();

View File

@ -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 );
}

View File

@ -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 );

View File

@ -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

View File

@ -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 ) );
}
}

View File

@ -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,

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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 );
} }
} }

View File

@ -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;
} }

View File

@ -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) {

View File

@ -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,

View File

@ -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
);
}
}

View File

@ -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()
); );
} }

View File

@ -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;
} }
} }

View File

@ -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
} }

View File

@ -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)",

View File

@ -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) {

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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 );

View File

@ -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
*/ */

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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

View File

@ -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

View File

@ -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 -> {

View File

@ -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();

View File

@ -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) -> {

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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'" );
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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();
} );
}
}

View File

@ -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;
}

View File

@ -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
);
}
}

View File

@ -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 );
} );
}
}

View File

@ -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) {
}
}
}

View File

@ -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'" );
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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'" );
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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