HHH-17164 - Proper, first-class soft-delete support
https://hibernate.atlassian.net/browse/HHH-17164
This commit is contained in:
parent
e323ec3d4a
commit
96a000e8ab
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.type.BooleanAsBooleanConverter;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
|
||||
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PACKAGE;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Describes a soft-delete indicator mapping.
|
||||
* <p/>
|
||||
* Soft deletes handle "deletions" from a database table by setting a column in
|
||||
* the table to indicate deletion.
|
||||
* <p/>
|
||||
* May be defined at various levels<ul>
|
||||
* <li>
|
||||
* {@linkplain ElementType#PACKAGE PACKAGE}, where it applies to all
|
||||
* mappings defined in the package, unless defined more specifically.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@linkplain ElementType#TYPE TYPE}, where it applies to an entity hierarchy.
|
||||
* The annotation must be defined on the root of the hierarchy and affects to the
|
||||
* hierarchy as a whole. The soft-delete column is assumed to be on the hierarchy's
|
||||
* root table.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@linkplain ElementType#FIELD FIELD} / {@linkplain ElementType#METHOD METHOD}, where
|
||||
* it applies to the rows of an {@link jakarta.persistence.ElementCollection} or
|
||||
* {@link jakarta.persistence.ManyToMany} table.
|
||||
* </li>
|
||||
* <li>
|
||||
* {@linkplain ElementType#ANNOTATION_TYPE ANNOTATION_TYPE} on another annotation
|
||||
* defined according to the previous targets.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 6.4
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Target({PACKAGE, TYPE, FIELD, METHOD, ANNOTATION_TYPE})
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
public @interface SoftDelete {
|
||||
/**
|
||||
* (Optional) The name of the column.
|
||||
* <p/>
|
||||
* Defaults to {@code deleted}.
|
||||
*/
|
||||
String columnName() default "deleted";
|
||||
|
||||
/**
|
||||
* (Optional) The Java type used for values when dealing with JDBC.
|
||||
* This type should match the "relational type" of the specified
|
||||
* {@linkplain #converter() converter}.
|
||||
* <p/>
|
||||
* By default, Hibernate will inspect the {@linkplain #converter() converter}
|
||||
* and determine the proper type from its signature.
|
||||
*
|
||||
* @apiNote Sometimes useful since {@linkplain #converter() converter}
|
||||
* signatures are not required to be parameterized.
|
||||
*/
|
||||
Class<?> jdbcType() default void.class;
|
||||
|
||||
/**
|
||||
* (Optional) Conversion to apply to determine the appropriate value to
|
||||
* store in the database. The "domain representation" can be: <dl>
|
||||
* <dt>{@code true}</dt>
|
||||
* <dd>Indicates that the row is considered deleted</dd>
|
||||
*
|
||||
* <dt>{@code false}</dt>
|
||||
* <dd>Indicates that the row is considered NOT deleted</dd>
|
||||
* </dl>
|
||||
* <p/>
|
||||
* By default, values are stored as booleans in the database according to
|
||||
* the {@linkplain Dialect#getPreferredSqlTypeCodeForBoolean() dialect}
|
||||
* and {@linkplain org.hibernate.cfg.MappingSettings#PREFERRED_BOOLEAN_JDBC_TYPE settings}
|
||||
*
|
||||
* @apiNote The converter should never return {@code null}
|
||||
*/
|
||||
Class<? extends AttributeConverter<Boolean,?>> converter() default BooleanAsBooleanConverter.class;
|
||||
}
|
|
@ -36,12 +36,15 @@ import org.hibernate.annotations.OnDeleteAction;
|
|||
import org.hibernate.annotations.SqlFragmentAlias;
|
||||
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.annotations.common.reflection.XPackage;
|
||||
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.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.PropertyData;
|
||||
import org.hibernate.dialect.DatabaseVersion;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
import org.hibernate.mapping.AttributeContainer;
|
||||
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.internal.util.StringHelper.isEmpty;
|
||||
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.property.access.spi.BuiltInPropertyAccessStrategies.EMBEDDED;
|
||||
import static org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies.NOOP;
|
||||
|
@ -1120,4 +1124,41 @@ public class BinderHelper {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an annotation from the package-info for the package the given class is defined in
|
||||
*
|
||||
* @param annotationType The type of annotation to return
|
||||
* @param xClass The class in the package
|
||||
* @param context The processing context
|
||||
*
|
||||
* @return The annotation or {@code null}
|
||||
*/
|
||||
public static <A extends Annotation> A extractFromPackage(
|
||||
Class<A> annotationType,
|
||||
XClass xClass,
|
||||
MetadataBuildingContext context) {
|
||||
|
||||
// todo (soft-delete) : or if we want caching of this per package
|
||||
// +
|
||||
// final SoftDelete fromPackage = context.getMetadataCollector().resolvePackageAnnotation( packageName, SoftDelete.class );
|
||||
// +
|
||||
// where context.getMetadataCollector() can cache some of this - either the annotations themselves
|
||||
// or even just the XPackage resolutions
|
||||
|
||||
final String declaringClassName = xClass.getName();
|
||||
final String packageName = qualifier( declaringClassName );
|
||||
if ( isNotEmpty( packageName ) ) {
|
||||
final ClassLoaderService classLoaderService = context.getBootstrapContext()
|
||||
.getServiceRegistry()
|
||||
.getService( ClassLoaderService.class );
|
||||
assert classLoaderService != null;
|
||||
final Package declaringClassPackage = classLoaderService.packageForNameOrNull( packageName );
|
||||
if ( declaringClassPackage != null ) {
|
||||
// will be null when there is no `package-info.class`
|
||||
final XPackage xPackage = context.getBootstrapContext().getReflectionManager().toXPackage( declaringClassPackage );
|
||||
return xPackage.getAnnotation( annotationType );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ import org.hibernate.annotations.SQLOrder;
|
|||
import org.hibernate.annotations.SQLRestriction;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.annotations.SortComparator;
|
||||
import org.hibernate.annotations.SortNatural;
|
||||
import org.hibernate.annotations.Synchronize;
|
||||
|
@ -94,6 +95,7 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
import org.hibernate.mapping.Backref;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
import org.hibernate.mapping.CheckConstraint;
|
||||
import org.hibernate.mapping.Collection;
|
||||
import org.hibernate.mapping.Column;
|
||||
|
@ -110,6 +112,7 @@ import org.hibernate.mapping.SimpleValue;
|
|||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.hibernate.metamodel.CollectionClassification;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
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.GeneratorBinder.buildGenerators;
|
||||
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.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
|
||||
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
|
||||
|
@ -423,6 +427,13 @@ public abstract class CollectionBinder {
|
|||
+ 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 )
|
||||
&& manyToMany != null && !manyToMany.mappedBy().isEmpty() ) {
|
||||
throw new AnnotationException("Collection '" + getPath( propertyHolder, inferredData ) +
|
||||
|
@ -2475,6 +2486,7 @@ public abstract class CollectionBinder {
|
|||
final Table collectionTable = tableBinder.bind();
|
||||
collection.setCollectionTable( collectionTable );
|
||||
handleCheckConstraints( collectionTable );
|
||||
processSoftDeletes();
|
||||
}
|
||||
|
||||
private void handleCheckConstraints(Table collectionTable) {
|
||||
|
@ -2500,6 +2512,31 @@ public abstract class CollectionBinder {
|
|||
: 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(
|
||||
XClass elementType,
|
||||
PersistentClass collectionEntity,
|
||||
|
@ -2528,6 +2565,7 @@ public abstract class CollectionBinder {
|
|||
// this is a ToOne with a @JoinTable or a regular property
|
||||
: otherSidePropertyValue.getTable();
|
||||
collection.setCollectionTable( table );
|
||||
processSoftDeletes();
|
||||
|
||||
if ( property.isAnnotationPresent( Checks.class )
|
||||
|| property.isAnnotationPresent( Check.class ) ) {
|
||||
|
|
|
@ -50,13 +50,14 @@ import org.hibernate.annotations.SQLDeleteAll;
|
|||
import org.hibernate.annotations.SQLDeletes;
|
||||
import org.hibernate.annotations.SQLInsert;
|
||||
import org.hibernate.annotations.SQLInserts;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.annotations.SQLUpdates;
|
||||
import org.hibernate.annotations.SQLRestriction;
|
||||
import org.hibernate.annotations.SecondaryRow;
|
||||
import org.hibernate.annotations.SecondaryRows;
|
||||
import org.hibernate.annotations.SelectBeforeUpdate;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.annotations.Subselect;
|
||||
import org.hibernate.annotations.Synchronize;
|
||||
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.processElementAnnotations;
|
||||
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.internal.util.StringHelper.isEmpty;
|
||||
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
||||
|
@ -214,6 +216,7 @@ public class EntityBinder {
|
|||
final InheritanceState inheritanceState = inheritanceStates.get( clazzToProcess );
|
||||
final PersistentClass superEntity = getSuperEntity( clazzToProcess, inheritanceStates, context, inheritanceState );
|
||||
detectedAttributeOverrideProblem( clazzToProcess, superEntity );
|
||||
|
||||
final PersistentClass persistentClass = makePersistentClass( inheritanceState, superEntity, context );
|
||||
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
||||
entityBinder.bindEntity();
|
||||
|
@ -233,6 +236,7 @@ public class EntityBinder {
|
|||
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
||||
if ( persistentClass instanceof RootClass ) {
|
||||
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
||||
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, inheritanceState, context );
|
||||
}
|
||||
if ( persistentClass instanceof Subclass) {
|
||||
assert superEntity != null;
|
||||
|
@ -248,6 +252,51 @@ public class EntityBinder {
|
|||
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() {
|
||||
if ( annotatedClass.isAnnotationPresent( Checks.class ) ) {
|
||||
// if we have more than one of them they are not overrideable :-/
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.boot.model.internal;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||
import org.hibernate.boot.model.relational.Database;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
import org.hibernate.mapping.Column;
|
||||
import org.hibernate.mapping.SoftDeletable;
|
||||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.metamodel.mapping.SoftDeletableModelPart;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
import org.hibernate.metamodel.mapping.internal.SoftDeleteMappingImpl;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
||||
|
||||
import static org.hibernate.internal.util.StringHelper.coalesce;
|
||||
import static org.hibernate.query.sqm.ComparisonOperator.EQUAL;
|
||||
|
||||
/**
|
||||
* Helper for dealing with {@link org.hibernate.annotations.SoftDelete}
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SoftDeleteHelper {
|
||||
|
||||
public static final String DEFAULT_COLUMN_NAME = "deleted";
|
||||
|
||||
/**
|
||||
* Creates and binds the column and value for modeling the soft-delete in the database
|
||||
*
|
||||
* @param softDeleteConfig The SoftDelete annotation
|
||||
* @param target The thing which is to be soft-deleted
|
||||
* @param table The table to which the soft-delete should be applied
|
||||
* @param context The processing context for access to needed info and services
|
||||
*/
|
||||
public static void bindSoftDeleteIndicator(
|
||||
SoftDelete softDeleteConfig,
|
||||
SoftDeletable target,
|
||||
Table table,
|
||||
MetadataBuildingContext context) {
|
||||
final BasicValue softDeleteIndicatorValue = createSoftDeleteIndicatorValue( softDeleteConfig, table, context );
|
||||
final Column softDeleteIndicatorColumn = createSoftDeleteIndicatorColumn(
|
||||
softDeleteConfig,
|
||||
softDeleteIndicatorValue,
|
||||
context
|
||||
);
|
||||
table.addColumn( softDeleteIndicatorColumn );
|
||||
target.enableSoftDelete( softDeleteIndicatorColumn );
|
||||
}
|
||||
|
||||
public static BasicValue createSoftDeleteIndicatorValue(
|
||||
SoftDelete softDelete,
|
||||
Table table,
|
||||
MetadataBuildingContext context) {
|
||||
final ClassBasedConverterDescriptor converterDescriptor = new ClassBasedConverterDescriptor(
|
||||
softDelete.converter(),
|
||||
context.getBootstrapContext().getClassmateContext()
|
||||
);
|
||||
|
||||
final BasicValue softDeleteIndicatorValue = new BasicValue( context, table );
|
||||
softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
|
||||
softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> {
|
||||
return converterDescriptor.getRelationalValueResolvedType().getErasedType();
|
||||
} );
|
||||
return softDeleteIndicatorValue;
|
||||
}
|
||||
|
||||
public static Column createSoftDeleteIndicatorColumn(
|
||||
SoftDelete softDeleteConfig,
|
||||
BasicValue softDeleteIndicatorValue,
|
||||
MetadataBuildingContext context) {
|
||||
final Column softDeleteColumn = new Column();
|
||||
|
||||
applyColumnName( softDeleteColumn, softDeleteConfig, context );
|
||||
|
||||
softDeleteColumn.setLength( 1 );
|
||||
softDeleteColumn.setNullable( false );
|
||||
softDeleteColumn.setUnique( false );
|
||||
softDeleteColumn.setComment( "Soft-delete indicator" );
|
||||
|
||||
softDeleteColumn.setValue( softDeleteIndicatorValue );
|
||||
softDeleteIndicatorValue.addColumn( softDeleteColumn );
|
||||
|
||||
return softDeleteColumn;
|
||||
}
|
||||
|
||||
private static void applyColumnName(
|
||||
Column softDeleteColumn,
|
||||
SoftDelete softDeleteConfig,
|
||||
MetadataBuildingContext context) {
|
||||
final Database database = context.getMetadataCollector().getDatabase();
|
||||
final PhysicalNamingStrategy namingStrategy = context.getBuildingOptions().getPhysicalNamingStrategy();
|
||||
final String logicalColumnName = softDeleteConfig == null
|
||||
? DEFAULT_COLUMN_NAME
|
||||
: coalesce( DEFAULT_COLUMN_NAME, softDeleteConfig.columnName() );
|
||||
final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName(
|
||||
database.toIdentifier( logicalColumnName ),
|
||||
database.getJdbcEnvironment()
|
||||
);
|
||||
softDeleteColumn.setName( physicalColumnName.render( database.getDialect() ) );
|
||||
}
|
||||
|
||||
public static SoftDeleteMappingImpl resolveSoftDeleteMapping(
|
||||
SoftDeletableModelPart softDeletableModelPart,
|
||||
SoftDeletable bootMapping,
|
||||
String tableName,
|
||||
MappingModelCreationProcess creationProcess) {
|
||||
return resolveSoftDeleteMapping(
|
||||
softDeletableModelPart,
|
||||
bootMapping,
|
||||
tableName,
|
||||
creationProcess.getCreationContext().getDialect()
|
||||
);
|
||||
}
|
||||
|
||||
public static SoftDeleteMappingImpl resolveSoftDeleteMapping(
|
||||
SoftDeletableModelPart softDeletableModelPart,
|
||||
SoftDeletable bootMapping,
|
||||
String tableName,
|
||||
Dialect dialect) {
|
||||
final Column softDeleteColumn = bootMapping.getSoftDeleteColumn();
|
||||
if ( softDeleteColumn == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final BasicValue columnValue = (BasicValue) softDeleteColumn.getValue();
|
||||
final BasicValue.Resolution<?> resolution = columnValue.resolve();
|
||||
//noinspection unchecked
|
||||
final BasicValueConverter<Boolean, Object> converter = (BasicValueConverter<Boolean, Object>) resolution.getValueConverter();
|
||||
//noinspection unchecked
|
||||
final JdbcLiteralFormatter<Object> literalFormatter = resolution.getJdbcMapping().getJdbcLiteralFormatter();
|
||||
|
||||
final Object deletedLiteralValue = converter.toRelationalValue( true );
|
||||
final Object nonDeletedLiteralValue = converter.toRelationalValue( false );
|
||||
|
||||
return new SoftDeleteMappingImpl(
|
||||
softDeletableModelPart,
|
||||
softDeleteColumn.getName(),
|
||||
tableName,
|
||||
deletedLiteralValue,
|
||||
literalFormatter.toJdbcLiteral( deletedLiteralValue, dialect, null ),
|
||||
nonDeletedLiteralValue,
|
||||
literalFormatter.toJdbcLiteral( nonDeletedLiteralValue, dialect, null ),
|
||||
resolution.getJdbcMapping()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SQL AST Predicate for restricting matches to non-deleted rows
|
||||
*
|
||||
* @param tableReference The table reference for the table containing the soft-delete column
|
||||
* @param softDeleteMapping The soft-delete mapping
|
||||
*/
|
||||
public static Predicate createNonSoftDeletedRestriction(
|
||||
TableReference tableReference,
|
||||
SoftDeleteMapping softDeleteMapping) {
|
||||
final ColumnReference softDeleteColumn = new ColumnReference( tableReference, softDeleteMapping );
|
||||
final JdbcLiteral<?> notDeletedLiteral = new JdbcLiteral<>(
|
||||
softDeleteMapping.getNonDeletedLiteralValue(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
return new ComparisonPredicate( softDeleteColumn, EQUAL, notDeletedLiteral );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SQL AST Predicate for restricting matches to non-deleted rows
|
||||
*
|
||||
* @param tableReference The table reference for the table containing the soft-delete column
|
||||
* @param softDeleteMapping The soft-delete mapping
|
||||
*/
|
||||
public static Predicate createNonSoftDeletedRestriction(
|
||||
TableReference tableReference,
|
||||
SoftDeleteMapping softDeleteMapping,
|
||||
SqlExpressionResolver expressionResolver) {
|
||||
final Expression softDeleteColumn = expressionResolver.resolveSqlExpression( tableReference, softDeleteMapping );
|
||||
final JdbcLiteral<?> notDeletedLiteral = new JdbcLiteral<>(
|
||||
softDeleteMapping.getNonDeletedLiteralValue(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
return new ComparisonPredicate( softDeleteColumn, EQUAL, notDeletedLiteral );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SQL AST Assignment for setting the soft-delete column to its
|
||||
* deleted indicate value
|
||||
*
|
||||
* @param tableReference The table reference for the table containing the soft-delete column
|
||||
* @param softDeleteMapping The soft-delete mapping
|
||||
*/
|
||||
public static Assignment createSoftDeleteAssignment(
|
||||
TableReference tableReference,
|
||||
SoftDeleteMapping softDeleteMapping) {
|
||||
final ColumnReference softDeleteColumn = new ColumnReference( tableReference, softDeleteMapping );
|
||||
final JdbcLiteral<?> softDeleteIndicator = new JdbcLiteral<>(
|
||||
softDeleteMapping.getDeletedLiteralValue(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
return new Assignment( softDeleteColumn, softDeleteIndicator );
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import org.hibernate.dialect.Dialect;
|
|||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.loader.internal.AliasConstantsHelper;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public final class StringHelper {
|
||||
|
@ -854,6 +855,22 @@ public final class StringHelper {
|
|||
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> {
|
||||
String render(T value);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.hibernate.metamodel.mapping.ValuedModelPart;
|
|||
import org.hibernate.metamodel.mapping.internal.IdClassEmbeddable;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
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.exec.internal.JdbcParameterBindingImpl;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||
|
@ -95,6 +97,10 @@ public class CollectionBatchLoaderArrayParam
|
|||
getSessionFactory()
|
||||
);
|
||||
|
||||
final QuerySpec querySpec = sqlSelect.getQueryPart().getFirstQuerySpec();
|
||||
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||
|
||||
jdbcSelectOperation = getSessionFactory().getJdbcServices()
|
||||
.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
|
|
|
@ -16,6 +16,8 @@ import org.hibernate.loader.ast.spi.CollectionBatchLoader;
|
|||
import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
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.exec.spi.JdbcOperationQuerySelect;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
|
@ -71,6 +73,11 @@ public class CollectionBatchLoaderInPredicate
|
|||
jdbcParametersBuilder::add,
|
||||
sessionFactory
|
||||
);
|
||||
|
||||
final QuerySpec querySpec = sqlAst.getQueryPart().getFirstQuerySpec();
|
||||
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||
|
||||
this.jdbcParameters = jdbcParametersBuilder.build();
|
||||
assert this.jdbcParameters.size() == this.sqlBatchSize * this.keyColumnCount;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.hibernate.loader.ast.internal;
|
|||
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
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.metamodel.mapping.PluralAttributeMapping;
|
||||
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.exec.internal.BaseExecutionContext;
|
||||
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
|
||||
|
@ -64,6 +65,12 @@ public class CollectionLoaderSingleKey implements CollectionLoader {
|
|||
jdbcParametersBuilder::add,
|
||||
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.jdbcSelect = sessionFactory.getJdbcServices()
|
||||
.getJdbcEnvironment()
|
||||
|
|
|
@ -26,6 +26,8 @@ import org.hibernate.loader.ast.spi.CollectionLoader;
|
|||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
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.exec.spi.JdbcOperationQuerySelect;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
|
@ -61,6 +63,10 @@ public class CollectionLoaderSubSelectFetch implements CollectionLoader {
|
|||
jdbcParameter -> {},
|
||||
session.getFactory()
|
||||
);
|
||||
|
||||
final QuerySpec querySpec = sqlAst.getQueryPart().getFirstQuerySpec();
|
||||
final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 );
|
||||
attributeMapping.applySoftDeleteRestrictions( tableGroup, querySpec::applyPredicate );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,7 +77,6 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
final EntityIdentifierMapping identifierMapping = getLoadable().getIdentifierMapping();
|
||||
|
||||
final int expectedNumberOfParameters = identifierMapping.getJdbcTypeCount() * sqlBatchSize;
|
||||
|
||||
final JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder( expectedNumberOfParameters );
|
||||
sqlAst = LoaderSelectBuilder.createSelect(
|
||||
getLoadable(),
|
||||
|
@ -177,7 +176,7 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
getLoadable().getEntityName(),
|
||||
pkValue,
|
||||
startIndex,
|
||||
startIndex + ( sqlBatchSize -1)
|
||||
startIndex + ( sqlBatchSize - 1 )
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -187,120 +186,8 @@ public class EntityBatchLoaderInPredicate<T>
|
|||
},
|
||||
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
|
||||
public String toString() {
|
||||
return String.format(
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.hibernate.usertype.UserCollectionType;
|
|||
*
|
||||
* @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_KEY_COLUMN_NAME = "id";
|
||||
|
@ -99,6 +99,8 @@ public abstract class Collection implements Fetchable, Value, Filterable {
|
|||
private boolean customDeleteAllCallable;
|
||||
private ExecuteUpdateResultCheckStyle deleteAllCheckStyle;
|
||||
|
||||
private Column softDeleteColumn;
|
||||
|
||||
private String loaderName;
|
||||
|
||||
/**
|
||||
|
@ -827,4 +829,14 @@ public abstract class Collection implements Fetchable, Value, Filterable {
|
|||
public boolean isColumnUpdateable(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableSoftDelete(Column indicatorColumn) {
|
||||
this.softDeleteColumn = indicatorColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Column getSoftDeleteColumn() {
|
||||
return softDeleteColumn;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
|
|||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public class RootClass extends PersistentClass implements TableOwner {
|
||||
public class RootClass extends PersistentClass implements TableOwner, SoftDeletable {
|
||||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( RootClass.class );
|
||||
|
||||
@Deprecated(since = "6.2") @Remove
|
||||
|
@ -58,6 +58,7 @@ public class RootClass extends PersistentClass implements TableOwner {
|
|||
private int nextSubclassId;
|
||||
private Property declaredIdentifierProperty;
|
||||
private Property declaredVersion;
|
||||
private Column softDeleteColumn;
|
||||
|
||||
public RootClass(MetadataBuildingContext buildingContext) {
|
||||
super( buildingContext );
|
||||
|
@ -401,6 +402,16 @@ public class RootClass extends PersistentClass implements TableOwner {
|
|||
return tables;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableSoftDelete(Column indicatorColumn) {
|
||||
this.softDeleteColumn = indicatorColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Column getSoftDeleteColumn() {
|
||||
return softDeleteColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object accept(PersistentClassVisitor mv) {
|
||||
return mv.accept( this );
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.mapping;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SoftDeletable {
|
||||
void enableSoftDelete(Column indicatorColumn);
|
||||
Column getSoftDeleteColumn();
|
||||
}
|
|
@ -9,6 +9,10 @@ package org.hibernate.metamodel;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.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 UnsupportedMappingException(String message) {
|
||||
super( message );
|
||||
|
|
|
@ -56,7 +56,8 @@ import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCH
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface EntityMappingType
|
||||
extends ManagedMappingType, EntityValuedModelPart, Loadable, Restrictable, Discriminable {
|
||||
extends ManagedMappingType, EntityValuedModelPart, Loadable, Restrictable, Discriminable,
|
||||
SoftDeletableModelPart {
|
||||
|
||||
/**
|
||||
* The entity name.
|
||||
|
@ -356,6 +357,18 @@ public interface EntityMappingType
|
|||
*/
|
||||
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
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface PluralAttributeMapping
|
||||
extends AttributeMapping, TableGroupJoinProducer, FetchableContainer, Loadable, Restrictable {
|
||||
extends AttributeMapping, TableGroupJoinProducer, FetchableContainer, Loadable, Restrictable, SoftDeletableModelPart {
|
||||
|
||||
CollectionPersister getCollectionDescriptor();
|
||||
|
||||
|
@ -44,6 +44,13 @@ public interface PluralAttributeMapping
|
|||
@Override
|
||||
CollectionMappingType<?> getMappedType();
|
||||
|
||||
@FunctionalInterface
|
||||
interface PredicateConsumer {
|
||||
void applyPredicate(Predicate predicate);
|
||||
}
|
||||
|
||||
void applySoftDeleteRestrictions(TableGroup tableGroup, PredicateConsumer predicateConsumer);
|
||||
|
||||
interface IndexMetadata {
|
||||
CollectionPart getIndexDescriptor();
|
||||
int getListIndexBase();
|
||||
|
@ -56,6 +63,13 @@ public interface PluralAttributeMapping
|
|||
|
||||
CollectionIdentifierDescriptor getIdentifierDescriptor();
|
||||
|
||||
/**
|
||||
* Mapping for soft-delete support, or {@code null} if soft-delete not defined
|
||||
*/
|
||||
default SoftDeleteMapping getSoftDeleteMapping() {
|
||||
return null;
|
||||
}
|
||||
|
||||
OrderByFragment getOrderByFragment();
|
||||
OrderByFragment getManyToManyOrderByFragment();
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.metamodel.mapping;
|
||||
|
||||
/**
|
||||
* Model part which can be soft-deleted
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SoftDeletableModelPart extends ModelPartContainer {
|
||||
/**
|
||||
* Get the mapping of the soft-delete indicator
|
||||
*/
|
||||
SoftDeleteMapping getSoftDeleteMapping();
|
||||
|
||||
TableDetails getSoftDeleteTableDetails();
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.metamodel.mapping;
|
||||
|
||||
/**
|
||||
*
|
||||
* Metadata about the indicator column for entities and collections enabled
|
||||
* for soft delete
|
||||
*
|
||||
* @see org.hibernate.annotations.SoftDelete
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface SoftDeleteMapping extends SelectableMapping, VirtualModelPart, SqlExpressible {
|
||||
/**
|
||||
* The name of the soft-delete indicator column.
|
||||
*/
|
||||
String getColumnName();
|
||||
|
||||
/**
|
||||
* The name of the table which holds the {@linkplain #getColumnName() indicator column}
|
||||
*/
|
||||
String getTableName();
|
||||
|
||||
/**
|
||||
* The SQL literal value which indicates a deleted row
|
||||
*/
|
||||
Object getDeletedLiteralValue();
|
||||
|
||||
/**
|
||||
* The String representation of the SQL literal value which indicates a deleted row
|
||||
*/
|
||||
String getDeletedLiteralText();
|
||||
|
||||
/**
|
||||
* The SQL literal value which indicates a non-deleted row
|
||||
*
|
||||
* @apiNote The inverse of {@linkplain #getDeletedLiteralValue()}
|
||||
*/
|
||||
Object getNonDeletedLiteralValue();
|
||||
|
||||
/**
|
||||
* The String representation of the SQL literal value which indicates a non-deleted row
|
||||
*/
|
||||
String getNonDeletedLiteralText();
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// SelectableMapping
|
||||
|
||||
@Override
|
||||
default String getSelectionExpression() {
|
||||
return getColumnName();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getSelectableName() {
|
||||
return getColumnName();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getWriteExpression() {
|
||||
return getNonDeletedLiteralText();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getContainingTableExpression() {
|
||||
return getTableName();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getCustomReadExpression() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getCustomWriteExpression() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isFormula() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isNullable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isInsertable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isUpdateable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isPartitioned() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getColumnDefinition() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Long getLength() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Integer getPrecision() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Integer getScale() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@ public interface TableDetails {
|
|||
/**
|
||||
* Details about a column within the key group
|
||||
*/
|
||||
interface KeyColumn {
|
||||
interface KeyColumn extends SelectableMapping {
|
||||
/**
|
||||
* The name of the column
|
||||
*/
|
||||
|
|
|
@ -678,7 +678,8 @@ public class MappingModelCreationHelper {
|
|||
style,
|
||||
cascadeStyle,
|
||||
declaringType,
|
||||
collectionDescriptor
|
||||
collectionDescriptor,
|
||||
creationProcess
|
||||
) );
|
||||
|
||||
creationProcess.registerInitializationCallback(
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||
import org.hibernate.engine.FetchStyle;
|
||||
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.PluralAttributeMapping;
|
||||
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.OrderByFragmentTranslator;
|
||||
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
|
||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.persister.collection.mutation.CollectionMutationTarget;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
import org.hibernate.property.access.spi.PropertyAccess;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -71,6 +75,9 @@ import org.hibernate.sql.results.graph.collection.internal.SelectEagerCollection
|
|||
|
||||
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
|
||||
*/
|
||||
|
@ -106,6 +113,8 @@ public class PluralAttributeMappingImpl
|
|||
private final CollectionIdentifierDescriptor identifierDescriptor;
|
||||
private final FetchTiming fetchTiming;
|
||||
private final FetchStyle fetchStyle;
|
||||
private final SoftDeleteMapping softDeleteMapping;
|
||||
private Boolean hasSoftDelete;
|
||||
|
||||
private final String bidirectionalAttributeName;
|
||||
|
||||
|
@ -136,7 +145,8 @@ public class PluralAttributeMappingImpl
|
|||
FetchStyle fetchStyle,
|
||||
CascadeStyle cascadeStyle,
|
||||
ManagedMappingType declaringType,
|
||||
CollectionPersister collectionDescriptor) {
|
||||
CollectionPersister collectionDescriptor,
|
||||
MappingModelCreationProcess creationProcess) {
|
||||
super( attributeName, fetchableIndex, declaringType );
|
||||
this.propertyAccess = propertyAccess;
|
||||
this.attributeMetadata = attributeMetadata;
|
||||
|
@ -193,9 +203,12 @@ public class PluralAttributeMappingImpl
|
|||
}
|
||||
};
|
||||
|
||||
softDeleteMapping = resolveSoftDeleteMapping( this, bootDescriptor, getSeparateCollectionTable(), creationProcess.getCreationContext().getDialect() );
|
||||
|
||||
injectAttributeMapping( elementDescriptor, indexDescriptor, collectionDescriptor, this );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For Hibernate Reactive
|
||||
*/
|
||||
|
@ -210,6 +223,8 @@ public class PluralAttributeMappingImpl
|
|||
this.identifierDescriptor = original.identifierDescriptor;
|
||||
this.fetchTiming = original.fetchTiming;
|
||||
this.fetchStyle = original.fetchStyle;
|
||||
this.softDeleteMapping = original.softDeleteMapping;
|
||||
this.hasSoftDelete = original.hasSoftDelete;
|
||||
this.collectionDescriptor = original.collectionDescriptor;
|
||||
this.referencedPropertyName = original.referencedPropertyName;
|
||||
this.mapKeyPropertyName = original.mapKeyPropertyName;
|
||||
|
@ -335,6 +350,16 @@ public class PluralAttributeMappingImpl
|
|||
return identifierDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||
return softDeleteMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDetails getSoftDeleteTableDetails() {
|
||||
return ( (CollectionMutationTarget) getCollectionDescriptor() ).getCollectionTableMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrderByFragment getOrderByFragment() {
|
||||
return orderByFragment;
|
||||
|
@ -401,6 +426,39 @@ public class PluralAttributeMappingImpl
|
|||
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
|
||||
public <T> DomainResult<T> createDomainResult(
|
||||
NavigablePath navigablePath,
|
||||
|
@ -643,6 +701,7 @@ public class PluralAttributeMappingImpl
|
|||
boolean addsPredicate,
|
||||
SqlAstCreationState creationState) {
|
||||
final PredicateCollector predicateCollector = new PredicateCollector();
|
||||
|
||||
final TableGroup tableGroup = createRootTableGroupJoin(
|
||||
navigablePath,
|
||||
lhs,
|
||||
|
@ -672,6 +731,12 @@ public class PluralAttributeMappingImpl
|
|||
creationState
|
||||
);
|
||||
|
||||
applySoftDeleteRestriction(
|
||||
predicateCollector::applyPredicate,
|
||||
tableGroup,
|
||||
creationState
|
||||
);
|
||||
|
||||
return new TableGroupJoin(
|
||||
navigablePath,
|
||||
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
|
||||
public TableGroup createRootTableGroupJoin(
|
||||
NavigablePath navigablePath,
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.hibernate.cache.MutableCacheKeyBuilder;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.IndexedConsumer;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.SoftDeletableModelPart;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.mapping.TableDetails;
|
||||
import org.hibernate.metamodel.model.domain.NavigableRole;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* SoftDeleteMapping implementation
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SoftDeleteMappingImpl implements SoftDeleteMapping {
|
||||
public static final String ROLE_NAME = "{soft-delete}";
|
||||
|
||||
private final SoftDeletableModelPart softDeletable;
|
||||
private final String columnName;
|
||||
private final String tableName;
|
||||
private final Object deletedLiteralValue;
|
||||
private final String deletedLiteralText;
|
||||
private final Object nonDeletedLiteralValue;
|
||||
private final String nonDeletedLiteralText;
|
||||
private final JdbcMapping jdbcMapping;
|
||||
|
||||
private final NavigableRole navigableRole;
|
||||
|
||||
public SoftDeleteMappingImpl(
|
||||
SoftDeletableModelPart softDeletable,
|
||||
String columnName,
|
||||
String tableName,
|
||||
Object deletedLiteralValue,
|
||||
String deletedLiteralText,
|
||||
Object nonDeletedLiteralValue,
|
||||
String nonDeletedLiteralText,
|
||||
JdbcMapping jdbcMapping) {
|
||||
this.softDeletable = softDeletable;
|
||||
this.columnName = columnName;
|
||||
this.tableName = tableName;
|
||||
this.deletedLiteralValue = deletedLiteralValue;
|
||||
this.deletedLiteralText = deletedLiteralText;
|
||||
this.nonDeletedLiteralValue = nonDeletedLiteralValue;
|
||||
this.nonDeletedLiteralText = nonDeletedLiteralText;
|
||||
this.jdbcMapping = jdbcMapping;
|
||||
|
||||
this.navigableRole = softDeletable.getNavigableRole().append( ROLE_NAME );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDeletedLiteralValue() {
|
||||
return deletedLiteralValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeletedLiteralText() {
|
||||
return deletedLiteralText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNonDeletedLiteralValue() {
|
||||
return nonDeletedLiteralValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNonDeletedLiteralText() {
|
||||
return nonDeletedLiteralText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcMapping getJdbcMapping() {
|
||||
return jdbcMapping;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPartName() {
|
||||
return ROLE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableRole getNavigableRole() {
|
||||
return navigableRole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
|
||||
action.accept( offset, jdbcMapping );
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object disassemble(Object value, SharedSessionContractImplementor session) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCacheKey(MutableCacheKeyBuilder cacheKey, Object value, SharedSessionContractImplementor session) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X, Y> int forEachDisassembledJdbcValue(
|
||||
Object value,
|
||||
int offset,
|
||||
X x,
|
||||
Y y,
|
||||
JdbcValuesBiConsumer<X, Y> valuesConsumer,
|
||||
SharedSessionContractImplementor session) {
|
||||
valuesConsumer.consume( offset, x, y, value, getJdbcMapping() );
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingType getPartMappingType() {
|
||||
return jdbcMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<?> getJavaType() {
|
||||
return jdbcMapping.getMappedJavaType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPartitionedSelectionMapping() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DomainResult<T> createDomainResult(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup tableGroup,
|
||||
String resultVariable,
|
||||
DomainResultCreationState creationState) {
|
||||
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||
return new BasicResult<>( sqlSelection.getValuesArrayPosition(), resultVariable, getJdbcMapping() );
|
||||
}
|
||||
|
||||
private SqlSelection resolveSqlSelection(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup tableGroup,
|
||||
DomainResultCreationState creationState) {
|
||||
final TableDetails indicatorTable = softDeletable.getSoftDeleteTableDetails();
|
||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||
navigablePath.getRealParent(),
|
||||
indicatorTable.getTableName()
|
||||
);
|
||||
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
|
||||
final SqlSelection sqlSelection = expressionResolver.resolveSqlSelection(
|
||||
expressionResolver.resolveSqlExpression( tableReference, this ),
|
||||
getJavaType(),
|
||||
null,
|
||||
creationState.getSqlAstCreationState().getCreationContext().getMappingMetamodel().getTypeConfiguration()
|
||||
);
|
||||
return sqlSelection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySqlSelections(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup tableGroup,
|
||||
DomainResultCreationState creationState) {
|
||||
resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySqlSelections(
|
||||
NavigablePath navigablePath,
|
||||
TableGroup tableGroup,
|
||||
DomainResultCreationState creationState,
|
||||
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
|
||||
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState );
|
||||
selectionConsumer.accept( sqlSelection, getJdbcMapping() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X, Y> int breakDownJdbcValues(
|
||||
Object domainValue,
|
||||
int offset,
|
||||
X x,
|
||||
Y y,
|
||||
JdbcValueBiConsumer<X, Y> valueConsumer,
|
||||
SharedSessionContractImplementor session) {
|
||||
valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this );
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityMappingType findContainingEntityMapping() {
|
||||
return softDeletable.findContainingEntityMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SoftDeleteMappingImpl(" + tableName + "." + columnName + ")";
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -34,6 +35,7 @@ import org.hibernate.mapping.Property;
|
|||
import org.hibernate.mapping.Selectable;
|
||||
import org.hibernate.mapping.ToOne;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
import org.hibernate.metamodel.mapping.AssociationKey;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
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.SelectableMapping;
|
||||
import org.hibernate.metamodel.mapping.SelectablePath;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
||||
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.Type;
|
||||
|
||||
import static org.hibernate.boot.model.internal.SoftDeleteHelper.createNonSoftDeletedRestriction;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -413,6 +418,18 @@ public class ToOneAttributeMapping
|
|||
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 ) {
|
||||
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
|
||||
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
|
||||
keyResult = foreignKeyDescriptor.createTargetDomainResult(
|
||||
fetchablePath,
|
||||
|
@ -1609,7 +1627,9 @@ public class ToOneAttributeMapping
|
|||
final boolean selectByUniqueKey = isSelectByUniqueKey( side );
|
||||
|
||||
// Consider all associations annotated with @NotFound as EAGER
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE || hasNotFoundAction() ) {
|
||||
if ( fetchTiming == FetchTiming.IMMEDIATE
|
||||
|| hasNotFoundAction()
|
||||
|| getAssociatedEntityMappingType().getSoftDeleteMapping() != null ) {
|
||||
return buildEntityFetchSelect(
|
||||
fetchParent,
|
||||
this,
|
||||
|
@ -1978,6 +1998,20 @@ public class ToOneAttributeMapping
|
|||
if ( getAssociatedEntityMappingType().getSuperMappingType() != null && !creationState.supportsEntityNameUsage() ) {
|
||||
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()
|
||||
);
|
||||
|
||||
final SoftDeleteMapping softDeleteMapping = getAssociatedEntityMappingType().getSoftDeleteMapping();
|
||||
|
||||
final boolean canUseInnerJoin;
|
||||
if ( ! lhs.canUseInnerJoins() ) {
|
||||
canUseInnerJoin = false;
|
||||
}
|
||||
else if ( isNullable || hasNotFoundAction() ) {
|
||||
else if ( isNullable
|
||||
|| hasNotFoundAction()
|
||||
|| softDeleteMapping != null ) {
|
||||
canUseInnerJoin = false;
|
||||
}
|
||||
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 ) {
|
||||
|
|
|
@ -201,6 +201,10 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl
|
|||
registerEntityNameResolvers( persister, entityNameResolvers );
|
||||
}
|
||||
|
||||
for ( EntityPersister persister : entityPersisterMap.values() ) {
|
||||
persister.prepareLoaders();
|
||||
}
|
||||
|
||||
collectionPersisterMap.values().forEach( CollectionPersister::postInstantiate );
|
||||
|
||||
registerEmbeddableMappingType( bootModel );
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
*/
|
||||
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.FetchMode;
|
||||
import org.hibernate.Filter;
|
||||
|
@ -120,18 +132,6 @@ import org.hibernate.type.CompositeType;
|
|||
import org.hibernate.type.EntityType;
|
||||
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.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
|
||||
|
||||
|
@ -1775,11 +1775,35 @@ public abstract class AbstractCollectionPersister
|
|||
ParameterUsage.RESTRICT,
|
||||
keyColumnCount
|
||||
);
|
||||
final java.util.List<ColumnValueBinding> keyRestrictionBindings = arrayList( keyColumnCount );
|
||||
fkDescriptor.getKeyPart().forEachSelectable( parameterBinders );
|
||||
for ( ColumnValueParameter columnValueParameter : parameterBinders ) {
|
||||
final java.util.List<ColumnValueBinding> restrictionBindings = arrayList( keyColumnCount );
|
||||
applyKeyRestrictions( tableReference, parameterBinders, restrictionBindings );
|
||||
|
||||
//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();
|
||||
keyRestrictionBindings.add(
|
||||
restrictionBindings.add(
|
||||
new ColumnValueBinding(
|
||||
columnReference,
|
||||
new ColumnWriteFragment(
|
||||
|
@ -1790,17 +1814,6 @@ public abstract class AbstractCollectionPersister
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
//noinspection unchecked,rawtypes
|
||||
return (RestrictedTableMutation) new TableDeleteStandard(
|
||||
tableReference,
|
||||
this,
|
||||
"one-shot delete for " + getRolePath(),
|
||||
keyRestrictionBindings,
|
||||
Collections.emptyList(),
|
||||
parameterBinders,
|
||||
sqlWhereString
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
*/
|
||||
package org.hibernate.persister.collection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Internal;
|
||||
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.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||
import org.hibernate.persister.collection.mutation.DeleteRowsCoordinator;
|
||||
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.spi.PersisterCreationContext;
|
||||
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.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.RestrictedTableMutation;
|
||||
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.TableInsertBuilderStandard;
|
||||
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
|
||||
import org.hibernate.sql.model.internal.TableUpdateStandard;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -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() {
|
||||
final OperationProducer insertRowOperationProducer;
|
||||
final RowMutationOperations.Values insertRowValues;
|
||||
|
@ -295,6 +350,11 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
|||
}
|
||||
|
||||
attributeMapping.getElementDescriptor().forEachInsertable( insertBuilder );
|
||||
|
||||
final SoftDeleteMapping softDeleteMapping = getAttributeMapping().getSoftDeleteMapping();
|
||||
if ( softDeleteMapping != null ) {
|
||||
insertBuilder.addValueColumn( softDeleteMapping );
|
||||
}
|
||||
}
|
||||
|
||||
private JdbcMutationOperation buildGeneratedInsertRowOperation(MutatingTableReference tableReference) {
|
||||
|
@ -597,6 +657,11 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
|||
final PluralAttributeMapping pluralAttribute = getAttributeMapping();
|
||||
assert pluralAttribute != null;
|
||||
|
||||
final SoftDeleteMapping softDeleteMapping = pluralAttribute.getSoftDeleteMapping();
|
||||
if ( softDeleteMapping != null ) {
|
||||
return generateSoftDeleteRowsAst( tableReference );
|
||||
}
|
||||
|
||||
final ForeignKeyDescriptor fkDescriptor = pluralAttribute.getKeyDescriptor();
|
||||
assert fkDescriptor != null;
|
||||
|
||||
|
@ -627,6 +692,50 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
|
|||
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(
|
||||
PersistentCollection<?> collection,
|
||||
Object keyValue,
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.hibernate.Remove;
|
|||
import org.hibernate.StaleObjectStateException;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.boot.spi.SessionFactoryOptions;
|
||||
|
@ -147,11 +148,13 @@ import org.hibernate.mapping.Formula;
|
|||
import org.hibernate.mapping.Join;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.RootClass;
|
||||
import org.hibernate.mapping.Selectable;
|
||||
import org.hibernate.mapping.Subclass;
|
||||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.hibernate.metadata.ClassMetadata;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
import org.hibernate.metamodel.mapping.Association;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
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.SelectableMapping;
|
||||
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.internal.BasicEntityIdentifierMappingImpl;
|
||||
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.persister.collection.CollectionPersister;
|
||||
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.EntityTableMapping;
|
||||
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.MutationOperationGroup;
|
||||
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.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
|
@ -438,6 +446,7 @@ public abstract class AbstractEntityPersister
|
|||
private EntityVersionMapping versionMapping;
|
||||
private EntityRowIdMapping rowIdMapping;
|
||||
private EntityDiscriminatorMapping discriminatorMapping;
|
||||
private SoftDeleteMapping softDeleteMapping;
|
||||
|
||||
private AttributeMappingsList attributeMappings;
|
||||
protected AttributeMappingsMap declaredAttributeMappings = AttributeMappingsMap.builder().build();
|
||||
|
@ -3092,10 +3101,22 @@ public abstract class AbstractEntityPersister
|
|||
getFactory()
|
||||
);
|
||||
|
||||
if ( additionalPredicateCollectorAccess != null && needsDiscriminator() ) {
|
||||
final String alias = tableGroup.getPrimaryTableReference().getIdentificationVariable();
|
||||
final Predicate discriminatorPredicate = createDiscriminatorPredicate( alias, tableGroup, creationState );
|
||||
additionalPredicateCollectorAccess.get().accept( discriminatorPredicate );
|
||||
if ( additionalPredicateCollectorAccess != null ) {
|
||||
if ( needsDiscriminator() ) {
|
||||
final String alias = tableGroup.getPrimaryTableReference().getIdentificationVariable();
|
||||
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;
|
||||
|
@ -3363,6 +3384,15 @@ public abstract class AbstractEntityPersister
|
|||
initPropertyPaths( mapping );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareLoaders() {
|
||||
// Hibernate Reactive needs to override the loaders
|
||||
singleIdLoader = buildSingleIdEntityLoader();
|
||||
multiIdLoader = buildMultiIdLoader();
|
||||
|
||||
lazyLoadPlanByFetchGroup = getLazyLoadPlanByFetchGroup();
|
||||
}
|
||||
|
||||
private void doLateInit() {
|
||||
if ( isIdentifierAssignedByInsert() ) {
|
||||
final OnExecutionGenerator generator = (OnExecutionGenerator) getGenerator();
|
||||
|
@ -3386,7 +3416,6 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
//select SQL
|
||||
lazyLoadPlanByFetchGroup = getLazyLoadPlanByFetchGroup();
|
||||
sqlVersionSelectString = generateSelectVersionString();
|
||||
|
||||
logStaticSQL();
|
||||
|
@ -3617,12 +3646,24 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
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 addSoftDeleteToInsertGroup(MutationGroupBuilder insertGroupBuilder) {
|
||||
if ( softDeleteMapping != null ) {
|
||||
final TableInsertBuilder insertBuilder = insertGroupBuilder.getTableDetailsBuilder( getIdentifierTableName() );
|
||||
insertBuilder.addValueColumn( softDeleteMapping );
|
||||
}
|
||||
}
|
||||
|
||||
protected String substituteBrackets(String sql) {
|
||||
return new SQLQueryParser( sql, null, getFactory() ).process();
|
||||
}
|
||||
|
@ -3630,9 +3671,6 @@ public abstract class AbstractEntityPersister
|
|||
@Override
|
||||
public final void postInstantiate() throws MappingException {
|
||||
doLateInit();
|
||||
// Hibernate Reactive needs to override the loaders
|
||||
singleIdLoader = buildSingleIdEntityLoader();
|
||||
multiIdLoader = buildMultiIdLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4883,6 +4921,7 @@ public abstract class AbstractEntityPersister
|
|||
naturalIdMapping = superMappingType.getNaturalIdMapping();
|
||||
versionMapping = superMappingType.getVersionMapping();
|
||||
rowIdMapping = superMappingType.getRowIdMapping();
|
||||
softDeleteMapping = superMappingType.getSoftDeleteMapping();
|
||||
}
|
||||
else {
|
||||
prepareMappingModel( creationProcess, bootEntityDescriptor );
|
||||
|
@ -5068,6 +5107,27 @@ public abstract class AbstractEntityPersister
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -5795,6 +5855,16 @@ public abstract class AbstractEntityPersister
|
|||
return discriminatorMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||
return softDeleteMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDetails getSoftDeleteTableDetails() {
|
||||
return getIdentifierTableDetails();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeMappingsList getAttributeMappings() {
|
||||
if ( attributeMappings == null ) {
|
||||
|
@ -6307,7 +6377,7 @@ public abstract class AbstractEntityPersister
|
|||
/**
|
||||
* 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)
|
||||
@Remove
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.hibernate.metadata.ClassMetadata;
|
|||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
|
||||
import org.hibernate.persister.walking.spi.AttributeSource;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
|
||||
|
@ -119,6 +120,17 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
|
|||
*/
|
||||
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
|
||||
* belongs.
|
||||
|
@ -1110,5 +1122,4 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
|
|||
*/
|
||||
@Incubating
|
||||
Iterable<UniqueKeyEntry> uniqueKeyEntries();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.persister.entity.mutation;
|
||||
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
|
||||
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
|
||||
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
|
||||
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
|
||||
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityVersionMapping;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.sql.model.MutationOperation;
|
||||
import org.hibernate.sql.model.MutationOperationGroup;
|
||||
|
||||
import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck;
|
||||
|
||||
/**
|
||||
* Template support for DeleteCoordinator implementations. Mainly
|
||||
* centers around delegation via {@linkplain #generateOperationGroup}.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractDeleteCoordinator
|
||||
extends AbstractMutationCoordinator
|
||||
implements DeleteCoordinator {
|
||||
private final BasicBatchKey batchKey;
|
||||
private final MutationOperationGroup staticOperationGroup;
|
||||
|
||||
private MutationOperationGroup noVersionDeleteGroup;
|
||||
|
||||
public AbstractDeleteCoordinator(
|
||||
AbstractEntityPersister entityPersister,
|
||||
SessionFactoryImplementor factory) {
|
||||
super( entityPersister, factory );
|
||||
|
||||
this.batchKey = new BasicBatchKey( entityPersister.getEntityName() + "#DELETE" );
|
||||
this.staticOperationGroup = generateOperationGroup( "", null, true, null );
|
||||
if ( !entityPersister.isVersioned() ) {
|
||||
noVersionDeleteGroup = staticOperationGroup;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutationOperationGroup getStaticDeleteGroup() {
|
||||
return staticOperationGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicBatchKey getBatchKey() {
|
||||
return batchKey;
|
||||
}
|
||||
|
||||
protected abstract MutationOperationGroup generateOperationGroup(
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
boolean applyVersion,
|
||||
SharedSessionContractImplementor session);
|
||||
|
||||
@Override
|
||||
public void coordinateDelete(
|
||||
Object entity,
|
||||
Object id,
|
||||
Object version,
|
||||
SharedSessionContractImplementor session) {
|
||||
boolean isImpliedOptimisticLocking = entityPersister().optimisticLockStyle().isAllOrDirty();
|
||||
|
||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
final EntityEntry entry = persistenceContext.getEntry( entity );
|
||||
final Object[] loadedState = entry != null && isImpliedOptimisticLocking ? entry.getLoadedState() : null;
|
||||
final Object rowId = entry != null ? entry.getRowId() : null;
|
||||
|
||||
if ( ( isImpliedOptimisticLocking && loadedState != null ) || ( rowId == null && entityPersister().hasRowId() ) ) {
|
||||
doDynamicDelete( entity, id, rowId, loadedState, session );
|
||||
}
|
||||
else {
|
||||
doStaticDelete( entity, id, rowId, entry == null ? null : entry.getLoadedState(), version, session );
|
||||
}
|
||||
}
|
||||
|
||||
protected void doDynamicDelete(
|
||||
Object entity,
|
||||
Object id,
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session) {
|
||||
final MutationOperationGroup operationGroup = generateOperationGroup( null, loadedState, true, session );
|
||||
final MutationExecutor mutationExecutor = executor( session, operationGroup );
|
||||
|
||||
for ( int i = 0; i < operationGroup.getNumberOfOperations(); i++ ) {
|
||||
final MutationOperation mutation = operationGroup.getOperation( i );
|
||||
if ( mutation != null ) {
|
||||
final String tableName = mutation.getTableDetails().getTableName();
|
||||
mutationExecutor.getPreparedStatementDetails( tableName );
|
||||
}
|
||||
}
|
||||
|
||||
applyDynamicDeleteTableDetails(
|
||||
id,
|
||||
rowId,
|
||||
loadedState,
|
||||
mutationExecutor,
|
||||
operationGroup,
|
||||
session
|
||||
);
|
||||
|
||||
try {
|
||||
mutationExecutor.execute(
|
||||
entity,
|
||||
null,
|
||||
null,
|
||||
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
||||
statementDetails,
|
||||
affectedRowCount,
|
||||
batchPosition,
|
||||
entityPersister(),
|
||||
id,
|
||||
factory()
|
||||
),
|
||||
session
|
||||
);
|
||||
}
|
||||
finally {
|
||||
mutationExecutor.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyDynamicDeleteTableDetails(
|
||||
Object id,
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
MutationExecutor mutationExecutor,
|
||||
MutationOperationGroup operationGroup,
|
||||
SharedSessionContractImplementor session) {
|
||||
applyLocking( null, loadedState, mutationExecutor, session );
|
||||
applyId( id, null, mutationExecutor, operationGroup, session );
|
||||
}
|
||||
|
||||
protected void applyLocking(
|
||||
Object version,
|
||||
Object[] loadedState,
|
||||
MutationExecutor mutationExecutor,
|
||||
SharedSessionContractImplementor session) {
|
||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||
switch ( optimisticLockStyle ) {
|
||||
case VERSION:
|
||||
applyVersionLocking( version, jdbcValueBindings );
|
||||
break;
|
||||
case ALL:
|
||||
case DIRTY:
|
||||
applyAllOrDirtyLocking( loadedState, session, jdbcValueBindings );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyAllOrDirtyLocking(
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session,
|
||||
JdbcValueBindings jdbcValueBindings) {
|
||||
if ( loadedState != null ) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
final boolean[] versionability = persister.getPropertyVersionability();
|
||||
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||
final AttributeMapping attribute;
|
||||
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||
final Object loadedValue = loadedState[attributeIndex];
|
||||
if ( loadedValue != null ) {
|
||||
final String mutationTableName = persister.getAttributeMutationTableName( attributeIndex );
|
||||
attribute.breakDownJdbcValues(
|
||||
loadedValue,
|
||||
0,
|
||||
jdbcValueBindings,
|
||||
mutationTableName,
|
||||
(valueIndex, bindings, tableName, jdbcValue, jdbcValueMapping) -> {
|
||||
if ( jdbcValue == null ) {
|
||||
// presumably the SQL was generated with `is null`
|
||||
return;
|
||||
}
|
||||
bindings.bindValue(
|
||||
jdbcValue,
|
||||
tableName,
|
||||
jdbcValueMapping.getSelectionExpression(),
|
||||
ParameterUsage.RESTRICT
|
||||
);
|
||||
},
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyVersionLocking(
|
||||
Object version,
|
||||
JdbcValueBindings jdbcValueBindings) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
final EntityVersionMapping versionMapping = persister.getVersionMapping();
|
||||
if ( version != null && versionMapping != null ) {
|
||||
jdbcValueBindings.bindValue(
|
||||
version,
|
||||
persister.physicalTableNameForMutation( versionMapping ),
|
||||
versionMapping.getSelectionExpression(),
|
||||
ParameterUsage.RESTRICT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyId(
|
||||
Object id,
|
||||
Object rowId,
|
||||
MutationExecutor mutationExecutor,
|
||||
MutationOperationGroup operationGroup,
|
||||
SharedSessionContractImplementor session) {
|
||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||
final EntityRowIdMapping rowIdMapping = entityPersister().getRowIdMapping();
|
||||
|
||||
for ( int position = 0; position < operationGroup.getNumberOfOperations(); position++ ) {
|
||||
final MutationOperation jdbcMutation = operationGroup.getOperation( position );
|
||||
final EntityTableMapping tableDetails = (EntityTableMapping) jdbcMutation.getTableDetails();
|
||||
breakDownKeyJdbcValues( id, rowId, session, jdbcValueBindings, tableDetails );
|
||||
final PreparedStatementDetails statementDetails = mutationExecutor.getPreparedStatementDetails( tableDetails.getTableName() );
|
||||
if ( statementDetails != null ) {
|
||||
// force creation of the PreparedStatement
|
||||
//noinspection resource
|
||||
statementDetails.resolveStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void doStaticDelete(
|
||||
Object entity,
|
||||
Object id,
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
Object version,
|
||||
SharedSessionContractImplementor session) {
|
||||
final boolean applyVersion;
|
||||
final MutationOperationGroup operationGroupToUse;
|
||||
if ( entity == null ) {
|
||||
applyVersion = false;
|
||||
operationGroupToUse = resolveNoVersionDeleteGroup( session );
|
||||
}
|
||||
else {
|
||||
applyVersion = true;
|
||||
operationGroupToUse = staticOperationGroup;
|
||||
}
|
||||
|
||||
final MutationExecutor mutationExecutor = executor( session, operationGroupToUse );
|
||||
for ( int position = 0; position < staticOperationGroup.getNumberOfOperations(); position++ ) {
|
||||
final MutationOperation mutation = staticOperationGroup.getOperation( position );
|
||||
if ( mutation != null ) {
|
||||
mutationExecutor.getPreparedStatementDetails( mutation.getTableDetails().getTableName() );
|
||||
}
|
||||
}
|
||||
|
||||
applyStaticDeleteTableDetails(
|
||||
id,
|
||||
rowId,
|
||||
loadedState,
|
||||
version,
|
||||
applyVersion,
|
||||
mutationExecutor,
|
||||
session
|
||||
);
|
||||
|
||||
mutationExecutor.execute(
|
||||
entity,
|
||||
null,
|
||||
null,
|
||||
(statementDetails, affectedRowCount, batchPosition) -> identifiedResultsCheck(
|
||||
statementDetails,
|
||||
affectedRowCount,
|
||||
batchPosition,
|
||||
entityPersister(),
|
||||
id,
|
||||
factory()
|
||||
),
|
||||
session
|
||||
);
|
||||
|
||||
mutationExecutor.release();
|
||||
}
|
||||
|
||||
protected void applyStaticDeleteTableDetails(
|
||||
Object id,
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
Object version,
|
||||
boolean applyVersion,
|
||||
MutationExecutor mutationExecutor,
|
||||
SharedSessionContractImplementor session) {
|
||||
if ( applyVersion ) {
|
||||
applyLocking( version, null, mutationExecutor, session );
|
||||
}
|
||||
|
||||
final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings();
|
||||
bindPartitionColumnValueBindings( loadedState, session, jdbcValueBindings );
|
||||
|
||||
applyId( id, rowId, mutationExecutor, staticOperationGroup, session );
|
||||
}
|
||||
|
||||
private MutationExecutor executor(SharedSessionContractImplementor session, MutationOperationGroup group) {
|
||||
return mutationExecutorService.createExecutor( resolveBatchKeyAccess( false, session ), group, session );
|
||||
}
|
||||
|
||||
protected MutationOperationGroup resolveNoVersionDeleteGroup(SharedSessionContractImplementor session) {
|
||||
if ( noVersionDeleteGroup == null ) {
|
||||
noVersionDeleteGroup = generateOperationGroup( "", null, false, session );
|
||||
}
|
||||
|
||||
return noVersionDeleteGroup;
|
||||
}
|
||||
}
|
|
@ -6,32 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.persister.entity.mutation;
|
||||
|
||||
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.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.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.
|
||||
|
@ -40,389 +16,19 @@ import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.id
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class DeleteCoordinator extends AbstractMutationCoordinator {
|
||||
private final MutationOperationGroup staticOperationGroup;
|
||||
private final BasicBatchKey batchKey;
|
||||
public interface DeleteCoordinator {
|
||||
/**
|
||||
* The operation group used to perform the deletion unless some form
|
||||
* of dynamic delete is necessary
|
||||
*/
|
||||
MutationOperationGroup getStaticDeleteGroup();
|
||||
|
||||
private MutationOperationGroup noVersionDeleteGroup;
|
||||
|
||||
public DeleteCoordinator(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
||||
super( entityPersister, factory );
|
||||
|
||||
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(
|
||||
/**
|
||||
* Perform the deletions
|
||||
*/
|
||||
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 ? 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
|
||||
}
|
||||
|
||||
SharedSessionContractImplementor session);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.persister.entity.mutation;
|
||||
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.sql.model.MutationOperation;
|
||||
import org.hibernate.sql.model.MutationOperationGroup;
|
||||
import org.hibernate.sql.model.MutationType;
|
||||
import org.hibernate.sql.model.ast.ColumnValueBindingList;
|
||||
import org.hibernate.sql.model.ast.RestrictedTableMutation;
|
||||
import org.hibernate.sql.model.ast.builder.TableUpdateBuilder;
|
||||
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
|
||||
import org.hibernate.sql.model.internal.MutationGroupSingle;
|
||||
import org.hibernate.sql.model.internal.MutationOperationGroupFactory;
|
||||
|
||||
/**
|
||||
* DeleteCoordinator for soft-deletes
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class DeleteCoordinatorSoft extends AbstractDeleteCoordinator {
|
||||
public DeleteCoordinatorSoft(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
||||
super( entityPersister, factory );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MutationOperationGroup generateOperationGroup(
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
boolean applyVersion,
|
||||
SharedSessionContractImplementor session) {
|
||||
final EntityTableMapping rootTableMapping = entityPersister().getIdentifierTableMapping();
|
||||
final TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder = new TableUpdateBuilderStandard<>(
|
||||
entityPersister(),
|
||||
rootTableMapping,
|
||||
factory()
|
||||
);
|
||||
|
||||
applyKeyRestriction( rowId, entityPersister(), tableUpdateBuilder, rootTableMapping );
|
||||
applySoftDelete( entityPersister().getSoftDeleteMapping(), tableUpdateBuilder );
|
||||
applyPartitionKeyRestriction( tableUpdateBuilder );
|
||||
applyOptimisticLocking( tableUpdateBuilder, loadedState, session );
|
||||
|
||||
final RestrictedTableMutation<MutationOperation> tableMutation = tableUpdateBuilder.buildMutation();
|
||||
final MutationGroupSingle mutationGroup = new MutationGroupSingle(
|
||||
MutationType.DELETE,
|
||||
entityPersister(),
|
||||
tableMutation
|
||||
);
|
||||
|
||||
final MutationOperation mutationOperation = tableMutation.createMutationOperation( null, factory() );
|
||||
return MutationOperationGroupFactory.singleOperation( mutationGroup, mutationOperation );
|
||||
}
|
||||
|
||||
private void applyPartitionKeyRestriction(TableUpdateBuilder<?> tableUpdateBuilder) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
if ( persister.hasPartitionedSelectionMapping() ) {
|
||||
final AttributeMappingsList attributeMappings = persister.getAttributeMappings();
|
||||
for ( int m = 0; m < attributeMappings.size(); m++ ) {
|
||||
final AttributeMapping attributeMapping = attributeMappings.get( m );
|
||||
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
||||
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
||||
final SelectableMapping selectableMapping = attributeMapping.getSelectable( i );
|
||||
if ( selectableMapping.isPartitioned() ) {
|
||||
tableUpdateBuilder.addKeyRestrictionLeniently( selectableMapping );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySoftDelete(
|
||||
SoftDeleteMapping softDeleteMapping,
|
||||
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder) {
|
||||
tableUpdateBuilder.addLiteralRestriction(
|
||||
softDeleteMapping.getSelectionExpression(),
|
||||
softDeleteMapping.getNonDeletedLiteralText(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
tableUpdateBuilder.addValueColumn(
|
||||
softDeleteMapping.getSelectionExpression(),
|
||||
softDeleteMapping.getDeletedLiteralText(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
}
|
||||
|
||||
protected void applyOptimisticLocking(
|
||||
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session) {
|
||||
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||
if ( optimisticLockStyle.isVersion() && entityPersister().getVersionMapping() != null ) {
|
||||
applyVersionBasedOptLocking( tableUpdateBuilder );
|
||||
}
|
||||
else if ( loadedState != null && entityPersister().optimisticLockStyle().isAllOrDirty() ) {
|
||||
applyNonVersionOptLocking(
|
||||
optimisticLockStyle,
|
||||
tableUpdateBuilder,
|
||||
loadedState,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyVersionBasedOptLocking(TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder) {
|
||||
assert entityPersister().optimisticLockStyle() == OptimisticLockStyle.VERSION;
|
||||
assert entityPersister().getVersionMapping() != null;
|
||||
|
||||
tableUpdateBuilder.addOptimisticLockRestriction( entityPersister().getVersionMapping() );
|
||||
}
|
||||
|
||||
protected void applyNonVersionOptLocking(
|
||||
OptimisticLockStyle lockStyle,
|
||||
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
assert loadedState != null;
|
||||
assert lockStyle.isAllOrDirty();
|
||||
assert persister.optimisticLockStyle().isAllOrDirty();
|
||||
assert session != null;
|
||||
|
||||
final boolean[] versionability = persister.getPropertyVersionability();
|
||||
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||
final AttributeMapping attribute;
|
||||
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||
if ( versionability[attributeIndex]
|
||||
&& !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||
breakDownJdbcValues( tableUpdateBuilder, session, attribute, loadedState[attributeIndex] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void breakDownJdbcValues(
|
||||
TableUpdateBuilderStandard<MutationOperation> tableUpdateBuilder,
|
||||
SharedSessionContractImplementor session,
|
||||
AttributeMapping attribute,
|
||||
Object loadedValue) {
|
||||
if ( !tableUpdateBuilder.getMutatingTable()
|
||||
.getTableName()
|
||||
.equals( attribute.getContainingTableExpression() ) ) {
|
||||
// it is not on the root table, skip it
|
||||
return;
|
||||
}
|
||||
|
||||
final ColumnValueBindingList optimisticLockBindings = tableUpdateBuilder.getOptimisticLockBindings();
|
||||
if ( optimisticLockBindings != null ) {
|
||||
attribute.breakDownJdbcValues(
|
||||
loadedValue,
|
||||
(valueIndex, value, jdbcValueMapping) -> {
|
||||
if ( !tableUpdateBuilder.getKeyRestrictionBindings()
|
||||
.containsColumn(
|
||||
jdbcValueMapping.getSelectableName(),
|
||||
jdbcValueMapping.getJdbcMapping()
|
||||
) ) {
|
||||
optimisticLockBindings.consume( valueIndex, value, jdbcValueMapping );
|
||||
}
|
||||
}
|
||||
,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.persister.entity.mutation;
|
||||
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.AttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.AttributeMappingsList;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.sql.model.MutationOperationGroup;
|
||||
import org.hibernate.sql.model.MutationType;
|
||||
import org.hibernate.sql.model.ast.ColumnValueBindingList;
|
||||
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
|
||||
import org.hibernate.sql.model.ast.builder.RestrictedTableMutationBuilder;
|
||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilder;
|
||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderSkipped;
|
||||
import org.hibernate.sql.model.ast.builder.TableDeleteBuilderStandard;
|
||||
|
||||
/**
|
||||
* Coordinates standard deleting of an entity.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class DeleteCoordinatorStandard extends AbstractDeleteCoordinator {
|
||||
|
||||
public DeleteCoordinatorStandard(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
|
||||
super( entityPersister, factory );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MutationOperationGroup generateOperationGroup(
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
boolean applyVersion,
|
||||
SharedSessionContractImplementor session) {
|
||||
final MutationGroupBuilder deleteGroupBuilder = new MutationGroupBuilder( MutationType.DELETE, entityPersister() );
|
||||
|
||||
entityPersister().forEachMutableTableReverse( (tableMapping) -> {
|
||||
final TableDeleteBuilder tableDeleteBuilder = tableMapping.isCascadeDeleteEnabled()
|
||||
? new TableDeleteBuilderSkipped( tableMapping )
|
||||
: new TableDeleteBuilderStandard( entityPersister(), tableMapping, factory() );
|
||||
deleteGroupBuilder.addTableDetailsBuilder( tableDeleteBuilder );
|
||||
} );
|
||||
|
||||
applyTableDeleteDetails( deleteGroupBuilder, rowId, loadedState, applyVersion, session );
|
||||
|
||||
return createOperationGroup( null, deleteGroupBuilder.buildMutationGroup() );
|
||||
}
|
||||
|
||||
private void applyTableDeleteDetails(
|
||||
MutationGroupBuilder deleteGroupBuilder,
|
||||
Object rowId,
|
||||
Object[] loadedState,
|
||||
boolean applyVersion,
|
||||
SharedSessionContractImplementor session) {
|
||||
// first, the table key column(s)
|
||||
deleteGroupBuilder.forEachTableMutationBuilder( (builder) -> {
|
||||
final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping();
|
||||
final TableDeleteBuilder tableDeleteBuilder = (TableDeleteBuilder) builder;
|
||||
applyKeyRestriction( rowId, entityPersister(), tableDeleteBuilder, tableMapping );
|
||||
} );
|
||||
|
||||
if ( applyVersion ) {
|
||||
// apply any optimistic locking
|
||||
applyOptimisticLocking( deleteGroupBuilder, loadedState, session );
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
if ( persister.hasPartitionedSelectionMapping() ) {
|
||||
final AttributeMappingsList attributeMappings = persister.getAttributeMappings();
|
||||
for ( int m = 0; m < attributeMappings.size(); m++ ) {
|
||||
final AttributeMapping attributeMapping = attributeMappings.get( m );
|
||||
final int jdbcTypeCount = attributeMapping.getJdbcTypeCount();
|
||||
for ( int i = 0; i < jdbcTypeCount; i++ ) {
|
||||
final SelectableMapping selectableMapping = attributeMapping.getSelectable( i );
|
||||
if ( selectableMapping.isPartitioned() ) {
|
||||
final String tableNameForMutation =
|
||||
persister.physicalTableNameForMutation( selectableMapping );
|
||||
final RestrictedTableMutationBuilder<?, ?> rootTableMutationBuilder =
|
||||
deleteGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
||||
rootTableMutationBuilder.addKeyRestrictionLeniently( selectableMapping );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyOptimisticLocking(
|
||||
MutationGroupBuilder mutationGroupBuilder,
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session) {
|
||||
final OptimisticLockStyle optimisticLockStyle = entityPersister().optimisticLockStyle();
|
||||
if ( optimisticLockStyle.isVersion() && entityPersister().getVersionMapping() != null ) {
|
||||
applyVersionBasedOptLocking( mutationGroupBuilder );
|
||||
}
|
||||
else if ( loadedState != null && entityPersister().optimisticLockStyle().isAllOrDirty() ) {
|
||||
applyNonVersionOptLocking(
|
||||
optimisticLockStyle,
|
||||
mutationGroupBuilder,
|
||||
loadedState,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyVersionBasedOptLocking(MutationGroupBuilder mutationGroupBuilder) {
|
||||
assert entityPersister().optimisticLockStyle() == OptimisticLockStyle.VERSION;
|
||||
assert entityPersister().getVersionMapping() != null;
|
||||
|
||||
final String tableNameForMutation = entityPersister().physicalTableNameForMutation( entityPersister().getVersionMapping() );
|
||||
final RestrictedTableMutationBuilder<?,?> rootTableMutationBuilder = mutationGroupBuilder.findTableDetailsBuilder( tableNameForMutation );
|
||||
rootTableMutationBuilder.addOptimisticLockRestriction( entityPersister().getVersionMapping() );
|
||||
}
|
||||
|
||||
protected void applyNonVersionOptLocking(
|
||||
OptimisticLockStyle lockStyle,
|
||||
MutationGroupBuilder mutationGroupBuilder,
|
||||
Object[] loadedState,
|
||||
SharedSessionContractImplementor session) {
|
||||
final AbstractEntityPersister persister = entityPersister();
|
||||
assert loadedState != null;
|
||||
assert lockStyle.isAllOrDirty();
|
||||
assert persister.optimisticLockStyle().isAllOrDirty();
|
||||
assert session != null;
|
||||
|
||||
final boolean[] versionability = persister.getPropertyVersionability();
|
||||
for ( int attributeIndex = 0; attributeIndex < versionability.length; attributeIndex++ ) {
|
||||
final AttributeMapping attribute;
|
||||
// only makes sense to lock on singular attributes which are not excluded from optimistic locking
|
||||
if ( versionability[attributeIndex] && !( attribute = persister.getAttributeMapping( attributeIndex ) ).isPluralAttributeMapping() ) {
|
||||
breakDownJdbcValues( mutationGroupBuilder, session, attribute, loadedState[attributeIndex] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void breakDownJdbcValues(
|
||||
MutationGroupBuilder mutationGroupBuilder,
|
||||
SharedSessionContractImplementor session,
|
||||
AttributeMapping attribute,
|
||||
Object loadedValue) {
|
||||
final RestrictedTableMutationBuilder<?, ?> tableMutationBuilder =
|
||||
mutationGroupBuilder.findTableDetailsBuilder( attribute.getContainingTableExpression() );
|
||||
if ( tableMutationBuilder != null ) {
|
||||
final ColumnValueBindingList optimisticLockBindings = tableMutationBuilder.getOptimisticLockBindings();
|
||||
if ( optimisticLockBindings != null ) {
|
||||
attribute.breakDownJdbcValues(
|
||||
loadedValue,
|
||||
(valueIndex, value, jdbcValueMapping) -> {
|
||||
if ( !tableMutationBuilder.getKeyRestrictionBindings()
|
||||
.containsColumn(
|
||||
jdbcValueMapping.getSelectableName(),
|
||||
jdbcValueMapping.getJdbcMapping()
|
||||
) ) {
|
||||
optimisticLockBindings.consume( valueIndex, value, jdbcValueMapping );
|
||||
}
|
||||
}
|
||||
,
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -285,7 +285,7 @@ public class EntityTableMapping implements TableMapping {
|
|||
}
|
||||
}
|
||||
|
||||
public static class KeyColumn implements SelectableMapping, TableDetails.KeyColumn {
|
||||
public static class KeyColumn implements TableDetails.KeyColumn {
|
||||
private final String tableName;
|
||||
private final String columnName;
|
||||
private final String writeExpression;
|
||||
|
|
|
@ -390,6 +390,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
|
|||
|
||||
// add the discriminator
|
||||
entityPersister().addDiscriminatorToInsertGroup( insertGroupBuilder );
|
||||
entityPersister().addSoftDeleteToInsertGroup( insertGroupBuilder );
|
||||
|
||||
// add the keys
|
||||
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.hibernate.metamodel.mapping.NaturalIdMapping;
|
|||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.mapping.TableDetails;
|
||||
import org.hibernate.metamodel.mapping.ValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
|
||||
|
@ -681,6 +682,16 @@ public class AnonymousTupleEntityValuedModelPart
|
|||
return delegate.getEntityMappingType().getRowIdMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoftDeleteMapping getSoftDeleteMapping() {
|
||||
return delegate.getEntityMappingType().getSoftDeleteMapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDetails getSoftDeleteTableDetails() {
|
||||
return delegate.getEntityMappingType().getSoftDeleteTableDetails();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer) {
|
||||
delegate.getEntityMappingType().visitConstraintOrderedTables( consumer );
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.action.internal.BulkOperationCleanupAction;
|
||||
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.MutableObject;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
||||
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslator;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractDeleteQueryPlan<S extends AbstractUpdateOrDeleteStatement, O extends JdbcOperationQueryMutation>
|
||||
implements NonSelectQueryPlan {
|
||||
private final EntityMappingType entityDescriptor;
|
||||
private final SqmDeleteStatement<?> sqmDelete;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
||||
private O jdbcOperation;
|
||||
|
||||
private SqmTranslation<DeleteStatement> sqmInterpretation;
|
||||
private Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref;
|
||||
|
||||
public AbstractDeleteQueryPlan(
|
||||
EntityMappingType entityDescriptor,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref) {
|
||||
assert entityDescriptor.getEntityName().equals( sqmDelete.getTarget().getEntityName() );
|
||||
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
this.sqmDelete = sqmDelete;
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
return entityDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(DomainQueryExecutionContext executionContext) {
|
||||
BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete );
|
||||
|
||||
final SharedSessionContractImplementor session = executionContext.getSession();
|
||||
final SessionFactoryImplementor factory = session.getFactory();
|
||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||
SqlAstTranslator<O> sqlAstTranslator = null;
|
||||
if ( jdbcOperation == null ) {
|
||||
sqlAstTranslator = createTranslator( executionContext );
|
||||
}
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
domainParameterXref,
|
||||
jdbcParamsXref,
|
||||
factory.getRuntimeMetamodels().getMappingMetamodel(),
|
||||
sqmInterpretation.getFromClauseAccess()::findTableGroup,
|
||||
new SqmParameterMappingModelResolutionAccess() {
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
|
||||
return (MappingModelExpressible<T>) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter);
|
||||
}
|
||||
},
|
||||
session
|
||||
);
|
||||
|
||||
if ( jdbcOperation != null
|
||||
&& ! jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) {
|
||||
sqlAstTranslator = createTranslator( executionContext );
|
||||
}
|
||||
|
||||
if ( sqlAstTranslator != null ) {
|
||||
jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||
}
|
||||
|
||||
final boolean missingRestriction = sqmInterpretation.getSqlAst().getRestriction() == null;
|
||||
if ( missingRestriction ) {
|
||||
assert domainParameterXref.getSqmParameterCount() == 0;
|
||||
assert jdbcParamsXref.isEmpty();
|
||||
}
|
||||
|
||||
final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext );
|
||||
|
||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||
entityDescriptor,
|
||||
(tableReference, attributeMapping) -> {
|
||||
final TableGroup collectionTableGroup = new MutatingTableReferenceGroupWrapper(
|
||||
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||
attributeMapping,
|
||||
(NamedTableReference) tableReference
|
||||
);
|
||||
|
||||
final MutableObject<Predicate> additionalPredicate = new MutableObject<>();
|
||||
attributeMapping.applyBaseRestrictions(
|
||||
p -> additionalPredicate.set( Predicate.combinePredicates( additionalPredicate.get(), p ) ),
|
||||
collectionTableGroup,
|
||||
factory.getJdbcServices().getDialect().getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS,
|
||||
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
if ( missingRestriction ) {
|
||||
return additionalPredicate.get();
|
||||
}
|
||||
|
||||
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||
final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||
collectionTableGroup,
|
||||
fkDescriptor.getKeyPart(),
|
||||
null,
|
||||
factory
|
||||
);
|
||||
|
||||
final QuerySpec matchingIdSubQuery = new QuerySpec( false );
|
||||
|
||||
final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper(
|
||||
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||
attributeMapping,
|
||||
sqmInterpretation.getSqlAst().getTargetTable()
|
||||
);
|
||||
final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||
tableGroup,
|
||||
fkDescriptor.getTargetPart(),
|
||||
sqmInterpretation.getSqlExpressionResolver(),
|
||||
factory
|
||||
);
|
||||
matchingIdSubQuery.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) );
|
||||
|
||||
matchingIdSubQuery.getFromClause().addRoot(
|
||||
tableGroup
|
||||
);
|
||||
|
||||
matchingIdSubQuery.applyPredicate( SqmMutationStrategyHelper.getIdSubqueryPredicate(
|
||||
sqmInterpretation.getSqlAst().getRestriction(),
|
||||
entityDescriptor,
|
||||
tableGroup,
|
||||
session
|
||||
) );
|
||||
|
||||
return Predicate.combinePredicates(
|
||||
additionalPredicate.get(),
|
||||
new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false )
|
||||
);
|
||||
},
|
||||
( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ),
|
||||
executionContextAdapter
|
||||
);
|
||||
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcOperation,
|
||||
jdbcParameterBindings,
|
||||
sql -> session
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
executionContextAdapter
|
||||
);
|
||||
}
|
||||
|
||||
protected SqlAstTranslator<O> createTranslator(DomainQueryExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
final SqmTranslator<DeleteStatement> translator = factory.getQueryEngine().getSqmTranslatorFactory().createSimpleDeleteTranslator(
|
||||
sqmDelete,
|
||||
executionContext.getQueryOptions(),
|
||||
domainParameterXref,
|
||||
executionContext.getQueryParameterBindings(),
|
||||
executionContext.getSession().getLoadQueryInfluencers(),
|
||||
factory
|
||||
);
|
||||
sqmInterpretation = translator.translate();
|
||||
|
||||
this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
|
||||
domainParameterXref,
|
||||
sqmInterpretation::getJdbcParamsBySqmParam
|
||||
);
|
||||
|
||||
final S ast = buildAst( sqmInterpretation, executionContext );
|
||||
return createTranslator( ast, executionContext );
|
||||
}
|
||||
|
||||
protected abstract S buildAst(
|
||||
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||
DomainQueryExecutionContext executionContext);
|
||||
|
||||
protected abstract SqlAstTranslator<O> createTranslator(
|
||||
S ast,
|
||||
DomainQueryExecutionContext executionContext);
|
||||
}
|
|
@ -11,7 +11,6 @@ import org.hibernate.query.spi.DomainQueryExecutionContext;
|
|||
import org.hibernate.query.spi.NonSelectQueryPlan;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
|
|
@ -759,12 +759,17 @@ public class QuerySqmImpl<R>
|
|||
private NonSelectQueryPlan buildConcreteDeleteQueryPlan(@SuppressWarnings("rawtypes") SqmDeleteStatement sqmDelete) {
|
||||
final EntityDomainType<?> entityDomainType = sqmDelete.getTarget().getModel();
|
||||
final String entityNameToDelete = entityDomainType.getHibernateEntityName();
|
||||
final EntityPersister persister =
|
||||
getSessionFactory().getMappingMetamodel().getEntityDescriptor( entityNameToDelete );
|
||||
final EntityPersister persister = getSessionFactory().getMappingMetamodel().getEntityDescriptor( entityNameToDelete );
|
||||
final SqmMultiTableMutationStrategy multiTableStrategy = persister.getSqmMultiTableMutationStrategy();
|
||||
return multiTableStrategy == null
|
||||
? new SimpleDeleteQueryPlan( persister, sqmDelete, domainParameterXref )
|
||||
: new MultiTableDeleteQueryPlan( sqmDelete, domainParameterXref, multiTableStrategy );
|
||||
if ( multiTableStrategy != null ) {
|
||||
// NOTE : MultiTableDeleteQueryPlan and SqmMultiTableMutationStrategy already handle soft-deletes internally
|
||||
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) {
|
||||
|
|
|
@ -6,207 +6,42 @@
|
|||
*/
|
||||
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.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.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.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SimpleDeleteQueryPlan implements NonSelectQueryPlan {
|
||||
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 class SimpleDeleteQueryPlan extends AbstractDeleteQueryPlan<DeleteStatement, JdbcOperationQueryDelete> {
|
||||
public SimpleDeleteQueryPlan(
|
||||
EntityMappingType entityDescriptor,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref) {
|
||||
assert entityDescriptor.getEntityName().equals( sqmDelete.getTarget().getEntityName() );
|
||||
|
||||
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() );
|
||||
super( entityDescriptor, sqmDelete, domainParameterXref );
|
||||
}
|
||||
|
||||
@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<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
|
||||
);
|
||||
protected DeleteStatement buildAst(
|
||||
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||
DomainQueryExecutionContext executionContext) {
|
||||
return sqmInterpretation.getSqlAst();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SqlAstTranslator<JdbcOperationQueryDelete> createTranslator(
|
||||
DeleteStatement ast,
|
||||
DomainQueryExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
return factory.getJdbcServices()
|
||||
.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
.buildDeleteTranslator( factory, ast );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
|
||||
|
||||
/**
|
||||
* NonSelectQueryPlan for handling DELETE queries against an entity with soft-delete
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SoftDeleteQueryPlan extends AbstractDeleteQueryPlan<UpdateStatement,JdbcOperationQueryUpdate> {
|
||||
public SoftDeleteQueryPlan(
|
||||
EntityMappingType entityDescriptor,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref) {
|
||||
super( entityDescriptor, sqmDelete, domainParameterXref );
|
||||
assert entityDescriptor.getSoftDeleteMapping() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UpdateStatement buildAst(
|
||||
SqmTranslation<DeleteStatement> sqmInterpretation,
|
||||
DomainQueryExecutionContext executionContext) {
|
||||
final DeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst();
|
||||
final NamedTableReference targetTable = sqlDeleteAst.getTargetTable();
|
||||
final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping();
|
||||
final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping );
|
||||
//noinspection rawtypes,unchecked
|
||||
final JdbcLiteral jdbcLiteral = new JdbcLiteral( columnMapping.getDeletedLiteralValue(), columnMapping.getJdbcMapping() );
|
||||
final Assignment assignment = new Assignment( columnReference, jdbcLiteral );
|
||||
|
||||
return new UpdateStatement(
|
||||
targetTable,
|
||||
Collections.singletonList( assignment ),
|
||||
sqlDeleteAst.getRestriction()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SqlAstTranslator<JdbcOperationQueryUpdate> createTranslator(
|
||||
UpdateStatement sqlUpdateAst,
|
||||
DomainQueryExecutionContext executionContext) {
|
||||
final SharedSessionContractImplementor session = executionContext.getSession();
|
||||
final SessionFactoryImplementor factory = session.getFactory();
|
||||
return factory.getJdbcServices()
|
||||
.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
.buildUpdateTranslator( factory, sqlUpdateAst );
|
||||
}
|
||||
}
|
|
@ -185,7 +185,7 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
|
|||
this.parameterResolutionConsumer = parameterResolutionConsumer;
|
||||
|
||||
if ( sqmWhereClause == null || sqmWhereClause.getPredicate() == null ) {
|
||||
return null;
|
||||
return discriminatorPredicate;
|
||||
}
|
||||
|
||||
final SqlAstProcessingState rootProcessingState = getCurrentProcessingState();
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal;
|
||||
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.internal.log.SubSystemLogging;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@SubSystemLogging(
|
||||
name = "MutationQueryLogging.MUTATION_QUERY_LOGGER_NAME",
|
||||
description = "Logging for multi-table mutation queries"
|
||||
)
|
||||
@Internal
|
||||
public interface MutationQueryLogging {
|
||||
String MUTATION_QUERY_LOGGER_NAME = SubSystemLogging.BASE + ".query.mutation";
|
||||
|
||||
Logger MUTATION_QUERY_LOGGER = Logger.getLogger( MUTATION_QUERY_LOGGER_NAME );
|
||||
}
|
|
@ -16,6 +16,7 @@ import org.hibernate.dialect.Dialect;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
|
||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||
|
@ -42,7 +43,6 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
|
|||
* @author Christian Beikov
|
||||
*/
|
||||
public class CteDeleteHandler extends AbstractCteMutationHandler implements DeleteHandler {
|
||||
|
||||
private static final String DELETE_RESULT_TABLE_NAME_PREFIX = "delete_cte_";
|
||||
|
||||
protected CteDeleteHandler(
|
||||
|
@ -61,7 +61,7 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
MultiTableSqmMutationConverter sqmConverter,
|
||||
Map<SqmParameter<?>, List<JdbcParameter>> parameterResolutions,
|
||||
SessionFactoryImplementor factory) {
|
||||
final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||
final SelectStatement idSelectStatement = (SelectStatement) idSelectCte.getCteDefinition();
|
||||
sqmConverter.getProcessingStateStack().push(
|
||||
new SqlAstQueryPartProcessingStateImpl(
|
||||
|
@ -73,14 +73,13 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
)
|
||||
);
|
||||
SqmMutationStrategyHelper.visitCollectionTables(
|
||||
(EntityMappingType) updatingTableGroup.getModelPart(),
|
||||
(EntityMappingType) mutatingTableGroup.getModelPart(),
|
||||
pluralAttribute -> {
|
||||
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
|
||||
// Ensure that the FK target columns are available
|
||||
final boolean useFkTarget = !pluralAttribute.getKeyDescriptor()
|
||||
.getTargetPart().isEntityIdentifierMapping();
|
||||
if ( useFkTarget ) {
|
||||
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
|
||||
pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections(
|
||||
mutatingTableGroup.getNavigablePath(),
|
||||
mutatingTableGroup,
|
||||
|
@ -141,6 +140,14 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
|
|||
|
||||
sqmConverter.getProcessingStateStack().pop();
|
||||
|
||||
applyDmlOperations( statement, idSelectCte, factory, mutatingTableGroup );
|
||||
}
|
||||
|
||||
protected void applyDmlOperations(
|
||||
CteContainer statement,
|
||||
CteStatement idSelectCte,
|
||||
SessionFactoryImplementor factory,
|
||||
TableGroup updatingTableGroup) {
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableColumnsVisitationSupplier) -> {
|
||||
final String cteTableName = getCteTableName( tableExpression );
|
||||
|
|
|
@ -94,7 +94,27 @@ public class CteMutationStrategy implements SqmMultiTableMutationStrategy {
|
|||
DomainParameterXref domainParameterXref,
|
||||
DomainQueryExecutionContext context) {
|
||||
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
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal.cte;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.metamodel.mapping.TableDetails;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||
import org.hibernate.sql.ast.tree.cte.CteContainer;
|
||||
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||
import org.hibernate.sql.ast.tree.cte.CteTable;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
|
||||
/**
|
||||
* Specialized CteDeleteHandler for soft-delete handling
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CteSoftDeleteHandler extends CteDeleteHandler {
|
||||
protected CteSoftDeleteHandler(
|
||||
CteTable cteTable,
|
||||
SqmDeleteStatement<?> sqmDeleteStatement,
|
||||
DomainParameterXref domainParameterXref,
|
||||
CteMutationStrategy strategy,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory );
|
||||
}
|
||||
|
||||
protected void applyDmlOperations(
|
||||
CteContainer statement,
|
||||
CteStatement idSelectCte,
|
||||
SessionFactoryImplementor factory,
|
||||
TableGroup updatingTableGroup) {
|
||||
final SoftDeleteMapping softDeleteMapping = getEntityDescriptor().getSoftDeleteMapping();
|
||||
final TableDetails softDeleteTable = getEntityDescriptor().getSoftDeleteTableDetails();
|
||||
final CteTable dmlResultCte = new CteTable(
|
||||
getCteTableName( softDeleteTable.getTableName() ),
|
||||
idSelectCte.getCteTable().getCteColumns()
|
||||
);
|
||||
final TableReference updatingTableReference = updatingTableGroup.getTableReference(
|
||||
updatingTableGroup.getNavigablePath(),
|
||||
softDeleteTable.getTableName(),
|
||||
true
|
||||
);
|
||||
final NamedTableReference dmlTableReference = resolveUnionTableReference(
|
||||
updatingTableReference,
|
||||
softDeleteTable.getTableName()
|
||||
);
|
||||
|
||||
final List<ColumnReference> columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() );
|
||||
final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails();
|
||||
keyDetails.forEachKeyColumn( (position, selectable) -> columnReferences.add(
|
||||
new ColumnReference( dmlTableReference, selectable )
|
||||
) );
|
||||
|
||||
final ColumnReference softDeleteColumnReference = new ColumnReference(
|
||||
dmlTableReference,
|
||||
softDeleteMapping
|
||||
);
|
||||
final JdbcLiteral<?> deletedIndicator = new JdbcLiteral<>(
|
||||
softDeleteMapping.getDeletedLiteralValue(),
|
||||
softDeleteMapping.getJdbcMapping()
|
||||
);
|
||||
final Assignment assignment = new Assignment(
|
||||
softDeleteColumnReference,
|
||||
deletedIndicator
|
||||
);
|
||||
|
||||
final MutationStatement dmlStatement = new UpdateStatement(
|
||||
dmlTableReference,
|
||||
Collections.singletonList( assignment ),
|
||||
createIdSubQueryPredicate( columnReferences, idSelectCte, factory ),
|
||||
columnReferences
|
||||
);
|
||||
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
|
||||
}
|
||||
}
|
|
@ -7,16 +7,18 @@
|
|||
package org.hibernate.query.sqm.mutation.internal.inline;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.MutableInteger;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
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.sqm.internal.DomainParameterXref;
|
||||
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.from.NamedTableReference;
|
||||
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.spi.JdbcMutationExecutor;
|
||||
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.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
|
||||
*
|
||||
|
@ -128,9 +136,18 @@ public class InlineDeleteHandler implements DeleteHandler {
|
|||
}
|
||||
);
|
||||
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnsVisitationSupplier) -> {
|
||||
executeDelete(
|
||||
final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping();
|
||||
if ( softDeleteMapping != null ) {
|
||||
performSoftDelete(
|
||||
entityDescriptor,
|
||||
idsAndFks,
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
else {
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnsVisitationSupplier) -> executeDelete(
|
||||
tableExpression,
|
||||
entityDescriptor,
|
||||
tableKeyColumnsVisitationSupplier,
|
||||
|
@ -139,13 +156,71 @@ public class InlineDeleteHandler implements DeleteHandler {
|
|||
null,
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
);
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
String targetTableExpression,
|
||||
EntityMappingType entityDescriptor,
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
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.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.Joinable;
|
||||
|
@ -166,7 +168,7 @@ public class InlineUpdateHandler implements UpdateHandler {
|
|||
|
||||
final TableGroup updatingTableGroup = converterDelegate.getMutatingTableGroup();
|
||||
|
||||
final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference(
|
||||
final NamedTableReference hierarchyRootTableReference = (NamedTableReference) updatingTableGroup.resolveTableReference(
|
||||
updatingTableGroup.getNavigablePath(),
|
||||
hierarchyRootTableName
|
||||
);
|
||||
|
@ -207,7 +209,16 @@ public class InlineUpdateHandler implements UpdateHandler {
|
|||
final Predicate providedPredicate;
|
||||
final SqmWhereClause whereClause = sqmUpdate.getWhereClause();
|
||||
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 {
|
||||
providedPredicate = converterDelegate.visitWhereClause(
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public abstract class AbstractDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
|
||||
private final EntityMappingType entityDescriptor;
|
||||
private final TemporaryTable idTable;
|
||||
private final AfterUseAction afterUseAction;
|
||||
private final SqmDeleteStatement<?> sqmDelete;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
private final Function<SharedSessionContractImplementor,String> sessionUidAccess;
|
||||
|
||||
private final MultiTableSqmMutationConverter converter;
|
||||
|
||||
public AbstractDeleteExecutionDelegate(
|
||||
EntityMappingType entityDescriptor,
|
||||
TemporaryTable idTable,
|
||||
AfterUseAction afterUseAction,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref,
|
||||
QueryOptions queryOptions,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
QueryParameterBindings queryParameterBindings,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
this.idTable = idTable;
|
||||
this.afterUseAction = afterUseAction;
|
||||
this.sqmDelete = sqmDelete;
|
||||
this.domainParameterXref = domainParameterXref;
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
|
||||
this.converter = new MultiTableSqmMutationConverter(
|
||||
entityDescriptor,
|
||||
getSqmDelete(),
|
||||
getSqmDelete().getTarget(),
|
||||
getDomainParameterXref(),
|
||||
queryOptions,
|
||||
loadQueryInfluencers,
|
||||
queryParameterBindings,
|
||||
getSessionFactory()
|
||||
);
|
||||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
return entityDescriptor;
|
||||
}
|
||||
|
||||
public TemporaryTable getIdTable() {
|
||||
return idTable;
|
||||
}
|
||||
|
||||
public AfterUseAction getAfterUseAction() {
|
||||
return afterUseAction;
|
||||
}
|
||||
|
||||
public SqmDeleteStatement<?> getSqmDelete() {
|
||||
return sqmDelete;
|
||||
}
|
||||
|
||||
public DomainParameterXref getDomainParameterXref() {
|
||||
return domainParameterXref;
|
||||
}
|
||||
|
||||
public SessionFactoryImplementor getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
public Function<SharedSessionContractImplementor, String> getSessionUidAccess() {
|
||||
return sessionUidAccess;
|
||||
}
|
||||
|
||||
public MultiTableSqmMutationConverter getConverter() {
|
||||
return converter;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
|||
import org.hibernate.engine.config.spi.StandardConverters;
|
||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
@ -52,6 +53,10 @@ public class GlobalTemporaryTableStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
return temporaryTable.getEntityDescriptor();
|
||||
}
|
||||
|
||||
public void prepare(
|
||||
MappingModelCreationProcess mappingModelCreationProcess,
|
||||
JdbcConnectionAccess connectionAccess) {
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.query.sqm.mutation.internal.temptable;
|
|||
|
||||
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
|
||||
|
@ -51,7 +52,7 @@ public class LocalTemporaryTableMutationStrategy extends LocalTemporaryTableStra
|
|||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref,
|
||||
DomainQueryExecutionContext context) {
|
||||
return new TableBasedDeleteHandler(
|
||||
final TableBasedDeleteHandler deleteHandler = new TableBasedDeleteHandler(
|
||||
sqmDelete,
|
||||
domainParameterXref,
|
||||
getTemporaryTable(),
|
||||
|
@ -62,7 +63,8 @@ public class LocalTemporaryTableMutationStrategy extends LocalTemporaryTableStra
|
|||
throw new UnsupportedOperationException( "Unexpected call to access Session uid" );
|
||||
},
|
||||
getSessionFactory()
|
||||
).execute( context );
|
||||
);
|
||||
return deleteHandler.execute( context );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
|||
import org.hibernate.engine.config.spi.StandardConverters;
|
||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
|
||||
/**
|
||||
|
@ -56,6 +57,10 @@ public class LocalTemporaryTableStrategy {
|
|||
return temporaryTable;
|
||||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
return getTemporaryTable().getEntityDescriptor();
|
||||
}
|
||||
|
||||
public boolean isDropIdTables() {
|
||||
return dropIdTables;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.hibernate.engine.config.spi.ConfigurationService;
|
|||
import org.hibernate.engine.config.spi.StandardConverters;
|
||||
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
|
||||
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";
|
||||
|
||||
private final TemporaryTable temporaryTable;
|
||||
|
||||
private final SessionFactoryImplementor sessionFactory;
|
||||
|
||||
private boolean prepared;
|
||||
|
@ -58,6 +58,10 @@ public abstract class PersistentTableStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
public EntityMappingType getEntityDescriptor() {
|
||||
return getTemporaryTable().getEntityDescriptor();
|
||||
}
|
||||
|
||||
public void prepare(
|
||||
MappingModelCreationProcess mappingModelCreationProcess,
|
||||
JdbcConnectionAccess connectionAccess) {
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.hibernate.query.spi.QueryParameterBindings;
|
|||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.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.mutation.internal.TableKeyExpressionCollector;
|
||||
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.JdbcParameterBindings;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import static org.hibernate.query.sqm.mutation.internal.MutationQueryLogging.MUTATION_QUERY_LOGGER;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate {
|
||||
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 class RestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate {
|
||||
public RestrictedDeleteExecutionDelegate(
|
||||
EntityMappingType entityDescriptor,
|
||||
TemporaryTable idTable,
|
||||
AfterUseAction afterUseAction,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
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.sessionUidAccess = sessionUidAccess;
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.converter = new MultiTableSqmMutationConverter(
|
||||
super(
|
||||
entityDescriptor,
|
||||
idTable,
|
||||
afterUseAction,
|
||||
sqmDelete,
|
||||
sqmDelete.getTarget(),
|
||||
domainParameterXref,
|
||||
queryOptions,
|
||||
loadQueryInfluencers,
|
||||
queryParameterBindings,
|
||||
sessionUidAccess,
|
||||
sessionFactory
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(DomainQueryExecutionContext executionContext) {
|
||||
final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels()
|
||||
final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels()
|
||||
.getMappingMetamodel()
|
||||
.getEntityDescriptor( sqmDelete.getTarget().getEntityName() );
|
||||
.getEntityDescriptor( getSqmDelete().getTarget().getEntityName() );
|
||||
final String hierarchyRootTableName = ( (Joinable) entityDescriptor ).getTableName();
|
||||
|
||||
final TableGroup deletingTableGroup = converter.getMutatingTableGroup();
|
||||
final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup();
|
||||
|
||||
final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference(
|
||||
deletingTableGroup.getNavigablePath(),
|
||||
|
@ -128,7 +110,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
final Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions;
|
||||
final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
|
||||
|
||||
if ( domainParameterXref.getSqmParameterCount() == 0 ) {
|
||||
if ( getDomainParameterXref().getSqmParameterCount() == 0 ) {
|
||||
parameterResolutions = 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
|
||||
// we can perform all of the deletes without using an id-table
|
||||
final MutableBoolean needsIdTableWrapper = new MutableBoolean( false );
|
||||
final Predicate specifiedRestriction = converter.visitWhereClause(
|
||||
sqmDelete.getWhereClause(),
|
||||
final Predicate specifiedRestriction = getConverter().visitWhereClause(
|
||||
getSqmDelete().getWhereClause(),
|
||||
columnReference -> {
|
||||
if ( ! hierarchyRootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
|
||||
needsIdTableWrapper.setValue( true );
|
||||
|
@ -169,10 +151,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
true,
|
||||
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||
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
|
||||
// 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,
|
||||
parameterResolutions,
|
||||
paramTypeResolutions,
|
||||
converter.getSqlExpressionResolver(),
|
||||
getConverter().getSqlExpressionResolver(),
|
||||
executionContextAdapter
|
||||
);
|
||||
}
|
||||
|
@ -211,9 +193,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions,
|
||||
SqlExpressionResolver sqlExpressionResolver,
|
||||
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 NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference(
|
||||
tableGroup.getNavigablePath(),
|
||||
|
@ -226,17 +208,17 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
suppliedPredicate,
|
||||
rootEntityPersister,
|
||||
sqlExpressionResolver,
|
||||
sessionFactory
|
||||
getSessionFactory()
|
||||
);
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
domainParameterXref,
|
||||
getDomainParameterXref(),
|
||||
SqmUtil.generateJdbcParamsXref(
|
||||
domainParameterXref,
|
||||
getDomainParameterXref(),
|
||||
() -> restrictionSqmParameterResolutions
|
||||
),
|
||||
sessionFactory.getRuntimeMetamodels().getMappingMetamodel(),
|
||||
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||
navigablePath -> tableGroup,
|
||||
new SqmParameterMappingModelResolutionAccess() {
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
|
@ -248,7 +230,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
);
|
||||
|
||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||
entityDescriptor,
|
||||
getEntityDescriptor(),
|
||||
(tableReference, attributeMapping) -> {
|
||||
// No need for a predicate if there is no supplied predicate i.e. this is a full cleanup
|
||||
if ( suppliedPredicate == null ) {
|
||||
|
@ -267,7 +249,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
suppliedPredicate,
|
||||
rootEntityPersister,
|
||||
sqlExpressionResolver,
|
||||
sessionFactory
|
||||
getSessionFactory()
|
||||
);
|
||||
}
|
||||
return new InSubQueryPredicate(
|
||||
|
@ -279,7 +261,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
),
|
||||
fkDescriptor,
|
||||
null,
|
||||
sessionFactory
|
||||
getSessionFactory()
|
||||
),
|
||||
idSelectFkSubQuery,
|
||||
false
|
||||
|
@ -292,7 +274,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
|
||||
if ( rootTableReference instanceof UnionTableReference ) {
|
||||
final MutableInteger rows = new MutableInteger();
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
||||
final NamedTableReference tableReference = new NamedTableReference(
|
||||
tableExpression,
|
||||
|
@ -322,7 +304,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
return rows.get();
|
||||
}
|
||||
else {
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnVisitationSupplier) -> {
|
||||
if ( !tableExpression.equals( rootTableName ) ) {
|
||||
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference(
|
||||
|
@ -381,7 +363,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext) {
|
||||
assert targetTableReference != null;
|
||||
log.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() );
|
||||
MUTATION_QUERY_LOGGER.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() );
|
||||
|
||||
final NamedTableReference deleteTableReference = new NamedTableReference(
|
||||
targetTableReference.getTableExpression(),
|
||||
|
@ -423,7 +405,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 );
|
||||
}
|
||||
else {
|
||||
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() );
|
||||
deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() );
|
||||
}
|
||||
|
||||
tableDeletePredicate = new InSubQueryPredicate(
|
||||
|
@ -439,7 +421,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
log.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
|
||||
MUTATION_QUERY_LOGGER.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows );
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
@ -477,12 +459,12 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
ExecutionContext executionContext) {
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
domainParameterXref,
|
||||
getDomainParameterXref(),
|
||||
SqmUtil.generateJdbcParamsXref(
|
||||
domainParameterXref,
|
||||
getDomainParameterXref(),
|
||||
() -> restrictionSqmParameterResolutions
|
||||
),
|
||||
sessionFactory.getRuntimeMetamodels().getMappingMetamodel(),
|
||||
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||
navigablePath -> deletingTableGroup,
|
||||
new SqmParameterMappingModelResolutionAccess() {
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
|
@ -494,7 +476,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
);
|
||||
|
||||
ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions(
|
||||
idTable,
|
||||
getIdTable(),
|
||||
executionContext
|
||||
);
|
||||
|
||||
|
@ -503,9 +485,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
}
|
||||
finally {
|
||||
ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions(
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
afterUseAction,
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
getAfterUseAction(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
@ -516,23 +498,23 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
ExecutionContext executionContext,
|
||||
JdbcParameterBindings jdbcParameterBindings) {
|
||||
final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable(
|
||||
converter,
|
||||
getConverter(),
|
||||
predicate,
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
|
||||
final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||
idTable,
|
||||
sessionUidAccess,
|
||||
entityDescriptor,
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
getEntityDescriptor(),
|
||||
executionContext
|
||||
);
|
||||
|
||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||
entityDescriptor,
|
||||
getEntityDescriptor(),
|
||||
(tableReference, attributeMapping) -> {
|
||||
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||
final QuerySpec idTableFkSubQuery;
|
||||
|
@ -541,10 +523,10 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
}
|
||||
else {
|
||||
idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||
idTable,
|
||||
getIdTable(),
|
||||
fkDescriptor.getTargetPart(),
|
||||
sessionUidAccess,
|
||||
entityDescriptor,
|
||||
getSessionUidAccess(),
|
||||
getEntityDescriptor(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
|
@ -557,7 +539,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
),
|
||||
fkDescriptor,
|
||||
null,
|
||||
sessionFactory
|
||||
getSessionFactory()
|
||||
),
|
||||
idTableFkSubQuery,
|
||||
false
|
||||
|
@ -568,7 +550,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
executionContext
|
||||
);
|
||||
|
||||
entityDescriptor.visitConstraintOrderedTables(
|
||||
getEntityDescriptor().visitConstraintOrderedTables(
|
||||
(tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable(
|
||||
tableExpression,
|
||||
tableKeyColumnVisitationSupplier,
|
||||
|
@ -585,11 +567,9 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle
|
|||
Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier,
|
||||
QuerySpec idTableSubQuery,
|
||||
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( entityDescriptor );
|
||||
final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() );
|
||||
final NamedTableReference targetTable = new NamedTableReference(
|
||||
tableExpression,
|
||||
DeleteStatement.DEFAULT_ALIAS,
|
||||
|
|
|
@ -0,0 +1,432 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.sqm.mutation.internal.temptable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.boot.model.internal.SoftDeleteHelper;
|
||||
import org.hibernate.dialect.temptable.TemporaryTable;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.util.MutableBoolean;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.TableDetails;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.spi.DomainQueryExecutionContext;
|
||||
import org.hibernate.query.spi.QueryOptions;
|
||||
import org.hibernate.query.spi.QueryParameterBindings;
|
||||
import org.hibernate.query.sqm.internal.DomainParameterXref;
|
||||
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
|
||||
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
|
||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper;
|
||||
import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.PredicateCollector;
|
||||
import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||
import org.hibernate.sql.ast.tree.update.Assignment;
|
||||
import org.hibernate.sql.ast.tree.update.UpdateStatement;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
|
||||
import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SoftDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate {
|
||||
public SoftDeleteExecutionDelegate(
|
||||
EntityMappingType entityDescriptor,
|
||||
TemporaryTable idTable,
|
||||
AfterUseAction afterUseAction,
|
||||
SqmDeleteStatement<?> sqmDelete,
|
||||
DomainParameterXref domainParameterXref,
|
||||
QueryOptions queryOptions,
|
||||
LoadQueryInfluencers loadQueryInfluencers,
|
||||
QueryParameterBindings queryParameterBindings,
|
||||
Function<SharedSessionContractImplementor, String> sessionUidAccess,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
super(
|
||||
entityDescriptor,
|
||||
idTable,
|
||||
afterUseAction,
|
||||
sqmDelete,
|
||||
domainParameterXref,
|
||||
queryOptions,
|
||||
loadQueryInfluencers,
|
||||
queryParameterBindings,
|
||||
sessionUidAccess,
|
||||
sessionFactory
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(DomainQueryExecutionContext domainQueryExecutionContext) {
|
||||
final String targetEntityName = getSqmDelete().getTarget().getEntityName();
|
||||
final EntityPersister targetEntityDescriptor = getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel()
|
||||
.getEntityDescriptor( targetEntityName );
|
||||
|
||||
final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor();
|
||||
final boolean targetIsHierarchyRoot = rootEntityDescriptor == targetEntityDescriptor;
|
||||
|
||||
// determine if we need to use a sub-query for matching ids -
|
||||
// 1. if the target is not the root we will
|
||||
// 2. if the supplied predicate (if any) refers to columns from a table
|
||||
// other than the identifier table we will
|
||||
final MutableBoolean needsSubQueryRef = new MutableBoolean( !targetIsHierarchyRoot );
|
||||
|
||||
final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( domainQueryExecutionContext );
|
||||
|
||||
final Map<SqmParameter<?>, List<List<JdbcParameter>>> parameterResolutions;
|
||||
final Map<SqmParameter<?>, MappingModelExpressible<?>> paramTypeResolutions;
|
||||
if ( getDomainParameterXref().getSqmParameterCount() == 0 ) {
|
||||
parameterResolutions = Collections.emptyMap();
|
||||
paramTypeResolutions = Collections.emptyMap();
|
||||
}
|
||||
else {
|
||||
parameterResolutions = new IdentityHashMap<>();
|
||||
paramTypeResolutions = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup();
|
||||
final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails();
|
||||
final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference(
|
||||
deletingTableGroup.getNavigablePath(),
|
||||
softDeleteTable.getTableName()
|
||||
);
|
||||
assert rootTableReference != null;
|
||||
|
||||
// NOTE : `converter.visitWhereClause` already applies the soft-delete restriction
|
||||
final Predicate specifiedRestriction = getConverter().visitWhereClause(
|
||||
getSqmDelete().getWhereClause(),
|
||||
columnReference -> {
|
||||
if ( !rootTableReference.getIdentificationVariable().equals( columnReference.getQualifier() ) ) {
|
||||
// the predicate referred to a column from a table other than hierarchy identifier table
|
||||
needsSubQueryRef.setValue( true );
|
||||
}
|
||||
},
|
||||
(sqmParameter, mappingType, jdbcParameters) -> {
|
||||
parameterResolutions.computeIfAbsent(
|
||||
sqmParameter,
|
||||
k -> new ArrayList<>( 1 )
|
||||
).add( jdbcParameters );
|
||||
paramTypeResolutions.put( sqmParameter, mappingType );
|
||||
}
|
||||
);
|
||||
|
||||
final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction );
|
||||
targetEntityDescriptor.applyBaseRestrictions(
|
||||
(filterPredicate) -> {
|
||||
needsSubQueryRef.setValue( true );
|
||||
predicateCollector.applyPredicate( filterPredicate );
|
||||
},
|
||||
deletingTableGroup,
|
||||
true,
|
||||
executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(),
|
||||
null,
|
||||
getConverter()
|
||||
);
|
||||
|
||||
getConverter().pruneTableGroupJoins();
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
executionContext.getQueryParameterBindings(),
|
||||
getDomainParameterXref(),
|
||||
SqmUtil.generateJdbcParamsXref(
|
||||
getDomainParameterXref(),
|
||||
() -> parameterResolutions
|
||||
),
|
||||
getSessionFactory().getRuntimeMetamodels().getMappingMetamodel(),
|
||||
navigablePath -> deletingTableGroup,
|
||||
new SqmParameterMappingModelResolutionAccess() {
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
public <T> MappingModelExpressible<T> getResolvedMappingModelType(SqmParameter<T> parameter) {
|
||||
return (MappingModelExpressible<T>) paramTypeResolutions.get(parameter);
|
||||
}
|
||||
},
|
||||
executionContext.getSession()
|
||||
);
|
||||
|
||||
final boolean needsSubQuery = needsSubQueryRef.getValue();
|
||||
if ( needsSubQuery ) {
|
||||
if ( getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() ) {
|
||||
return performDeleteWithSubQuery(
|
||||
targetEntityDescriptor,
|
||||
rootEntityDescriptor,
|
||||
deletingTableGroup,
|
||||
rootTableReference,
|
||||
predicateCollector,
|
||||
jdbcParameterBindings,
|
||||
getConverter(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
else {
|
||||
return performDeleteWithIdTable(
|
||||
rootEntityDescriptor,
|
||||
rootTableReference,
|
||||
predicateCollector,
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performDirectDelete(
|
||||
targetEntityDescriptor,
|
||||
rootEntityDescriptor,
|
||||
deletingTableGroup,
|
||||
rootTableReference,
|
||||
predicateCollector,
|
||||
jdbcParameterBindings,
|
||||
getConverter(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private int performDeleteWithIdTable(
|
||||
EntityMappingType rootEntityDescriptor,
|
||||
NamedTableReference targetTableReference,
|
||||
PredicateCollector predicateCollector,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
SqmJdbcExecutionContextAdapter executionContext) {
|
||||
ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions(
|
||||
getIdTable(),
|
||||
executionContext
|
||||
);
|
||||
|
||||
try {
|
||||
return deleteUsingIdTable(
|
||||
rootEntityDescriptor,
|
||||
targetTableReference,
|
||||
predicateCollector,
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
finally {
|
||||
ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions(
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
getAfterUseAction(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private int deleteUsingIdTable(
|
||||
EntityMappingType rootEntityDescriptor,
|
||||
NamedTableReference targetTableReference,
|
||||
PredicateCollector predicateCollector,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
SqmJdbcExecutionContextAdapter executionContext) {
|
||||
final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable(
|
||||
getConverter(),
|
||||
predicateCollector.getPredicate(),
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
jdbcParameterBindings,
|
||||
executionContext
|
||||
);
|
||||
|
||||
final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||
getIdTable(),
|
||||
getSessionUidAccess(),
|
||||
getEntityDescriptor(),
|
||||
executionContext
|
||||
);
|
||||
|
||||
SqmMutationStrategyHelper.cleanUpCollectionTables(
|
||||
getEntityDescriptor(),
|
||||
(tableReference, attributeMapping) -> {
|
||||
final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor();
|
||||
final QuerySpec idTableFkSubQuery;
|
||||
if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) {
|
||||
idTableFkSubQuery = idTableIdentifierSubQuery;
|
||||
}
|
||||
else {
|
||||
idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec(
|
||||
getIdTable(),
|
||||
fkDescriptor.getTargetPart(),
|
||||
getSessionUidAccess(),
|
||||
getEntityDescriptor(),
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
return new InSubQueryPredicate(
|
||||
MappingModelCreationHelper.buildColumnReferenceExpression(
|
||||
new MutatingTableReferenceGroupWrapper(
|
||||
new NavigablePath( attributeMapping.getRootPathName() ),
|
||||
attributeMapping,
|
||||
(NamedTableReference) tableReference
|
||||
),
|
||||
fkDescriptor,
|
||||
null,
|
||||
getSessionFactory()
|
||||
),
|
||||
idTableFkSubQuery,
|
||||
false
|
||||
);
|
||||
|
||||
},
|
||||
JdbcParameterBindings.NO_BINDINGS,
|
||||
executionContext
|
||||
);
|
||||
|
||||
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||
targetTableReference,
|
||||
rootEntityDescriptor.getSoftDeleteMapping()
|
||||
);
|
||||
|
||||
final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails();
|
||||
final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails();
|
||||
final List<Expression> idExpressions = new ArrayList<>( keyDetails.getColumnCount() );
|
||||
keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add(
|
||||
new ColumnReference( targetTableReference, column )
|
||||
) );
|
||||
final Expression idExpression = idExpressions.size() == 1
|
||||
? idExpressions.get( 0 )
|
||||
: new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() );
|
||||
|
||||
final UpdateStatement updateStatement = new UpdateStatement(
|
||||
targetTableReference,
|
||||
Collections.singletonList( softDeleteAssignment ),
|
||||
new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false )
|
||||
);
|
||||
|
||||
executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private int performDeleteWithSubQuery(
|
||||
EntityMappingType targetEntityDescriptor,
|
||||
EntityMappingType rootEntityDescriptor,
|
||||
TableGroup deletingTableGroup,
|
||||
NamedTableReference rootTableReference,
|
||||
PredicateCollector predicateCollector,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
MultiTableSqmMutationConverter converter,
|
||||
SqmJdbcExecutionContextAdapter executionContext) {
|
||||
final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 );
|
||||
matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup );
|
||||
|
||||
final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails();
|
||||
final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails();
|
||||
|
||||
final NamedTableReference targetTable = new NamedTableReference(
|
||||
identifierTableDetails.getTableName(),
|
||||
DeleteStatement.DEFAULT_ALIAS,
|
||||
false
|
||||
);
|
||||
|
||||
final List<Expression> idExpressions = new ArrayList<>( keyDetails.getColumnCount() );
|
||||
keyDetails.forEachKeyColumn( (position, column) -> {
|
||||
final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression(
|
||||
rootTableReference,
|
||||
column
|
||||
);
|
||||
matchingIdSubQuery.getSelectClause().addSqlSelection(
|
||||
new SqlSelectionImpl( position, columnReference )
|
||||
);
|
||||
idExpressions.add( new ColumnReference( targetTable, column ) );
|
||||
} );
|
||||
|
||||
matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() );
|
||||
final Expression idExpression = idExpressions.size() == 1
|
||||
? idExpressions.get( 0 )
|
||||
: new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() );
|
||||
|
||||
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||
targetTable,
|
||||
rootEntityDescriptor.getSoftDeleteMapping()
|
||||
);
|
||||
|
||||
final UpdateStatement updateStatement = new UpdateStatement(
|
||||
targetTable,
|
||||
Collections.singletonList( softDeleteAssignment ),
|
||||
new InSubQueryPredicate( idExpression, matchingIdSubQuery, false )
|
||||
);
|
||||
|
||||
return executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||
}
|
||||
|
||||
private int performDirectDelete(
|
||||
EntityMappingType targetEntityDescriptor,
|
||||
EntityMappingType rootEntityDescriptor,
|
||||
TableGroup deletingTableGroup,
|
||||
NamedTableReference rootTableReference,
|
||||
PredicateCollector predicateCollector,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
MultiTableSqmMutationConverter converter,
|
||||
SqmJdbcExecutionContextAdapter executionContext) {
|
||||
final Assignment softDeleteAssignment = SoftDeleteHelper.createSoftDeleteAssignment(
|
||||
rootTableReference,
|
||||
rootEntityDescriptor.getSoftDeleteMapping()
|
||||
);
|
||||
|
||||
final UpdateStatement updateStatement = new UpdateStatement(
|
||||
rootTableReference,
|
||||
Collections.singletonList( softDeleteAssignment ),
|
||||
predicateCollector.getPredicate()
|
||||
);
|
||||
|
||||
return executeUpdate( updateStatement, jdbcParameterBindings, executionContext );
|
||||
}
|
||||
|
||||
private int executeUpdate(
|
||||
UpdateStatement updateStatement,
|
||||
JdbcParameterBindings jdbcParameterBindings,
|
||||
ExecutionContext executionContext) {
|
||||
final SessionFactoryImplementor factory = executionContext.getSession().getFactory();
|
||||
final JdbcServices jdbcServices = factory.getJdbcServices();
|
||||
|
||||
final JdbcOperationQueryUpdate jdbcUpdate = jdbcServices.getJdbcEnvironment()
|
||||
.getSqlAstTranslatorFactory()
|
||||
.buildUpdateTranslator( factory, updateStatement )
|
||||
.translate( jdbcParameterBindings, executionContext.getQueryOptions() );
|
||||
|
||||
return jdbcServices.getJdbcMutationExecutor().execute(
|
||||
jdbcUpdate,
|
||||
jdbcParameterBindings,
|
||||
sql -> executionContext.getSession()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( sql ),
|
||||
(integer, preparedStatement) -> {},
|
||||
executionContext
|
||||
);
|
||||
}
|
||||
}
|
|
@ -65,16 +65,31 @@ public class TableBasedDeleteHandler
|
|||
}
|
||||
|
||||
protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) {
|
||||
if ( getEntityDescriptor().getSoftDeleteMapping() != null ) {
|
||||
return new SoftDeleteExecutionDelegate(
|
||||
getEntityDescriptor(),
|
||||
idTable,
|
||||
afterUseAction,
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
domainParameterXref,
|
||||
executionContext.getQueryOptions(),
|
||||
executionContext.getSession().getLoadQueryInfluencers(),
|
||||
executionContext.getQueryParameterBindings(),
|
||||
sessionUidAccess,
|
||||
getSessionFactory()
|
||||
);
|
||||
}
|
||||
|
||||
return new RestrictedDeleteExecutionDelegate(
|
||||
getEntityDescriptor(),
|
||||
idTable,
|
||||
afterUseAction,
|
||||
getSqmDeleteOrUpdateStatement(),
|
||||
domainParameterXref,
|
||||
sessionUidAccess,
|
||||
executionContext.getQueryOptions(),
|
||||
executionContext.getSession().getLoadQueryInfluencers(),
|
||||
executionContext.getQueryParameterBindings(),
|
||||
sessionUidAccess,
|
||||
getSessionFactory()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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.SessionFactoryImplementor;
|
||||
|
@ -23,6 +24,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
|
|||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.results.TableGroupImpl;
|
||||
|
@ -97,15 +99,29 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
this.afterUseAction = afterUseAction;
|
||||
this.sessionUidAccess = sessionUidAccess;
|
||||
this.updatingTableGroup = updatingTableGroup;
|
||||
this.suppliedPredicate = suppliedPredicate;
|
||||
|
||||
this.sessionFactory = executionContext.getSession().getFactory();
|
||||
|
||||
final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart();
|
||||
assert updatingModelPart instanceof EntityMappingType;
|
||||
|
||||
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 );
|
||||
|
||||
jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(
|
||||
|
@ -513,4 +529,5 @@ public class UpdateExecutionDelegate implements TableBasedUpdateHandler.Executio
|
|||
protected SessionFactoryImplementor getSessionFactory() {
|
||||
return sessionFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6127,7 +6127,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
|
||||
@Override
|
||||
public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {
|
||||
// nothing to do... handled within TableGroup#render
|
||||
// nothing to do... handled within TableGroupTableGroup#render
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -224,6 +224,15 @@ public class ColumnReference implements Expression, Assignable {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
if ( StringHelper.isNotEmpty( qualifier ) ) {
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"%s(%s.%s)",
|
||||
getClass().getSimpleName(),
|
||||
qualifier,
|
||||
getExpressionText()
|
||||
);
|
||||
}
|
||||
return String.format(
|
||||
Locale.ROOT,
|
||||
"%s(%s)",
|
||||
|
|
|
@ -38,7 +38,7 @@ public class ColumnWriteFragment implements Expression {
|
|||
|
||||
public ColumnWriteFragment(String fragment, ColumnValueParameter parameter, JdbcMapping jdbcMapping) {
|
||||
this( fragment, Collections.singletonList( parameter ), jdbcMapping );
|
||||
assert parameter != null;
|
||||
assert !fragment.contains( "?" ) || parameter != null;
|
||||
}
|
||||
|
||||
public ColumnWriteFragment(String fragment, List<ColumnValueParameter> parameters, JdbcMapping jdbcMapping) {
|
||||
|
|
|
@ -76,6 +76,11 @@ public abstract class AbstractRestrictedTableMutationBuilder<O extends MutationO
|
|||
optimisticLockBindings.addRestriction( columnName, columnWriteFragment, jdbcMapping );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||
keyRestrictionBindings.addRestriction( columnName, sqlLiteralText, jdbcMapping );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWhere(String fragment) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -100,6 +100,8 @@ public interface RestrictedTableMutationBuilder<O extends MutationOperation, M e
|
|||
*/
|
||||
void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping);
|
||||
|
||||
void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping);
|
||||
|
||||
ColumnValueBindingList getKeyRestrictionBindings();
|
||||
|
||||
ColumnValueBindingList getOptimisticLockBindings();
|
||||
|
|
|
@ -35,6 +35,10 @@ public class TableDeleteBuilderSkipped implements TableDeleteBuilder {
|
|||
public void addOptimisticLockRestriction(String columnName, String columnWriteFragment, JdbcMapping jdbcMapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnValueBindingList getKeyRestrictionBindings() {
|
||||
return null;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.sql.model.ast.builder;
|
||||
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.sql.model.ast.TableInsert;
|
||||
|
|
|
@ -48,6 +48,10 @@ public class TableUpdateBuilderSkipped implements TableUpdateBuilder {
|
|||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLiteralRestriction(String columnName, String sqlLiteralText, JdbcMapping jdbcMapping) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnValueBindingList getKeyRestrictionBindings() {
|
||||
return null;
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.sql.model.MutationOperation;
|
||||
import org.hibernate.sql.model.MutationTarget;
|
||||
import org.hibernate.sql.model.TableMapping;
|
||||
|
|
|
@ -103,8 +103,8 @@ class ColumnDefinitions {
|
|||
SqlStringGenerationContext context) {
|
||||
statement.append( column.getQuotedName( dialect ) );
|
||||
appendColumnDefinition( statement, column, table, metadata, dialect );
|
||||
appendConstraints( statement, column, table, dialect, context );
|
||||
appendComment( statement, column, dialect );
|
||||
appendConstraints( statement, column, table, dialect, context );
|
||||
}
|
||||
|
||||
private static void appendConstraints(
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.type;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.descriptor.java.BooleanJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
/**
|
||||
* Simple pass-through boolean value converter.
|
||||
* Useful in {@linkplain SoftDelete#converter() certain scenarios}.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class BooleanAsBooleanConverter implements StandardBooleanConverter<Boolean> {
|
||||
public static final BooleanAsBooleanConverter INSTANCE = new BooleanAsBooleanConverter();
|
||||
|
||||
@Override
|
||||
public Boolean convertToDatabaseColumn(Boolean attribute) {
|
||||
return toRelationalValue( attribute );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertToEntityAttribute(Boolean dbData) {
|
||||
return toDomainValue( dbData );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean toDomainValue(Boolean relationalForm) {
|
||||
return relationalForm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean toRelationalValue(Boolean domainForm) {
|
||||
return domainForm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<Boolean> getDomainJavaType() {
|
||||
return BooleanJavaType.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<Boolean> getRelationalJavaType() {
|
||||
return BooleanJavaType.INSTANCE;
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.type;
|
||||
|
||||
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.CharacterJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
@ -18,11 +16,7 @@ import org.hibernate.type.descriptor.java.JavaType;
|
|||
* @author Steve Ebersole
|
||||
* @author Gavin King
|
||||
*/
|
||||
public abstract class CharBooleanConverter
|
||||
implements AttributeConverter<Boolean, Character>, BasicValueConverter<Boolean, Character> {
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
public abstract class CharBooleanConverter implements StandardBooleanConverter<Character> {
|
||||
@Override
|
||||
public Character convertToDatabaseColumn(Boolean attribute) {
|
||||
return toRelationalValue( attribute );
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
*/
|
||||
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.IntegerJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
/**
|
||||
|
@ -20,8 +18,7 @@ import jakarta.persistence.Converter;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
@Converter
|
||||
public class NumericBooleanConverter implements AttributeConverter<Boolean, Integer>,
|
||||
BasicValueConverter<Boolean, Integer> {
|
||||
public class NumericBooleanConverter implements StandardBooleanConverter<Integer> {
|
||||
/**
|
||||
* Singleton access
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.type;
|
||||
|
||||
/**
|
||||
* Marker for Hibernate defined converters of Boolean-typed domain values
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface StandardBooleanConverter<R> extends StandardConverter<Boolean,R> {
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.type;
|
||||
|
||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
|
||||
/**
|
||||
* Marker for Hibernate supplied {@linkplain AttributeConverter converter} classes.
|
||||
* <p/>
|
||||
* Also implements the Hibernate-specific BasicValueConverter contract
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface StandardConverter<O,R> extends AttributeConverter<O,R>, BasicValueConverter<O,R> {
|
||||
}
|
|
@ -19,6 +19,7 @@ public class TrueFalseConverter extends CharBooleanConverter {
|
|||
* Singleton access
|
||||
*/
|
||||
public static final TrueFalseConverter INSTANCE = new TrueFalseConverter();
|
||||
|
||||
private static final String[] VALUES = {"F", "T"};
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,7 @@ public class YesNoConverter extends CharBooleanConverter {
|
|||
* Singleton access
|
||||
*/
|
||||
public static final YesNoConverter INSTANCE = new YesNoConverter();
|
||||
|
||||
private static final String[] VALUES = {"N", "Y"};
|
||||
|
||||
@Override
|
||||
|
|
|
@ -57,7 +57,7 @@ public class FailingAddToBatchTest extends AbstractBatchingTest {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new RuntimeException();
|
||||
// final Long id = scope.fromTransaction( (em) -> {
|
||||
|
@ -80,7 +80,7 @@ public class FailingAddToBatchTest extends AbstractBatchingTest {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new RuntimeException();
|
||||
// Long id = scope.fromTransaction( em -> {
|
||||
|
|
|
@ -41,7 +41,7 @@ import static org.hamcrest.Matchers.nullValue;
|
|||
@SessionFactory
|
||||
public class EntityHidingTests {
|
||||
@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) {
|
||||
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
|
||||
final RuntimeMetamodels runtimeMetamodels = sessionFactory.getRuntimeMetamodels();
|
||||
|
|
|
@ -95,7 +95,6 @@ public class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
|
|||
|
||||
@Test
|
||||
@FailureExpected( jiraKey = "HHH-14975", message = "Not yet implemented" )
|
||||
@NotImplementedYet
|
||||
@JiraKey( "HHH-14975" )
|
||||
public void testAutoAppliedConverterAsNativeQueryResult() {
|
||||
inTransaction( (session) -> {
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.YesNoConverter;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for collections which contain soft-deletable entities
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = { CollectionOfSoftDeleteTests.Shelf.class, CollectionOfSoftDeleteTests.Book.class } )
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class CollectionOfSoftDeleteTests {
|
||||
@BeforeEach
|
||||
void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Shelf horror = new Shelf( 1, "Horror" );
|
||||
session.persist( horror );
|
||||
|
||||
final Book theShining = new Book( 1, "The Shining" );
|
||||
horror.addBook( theShining );
|
||||
session.persist( theShining );
|
||||
|
||||
session.flush();
|
||||
|
||||
session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "update books set deleted = 'Y' where id = 1" );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "delete from books" );
|
||||
statement.execute( "delete from shelves" );
|
||||
} ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final Shelf loaded = session.get( Shelf.class, 1 );
|
||||
assertThat( loaded ).isNotNull();
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||
|
||||
sqlInspector.clear();
|
||||
|
||||
assertThat( loaded.getBooks() ).isNotNull();
|
||||
assertThat( loaded.getBooks() ).isEmpty();
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".deleted='N'" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQueryJoin(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final Shelf shelf = session
|
||||
.createSelectionQuery( "from Shelf join fetch books", Shelf.class )
|
||||
.uniqueResult();
|
||||
assertThat( shelf ).isNotNull();
|
||||
assertThat( shelf.getBooks() ).isNotNull();
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".deleted='N'" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity(name="Shelf")
|
||||
@Table(name="shelves")
|
||||
public static class Shelf {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@OneToMany
|
||||
@JoinColumn(name = "shelf_fk")
|
||||
private Collection<Book> books;
|
||||
|
||||
public Shelf() {
|
||||
}
|
||||
|
||||
public Shelf(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Collection<Book> getBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public void setBooks(Collection<Book> books) {
|
||||
this.books = books;
|
||||
}
|
||||
|
||||
public void addBook(Book book) {
|
||||
if ( books == null ) {
|
||||
books = new ArrayList<>();
|
||||
}
|
||||
books.add( book );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name="Book")
|
||||
@Table(name="books")
|
||||
@SoftDelete(converter = YesNoConverter.class)
|
||||
public static class Book {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
public Book(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.type.BooleanAsBooleanConverter;
|
||||
import org.hibernate.type.NumericBooleanConverter;
|
||||
import org.hibernate.type.TrueFalseConverter;
|
||||
import org.hibernate.type.YesNoConverter;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* Centralizes the checks about column names, values, etc.
|
||||
* to avoid problems across dialects
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = {
|
||||
MappingTests.BooleanEntity.class,
|
||||
MappingTests.NumericEntity.class,
|
||||
MappingTests.TrueFalseEntity.class,
|
||||
MappingTests.YesNoEntity.class,
|
||||
MappingTests.ReversedYesNoEntity.class
|
||||
})
|
||||
@SessionFactory(exportSchema = false)
|
||||
@SuppressWarnings("unused")
|
||||
public class MappingTests {
|
||||
@Test
|
||||
void verifyEntityMappings(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( BooleanEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"boolean_entity",
|
||||
true
|
||||
);
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( NumericEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"numeric_entity",
|
||||
1
|
||||
);
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( TrueFalseEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"true_false_entity",
|
||||
'T'
|
||||
);
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( YesNoEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"yes_no_entity",
|
||||
'Y'
|
||||
);
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( ReversedYesNoEntity.class ).getSoftDeleteMapping(),
|
||||
"active",
|
||||
"reversed_yes_no_entity",
|
||||
'N'
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name="BooleanEntity")
|
||||
@Table(name="boolean_entity")
|
||||
@SoftDelete(converter = BooleanAsBooleanConverter.class)
|
||||
public static class BooleanEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="NumericEntity")
|
||||
@Table(name="numeric_entity")
|
||||
@SoftDelete(converter = NumericBooleanConverter.class)
|
||||
public static class NumericEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="TrueFalseEntity")
|
||||
@Table(name="true_false_entity")
|
||||
@SoftDelete(converter = TrueFalseConverter.class)
|
||||
public static class TrueFalseEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="YesNoEntity")
|
||||
@Table(name="yes_no_entity")
|
||||
@SoftDelete(converter = YesNoConverter.class)
|
||||
public static class YesNoEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="ReversedYesNoEntity")
|
||||
@Table(name="reversed_yes_no_entity")
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
public static class ReversedYesNoEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import org.hibernate.metamodel.mapping.SoftDeleteMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class MappingVerifier {
|
||||
public static void verifyMapping(
|
||||
SoftDeleteMapping mapping,
|
||||
String expectedColumnName,
|
||||
String expectedTableName,
|
||||
Object expectedDeletedLiteralValue) {
|
||||
assertThat( mapping ).isNotNull();
|
||||
assertThat( mapping.getColumnName() ).isEqualTo( expectedColumnName );
|
||||
assertThat( mapping.getTableName() ).isEqualTo( expectedTableName );
|
||||
assertThat( mapping.getDeletedLiteralValue() ).isEqualTo( expectedDeletedLiteralValue );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ReverseYesNoConverter implements AttributeConverter<Boolean,Character> {
|
||||
@Override
|
||||
public Character convertToDatabaseColumn(Boolean attribute) {
|
||||
return attribute ? 'N' : 'Y';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean convertToEntityAttribute(Character dbData) {
|
||||
if ( dbData == 'Y' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( dbData == 'N' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException( "Illegal value [" + dbData + "]; expecting 'Y' or 'N'" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.YesNoConverter;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = { SimpleSoftDeleteTests.SimpleEntity.class, SimpleSoftDeleteTests.BatchLoadable.class })
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class SimpleSoftDeleteTests {
|
||||
@BeforeEach
|
||||
void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.persist( new SimpleEntity( 1, "first" ) );
|
||||
session.persist( new SimpleEntity( 2, "second" ) );
|
||||
session.persist( new SimpleEntity( 3, "third" ) );
|
||||
|
||||
session.persist( new BatchLoadable( 1, "first" ) );
|
||||
session.persist( new BatchLoadable( 2, "second" ) );
|
||||
|
||||
session.flush();
|
||||
|
||||
session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "update simple set removed = 'Y' where id = 1" );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "delete from simple" );
|
||||
statement.execute( "delete from batch_loadable" );
|
||||
} ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectionQuery(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
// should not return #1
|
||||
assertThat( session.createQuery( "from SimpleEntity" ).list() ).hasSize( 2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoading(SessionFactoryScope scope) {
|
||||
// Load
|
||||
scope.inTransaction( (session) -> {
|
||||
assertThat( session.get( SimpleEntity.class, 1 ) ).isNull();
|
||||
assertThat( session.get( SimpleEntity.class, 2 ) ).isNotNull();
|
||||
assertThat( session.get( SimpleEntity.class, 3 ) ).isNotNull();
|
||||
} );
|
||||
|
||||
// Proxy
|
||||
scope.inTransaction( (session) -> {
|
||||
final SimpleEntity reference = session.getReference( SimpleEntity.class, 1 );
|
||||
try {
|
||||
reference.getName();
|
||||
fail( "Expecting to fail" );
|
||||
}
|
||||
catch (ObjectNotFoundException expected) {
|
||||
}
|
||||
|
||||
final SimpleEntity reference2 = session.getReference( SimpleEntity.class, 2 );
|
||||
reference2.getName();
|
||||
|
||||
final SimpleEntity reference3 = session.getReference( SimpleEntity.class, 3 );
|
||||
reference3.getName();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
statementInspector.clear();
|
||||
final List<SimpleEntity> results = session
|
||||
.byMultipleIds( SimpleEntity.class )
|
||||
// otherwise the first position would contain a null for #1
|
||||
.enableOrderedReturn( false )
|
||||
.multiLoad( 1, 2, 3 );
|
||||
assertThat( results ).hasSize( 2 );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "removed='N'" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNaturalIdLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
statementInspector.clear();
|
||||
session.bySimpleNaturalId( SimpleEntity.class ).load( "second" );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "removed='N'" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
statementInspector.clear();
|
||||
final BatchLoadable first = session.getReference( BatchLoadable.class, 1 );
|
||||
final BatchLoadable second = session.getReference( BatchLoadable.class, 2 );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 0 );
|
||||
|
||||
assertThat( Hibernate.isInitialized( first ) ).isFalse();
|
||||
assertThat( Hibernate.isInitialized( second ) ).isFalse();
|
||||
|
||||
// trigger load
|
||||
first.getName();
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||
|
||||
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeletion(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final SimpleEntity reference = session.getReference( SimpleEntity.class, 2 );
|
||||
session.remove( reference );
|
||||
session.flush();
|
||||
|
||||
final List<SimpleEntity> active = session
|
||||
.createSelectionQuery( "from SimpleEntity", SimpleEntity.class )
|
||||
.list();
|
||||
// #1 was "deleted" up front and we just "deleted" #2... only #3 should be active
|
||||
assertThat( active ).hasSize( 1 );
|
||||
assertThat( active.get(0).getId() ).isEqualTo( 3 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullUpdateMutationQuery(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final int affected = session.createMutationQuery( "update SimpleEntity set name = null" ).executeUpdate();
|
||||
assertThat( affected ).isEqualTo( 2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRestrictedUpdateMutationQuery(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final int affected = session
|
||||
.createMutationQuery( "update SimpleEntity set name = null where name = 'second'" )
|
||||
.executeUpdate();
|
||||
assertThat( affected ).isEqualTo( 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullDeleteMutationQuery(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
statementInspector.clear();
|
||||
final int affected = session.createMutationQuery( "delete SimpleEntity" ).executeUpdate();
|
||||
// only #2 and #3
|
||||
assertThat( affected ).isEqualTo( 2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRestrictedDeleteMutationQuery(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
statementInspector.clear();
|
||||
final int affected = session
|
||||
.createMutationQuery( "delete SimpleEntity where name = 'second'" )
|
||||
.executeUpdate();
|
||||
// only #2
|
||||
assertThat( affected ).isEqualTo( 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @implNote Uses YesNoConverter to work across all databases, even those
|
||||
* not supporting an actual BOOLEAN datatype
|
||||
*/
|
||||
@Entity(name="SimpleEntity")
|
||||
@Table(name="simple")
|
||||
@SoftDelete(columnName = "removed", converter = YesNoConverter.class)
|
||||
public static class SimpleEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
@NaturalId
|
||||
private String name;
|
||||
|
||||
public SimpleEntity() {
|
||||
}
|
||||
|
||||
public SimpleEntity(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name="BatchLoadable")
|
||||
@Table(name="batch_loadable")
|
||||
@BatchSize(size = 5)
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
public static class BatchLoadable {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public BatchLoadable() {
|
||||
}
|
||||
|
||||
public BatchLoadable(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import java.sql.Statement;
|
||||
|
||||
import org.hibernate.annotations.Fetch;
|
||||
import org.hibernate.annotations.FetchMode;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = { ToOneTests.Issue.class, ToOneTests.User.class } )
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class ToOneTests {
|
||||
@BeforeEach
|
||||
void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final User steve = new User( 1, "Steve" );
|
||||
final User john = new User( 2, "John" );
|
||||
final User jacob = new User( 3, "Jacob" );
|
||||
final User bobby = new User( 4, "Bobby" );
|
||||
session.persist( steve );
|
||||
session.persist( john );
|
||||
session.persist( jacob );
|
||||
session.persist( bobby );
|
||||
|
||||
final Issue first = new Issue( 1, "first", jacob, steve );
|
||||
final Issue second = new Issue( 2, "second", bobby, steve );
|
||||
final Issue third = new Issue( 3, "third", jacob, john );
|
||||
session.persist( first );
|
||||
session.persist( second );
|
||||
session.persist( third );
|
||||
|
||||
// soft-delete John and Bobby
|
||||
session.createMutationQuery( "delete User where id in (2,4)" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "delete from issues" );
|
||||
statement.execute( "delete from users" );
|
||||
} ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicBaselineTest(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final Issue issue1 = session.get( Issue.class, 1 );
|
||||
assertThat( issue1 ).isNotNull();
|
||||
assertThat( issue1.reporter ).isNotNull();
|
||||
assertThat( issue1.assignee ).isNotNull();
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 2 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".reporter_fk" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( ".active='Y" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsOnlyOnce( "active" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).contains( ".active='Y" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 1 ) ).containsOnlyOnce( "active" );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicJoinedTest(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Issue issue2 = session.get( Issue.class, 2 );
|
||||
assertThat( issue2 ).isNotNull();
|
||||
assertThat( issue2.reporter ).isNull();
|
||||
assertThat( issue2.assignee ).isNotNull();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicSelectedTest(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final Issue issue3 = session.get( Issue.class, 3 );
|
||||
assertThat( issue3 ).isNotNull();
|
||||
assertThat( issue3.reporter ).isNotNull();
|
||||
assertThat( issue3.assignee ).isNull();
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity(name="Issue")
|
||||
@Table(name="issues")
|
||||
public static class Issue {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String description;
|
||||
@ManyToOne
|
||||
@JoinColumn(name="reporter_fk")
|
||||
@Fetch( FetchMode.JOIN )
|
||||
private User reporter;
|
||||
@ManyToOne
|
||||
@JoinColumn(name="assignee_fk")
|
||||
@Fetch( FetchMode.SELECT )
|
||||
private User assignee;
|
||||
|
||||
public Issue() {
|
||||
}
|
||||
|
||||
public Issue(Integer id, String description, User reporter) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
public Issue(Integer id, String description, User reporter, User assignee) {
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.reporter = reporter;
|
||||
this.assignee = assignee;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name="User")
|
||||
@Table(name="users")
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
public static class User {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ValidationTests {
|
||||
@Test
|
||||
void testLazyToOne() {
|
||||
final Metadata metadata = new MetadataSources().addAnnotatedClass( Person.class )
|
||||
.addAnnotatedClass( Address.class )
|
||||
.buildMetadata();
|
||||
try (SessionFactory sessionFactory = metadata.buildSessionFactory()) {
|
||||
fail( "Expecting a failure" );
|
||||
}
|
||||
catch (UnsupportedMappingException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomSql() {
|
||||
final Metadata metadata = new MetadataSources().addAnnotatedClass( NoNo.class )
|
||||
.buildMetadata();
|
||||
try (SessionFactory sessionFactory = metadata.buildSessionFactory()) {
|
||||
fail( "Expecting a failure" );
|
||||
}
|
||||
catch (UnsupportedMappingException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name="Person")
|
||||
@Table(name="persons")
|
||||
public static class Person {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name="address_fk")
|
||||
private Address address;
|
||||
}
|
||||
|
||||
@Entity(name="Address")
|
||||
@Table(name="addresses")
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
public static class Address {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="NoNo")
|
||||
@Table(name="nonos")
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
@SQLDelete( sql = "delete from nonos" )
|
||||
public static class NoNo {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "coll_owned")
|
||||
public class CollectionOwned {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
public CollectionOwned() {
|
||||
}
|
||||
|
||||
public CollectionOwned(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.NumericBooleanConverter;
|
||||
import org.hibernate.type.YesNoConverter;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "coll_owner")
|
||||
public class CollectionOwner {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "elements", joinColumns = @JoinColumn(name = "owner_fk"))
|
||||
@Column(name = "txt")
|
||||
@SoftDelete(converter = YesNoConverter.class)
|
||||
private Collection<String> elements;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "m2m",
|
||||
joinColumns = @JoinColumn(name = "owner_fk"),
|
||||
inverseJoinColumns = @JoinColumn(name = "owned_fk")
|
||||
)
|
||||
@SoftDelete(columnName = "gone", converter = NumericBooleanConverter.class)
|
||||
private Collection<CollectionOwned> manyToMany;
|
||||
|
||||
protected CollectionOwner() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public CollectionOwner(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Collection<String> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public void setElements(Collection<String> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public void addElement(String element) {
|
||||
if ( elements == null ) {
|
||||
elements = new ArrayList<>();
|
||||
}
|
||||
elements.add( element );
|
||||
}
|
||||
|
||||
public Collection<CollectionOwned> getManyToMany() {
|
||||
return manyToMany;
|
||||
}
|
||||
|
||||
public void setManyToMany(Collection<CollectionOwned> manyToMany) {
|
||||
this.manyToMany = manyToMany;
|
||||
}
|
||||
|
||||
public void addManyToMany(CollectionOwned element) {
|
||||
if ( manyToMany == null ) {
|
||||
manyToMany = new ArrayList<>();
|
||||
}
|
||||
manyToMany.add( element );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.Fetch;
|
||||
import org.hibernate.annotations.FetchMode;
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "coll_owner2")
|
||||
public class CollectionOwner2 {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name="batch_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||
@BatchSize(size = 5)
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
private Set<String> batchLoadable;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name="subselect_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
private Set<String> subSelectLoadable;
|
||||
|
||||
public CollectionOwner2() {
|
||||
}
|
||||
|
||||
public CollectionOwner2(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Set<String> getBatchLoadable() {
|
||||
return batchLoadable;
|
||||
}
|
||||
|
||||
public void setBatchLoadable(Set<String> batchLoadable) {
|
||||
this.batchLoadable = batchLoadable;
|
||||
}
|
||||
|
||||
public Set<String> getSubSelectLoadable() {
|
||||
return subSelectLoadable;
|
||||
}
|
||||
|
||||
public void setSubSelectLoadable(Set<String> subSelectLoadable) {
|
||||
this.subSelectLoadable = subSelectLoadable;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = CollectionOwner2.class)
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class FetchLoadableTests {
|
||||
@BeforeEach
|
||||
void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner2 owner = new CollectionOwner2( 1, "first" );
|
||||
final CollectionOwner2 owner2 = new CollectionOwner2( 2, "second" );
|
||||
|
||||
owner.setBatchLoadable( new HashSet<>() );
|
||||
owner.getBatchLoadable().add( "batchable1" );
|
||||
owner.getBatchLoadable().add( "batchable2" );
|
||||
owner.getBatchLoadable().add( "batchable3" );
|
||||
|
||||
owner.setSubSelectLoadable( new HashSet<>() );
|
||||
owner.getSubSelectLoadable().add( "subselectable1" );
|
||||
owner.getSubSelectLoadable().add( "subselectable2" );
|
||||
owner.getSubSelectLoadable().add( "subselectable3" );
|
||||
|
||||
owner2.setBatchLoadable( new HashSet<>() );
|
||||
owner2.getBatchLoadable().add( "batchable21" );
|
||||
owner2.getBatchLoadable().add( "batchable22" );
|
||||
owner2.getBatchLoadable().add( "batchable23" );
|
||||
|
||||
owner2.setSubSelectLoadable( new HashSet<>() );
|
||||
owner2.getSubSelectLoadable().add( "subselectable21" );
|
||||
owner2.getSubSelectLoadable().add( "subselectable22" );
|
||||
owner2.getSubSelectLoadable().add( "subselectable23" );
|
||||
|
||||
session.persist( owner );
|
||||
session.persist( owner2 );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "delete from batch_loadables" );
|
||||
statement.execute( "delete from subselect_loadables" );
|
||||
statement.execute( "delete from coll_owner2" );
|
||||
} ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<CollectionOwner2> result = session.createQuery(
|
||||
"from CollectionOwner2 order",
|
||||
CollectionOwner2.class
|
||||
).list();
|
||||
|
||||
final CollectionOwner2 first = result.get( 0 );
|
||||
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( first.getBatchLoadable() ) ).isFalse();
|
||||
|
||||
final CollectionOwner2 second = result.get( 1 );
|
||||
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( second.getBatchLoadable() ) ).isFalse();
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// trigger loading one of the batch-loadable collections
|
||||
first.getBatchLoadable().size();
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||
assertThat( Hibernate.isInitialized( first.getBatchLoadable() ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( second.getBatchLoadable() ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubSelectLoading(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final List<CollectionOwner2> result = session.createQuery(
|
||||
"from CollectionOwner2 order",
|
||||
CollectionOwner2.class
|
||||
).list();
|
||||
|
||||
final CollectionOwner2 first = result.get( 0 );
|
||||
assertThat( Hibernate.isInitialized( first ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( first.getSubSelectLoadable() ) ).isFalse();
|
||||
|
||||
final CollectionOwner2 second = result.get( 1 );
|
||||
assertThat( Hibernate.isInitialized( second ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( second.getSubSelectLoadable() ) ).isFalse();
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// trigger loading one of the subselect-loadable collections
|
||||
first.getSubSelectLoadable().size();
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "active='Y'" );
|
||||
assertThat( Hibernate.isInitialized( first.getSubSelectLoadable() ) ).isTrue();
|
||||
assertThat( Hibernate.isInitialized( second.getSubSelectLoadable() ) ).isTrue();
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.NumericBooleanConverter;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "invalid_coll_owner")
|
||||
public class InvalidCollectionOwner {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
@OneToMany
|
||||
@JoinColumn(name="owned_fk")
|
||||
@SoftDelete(columnName = "gone", converter = NumericBooleanConverter.class)
|
||||
private Collection<CollectionOwned> oneToMany;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = { CollectionOwner.class, CollectionOwned.class })
|
||||
@SessionFactory(exportSchema = false)
|
||||
@SuppressWarnings("unused")
|
||||
public class MappingTests {
|
||||
|
||||
@Test
|
||||
void verifyCollectionMappings(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getCollectionDescriptor( CollectionOwner.class.getName() + ".elements" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"elements",
|
||||
'Y'
|
||||
);
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getCollectionDescriptor( CollectionOwner.class.getName() + ".manyToMany" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||
"gone",
|
||||
"m2m",
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import java.sql.Statement;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = { CollectionOwner.class, CollectionOwned.class })
|
||||
@SessionFactory(useCollectingStatementInspector = true)
|
||||
public class UsageTests {
|
||||
@BeforeEach
|
||||
void createTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwned owned = new CollectionOwned( 1, "owned" );
|
||||
session.persist( owned );
|
||||
|
||||
final CollectionOwner owner = new CollectionOwner( 1, "owner" );
|
||||
owner.addElement( "an element" );
|
||||
owner.addElement( "another element" );
|
||||
owner.addManyToMany( owned );
|
||||
session.persist( owner );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void dropTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> session.doWork( (connection) -> {
|
||||
final Statement statement = connection.createStatement();
|
||||
statement.execute( "delete from m2m" );
|
||||
statement.execute( "delete from elements" );
|
||||
statement.execute( "delete from coll_owned" );
|
||||
statement.execute( "delete from coll_owner" );
|
||||
} ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHqlElements(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
} );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='N'" );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHqlManyToMany(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
} );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=0" );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveElements(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
statementInspector.clear();
|
||||
owner.getElements().clear();
|
||||
} );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "deleted='N'" );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||
assertThat( owner.getElements() ).hasSize( 0 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveManyToMany(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
statementInspector.clear();
|
||||
owner.getManyToMany().clear();
|
||||
} );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=1" );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "gone=0" );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||
statementInspector.clear();
|
||||
assertThat( owner.getManyToMany() ).hasSize( 0 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteElement(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.elements where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
statementInspector.clear();
|
||||
owner.getElements().remove( "an element" );
|
||||
} );
|
||||
// this will be a "recreate"
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "deleted='N'" );
|
||||
assertThat( statementInspector.getSqlQueries().get( 1 ) ).startsWith( "insert " );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||
assertThat( owner.getElements() ).hasSize( 1 );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteManyToMany(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwned owned = session.get( CollectionOwned.class, 1 );
|
||||
final CollectionOwner owner = session
|
||||
.createQuery( "select o from CollectionOwner o join fetch o.manyToMany where o.id = 1", CollectionOwner.class )
|
||||
.uniqueResult();
|
||||
statementInspector.clear();
|
||||
owner.getManyToMany().remove( owned );
|
||||
} );
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).startsWith( "update " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( "gone=1" );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).endsWith( "gone=0" );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final CollectionOwner owner = session.get( CollectionOwner.class, 1 );
|
||||
assertThat( owner.getManyToMany() ).hasSize( 0 );
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.collections;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.Metadata;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ValidationTests {
|
||||
@Test
|
||||
void testOneToMany() {
|
||||
try {
|
||||
final Metadata metadata = new MetadataSources()
|
||||
.addAnnotatedClass( InvalidCollectionOwner.class )
|
||||
.addAnnotatedClass( CollectionOwned.class )
|
||||
.buildMetadata();
|
||||
}
|
||||
catch (UnsupportedMappingException expected) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.converter;
|
||||
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = TheEntity.class)
|
||||
@SessionFactory( useCollectingStatementInspector = true)
|
||||
public class ConvertedSoftDeleteTests {
|
||||
@Test
|
||||
void verifySchema(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( TheEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"the_entity",
|
||||
'Y'
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUsage(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
session.persist( new TheEntity( 1, "it" ) );
|
||||
} );
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "'N'" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( "'Y'" );
|
||||
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TheEntity reference = session.getReference( TheEntity.class, 1 );
|
||||
session.remove( reference );
|
||||
} );
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContainIgnoringCase( "delete " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsIgnoringCase( "update " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "deleted='Y'" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.converter;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.type.YesNoConverter;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "the_entity")
|
||||
@SoftDelete(converter = YesNoConverter.class)
|
||||
public class TheEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
protected TheEntity() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public TheEntity(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests applying a custom {@linkplain org.hibernate.annotations.SoftDelete#converter() converter}
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.converter;
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel(annotatedClasses = { ReverseYesNoConverter.class, TheEntity.class })
|
||||
@SessionFactory( useCollectingStatementInspector = true)
|
||||
public class ReversedSoftDeleteTests {
|
||||
@Test
|
||||
void verifySchema(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( TheEntity.class ).getSoftDeleteMapping(),
|
||||
"active",
|
||||
"the_entity",
|
||||
'N'
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUsage(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector sqlInspector = scope.getCollectingStatementInspector();
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
session.persist( new TheEntity( 1, "it" ) );
|
||||
} );
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "'Y'" );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContain( "'N'" );
|
||||
|
||||
sqlInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TheEntity reference = session.getReference( TheEntity.class, 1 );
|
||||
session.remove( reference );
|
||||
} );
|
||||
|
||||
assertThat( sqlInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).doesNotContainIgnoringCase( "delete " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).containsIgnoringCase( "update " );
|
||||
assertThat( sqlInspector.getSqlQueries().get( 0 ) ).contains( "active='N'" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||
|
||||
import org.hibernate.annotations.SoftDelete;
|
||||
import org.hibernate.orm.test.softdelete.ReverseYesNoConverter;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "the_entity")
|
||||
@SoftDelete(columnName = "active", converter = ReverseYesNoConverter.class)
|
||||
public class TheEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
protected TheEntity() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public TheEntity(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for {@link org.hibernate.annotations.SoftDelete} and {@link org.hibernate.annotations.SoftDeleteColumn}
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete;
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.pkg;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "the_table")
|
||||
public class AnEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
@Basic
|
||||
private String name;
|
||||
@ElementCollection
|
||||
@CollectionTable(name="elements", joinColumns = @JoinColumn(name = "owner_fk"))
|
||||
@Column(name="txt")
|
||||
private Collection<String> elements;
|
||||
|
||||
protected AnEntity() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public AnEntity(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.orm.test.softdelete.pkg;
|
||||
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.orm.test.softdelete.MappingVerifier;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.NotImplementedYet;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = AnEntity.class )
|
||||
@SessionFactory
|
||||
public class PackageLevelSoftDeleteTests {
|
||||
@Test
|
||||
public void verifyEntitySchema(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getEntityDescriptor( AnEntity.class ).getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"the_table",
|
||||
'Y'
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyCollectionSchema(SessionFactoryScope scope) {
|
||||
final MappingMetamodelImplementor metamodel = scope.getSessionFactory().getMappingMetamodel();
|
||||
MappingVerifier.verifyMapping(
|
||||
metamodel.getCollectionDescriptor( AnEntity.class.getName() + ".elements" ).getAttributeMapping().getSoftDeleteMapping(),
|
||||
"deleted",
|
||||
"elements",
|
||||
'Y'
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue