Some reflection optimizer related fixes

This commit is contained in:
Christian Beikov 2022-11-09 01:10:31 +01:00
parent 2a93aa5467
commit 4fdbb3d5f6
12 changed files with 389 additions and 200 deletions

View File

@ -1,34 +0,0 @@
/*
* 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.bytecode.internal.bytebuddy;
import org.hibernate.HibernateException;
public class BulkAccessorException extends HibernateException {
private final int index;
public BulkAccessorException(String message) {
this( message, -1 );
}
public BulkAccessorException(String message, int index) {
this( message, index, null );
}
public BulkAccessorException(String message, Exception cause) {
this( message, -1, cause );
}
public BulkAccessorException(String message, int index, Exception cause) {
super( message + " : @" + index, cause );
this.index = index;
}
public int getIndex() {
return this.index;
}
}

View File

@ -18,10 +18,14 @@ import org.hibernate.bytecode.enhance.spi.Enhancer;
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.bytecode.spi.ProxyFactoryFactory;
import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
@ -39,12 +43,17 @@ import net.bytebuddy.matcher.ElementMatchers;
public class BytecodeProviderImpl implements BytecodeProvider {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( BytecodeProviderImpl.class );
private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator";
private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer";
private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" );
private static final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( "getPropertyValues" );
private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" );
private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" );
private static final ElementMatcher.Junction<NamedElement> newInstanceMethodName = ElementMatchers.named(
"newInstance" );
private static final ElementMatcher.Junction<NamedElement> getPropertyValuesMethodName = ElementMatchers.named(
"getPropertyValues" );
private static final ElementMatcher.Junction<NamedElement> setPropertyValuesMethodName = ElementMatchers.named(
"setPropertyValues" );
private static final ElementMatcher.Junction<NamedElement> getPropertyNamesMethodName = ElementMatchers.named(
"getPropertyNames" );
private final ByteBuddyState byteBuddyState;
@ -78,18 +87,27 @@ public class BytecodeProviderImpl implements BytecodeProvider {
final String[] getterNames,
final String[] setterNames,
final Class[] types) {
final Class fastClass;
final Class<?> fastClass;
if ( !clazz.isInterface() && !Modifier.isAbstract( clazz.getModifiers() ) ) {
// we only provide a fast class instantiator if the class can be instantiated
final Constructor<?> constructor = findConstructor( clazz );
fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
.with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX,
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
.method( newInstanceMethodName )
.intercept( MethodCall.construct( constructor ) )
);
if ( constructor == null || Modifier.isPrivate( constructor.getModifiers() ) ) {
// In the current implementation of the ReflectionOptimizer contract, we can't call private constructors
// To support that, we have to inject a static factory method into the class during enhancement
fastClass = null;
}
else {
fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
.with( new NamingStrategy.SuffixingRandom(
INSTANTIATOR_PROXY_NAMING_SUFFIX,
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
) )
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
.method( newInstanceMethodName )
.intercept( MethodCall.construct( constructor ) )
);
}
}
else {
fastClass = null;
@ -97,18 +115,26 @@ public class BytecodeProviderImpl implements BytecodeProvider {
final Method[] getters = new Method[getterNames.length];
final Method[] setters = new Method[setterNames.length];
findAccessors( clazz, getterNames, setterNames, types, getters, setters );
try {
findAccessors( clazz, getterNames, setterNames, types, getters, setters );
}
catch (PrivateAccessorException ex) {
LOG.unableToGenerateReflectionOptimizer( clazz.getName(), ex );
return null;
}
final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
.with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX,
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
final Class<?> bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
.with( new NamingStrategy.SuffixingRandom(
OPTIMIZER_PROXY_NAMING_SUFFIX,
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() )
) )
.subclass( ReflectionOptimizer.AccessOptimizer.class )
.method( getPropertyValuesMethodName )
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
.method( setPropertyValuesMethodName )
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
.method( getPropertyNamesMethodName )
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
);
try {
@ -128,11 +154,11 @@ public class BytecodeProviderImpl implements BytecodeProvider {
private static class GetPropertyValues implements ByteCodeAppender {
private final Class clazz;
private final Class<?> clazz;
private final Method[] getters;
public GetPropertyValues(Class clazz, Method[] getters) {
public GetPropertyValues(Class<?> clazz, Method[] getters) {
this.clazz = clazz;
this.getters = getters;
}
@ -151,11 +177,11 @@ public class BytecodeProviderImpl implements BytecodeProvider {
methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 );
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
Type.getInternalName( clazz ),
getter.getDeclaringClass().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
Type.getInternalName( getter.getDeclaringClass() ),
getter.getName(),
Type.getMethodDescriptor( getter ),
false
getter.getDeclaringClass().isInterface()
);
if ( getter.getReturnType().isPrimitive() ) {
PrimitiveBoxingDelegate.forPrimitive( new TypeDescription.ForLoadedType( getter.getReturnType() ) )
@ -175,11 +201,11 @@ public class BytecodeProviderImpl implements BytecodeProvider {
private static class SetPropertyValues implements ByteCodeAppender {
private final Class clazz;
private final Class<?> clazz;
private final Method[] setters;
public SetPropertyValues(Class clazz, Method[] setters) {
public SetPropertyValues(Class<?> clazz, Method[] setters) {
this.clazz = clazz;
this.setters = setters;
}
@ -206,15 +232,30 @@ public class BytecodeProviderImpl implements BytecodeProvider {
.apply( methodVisitor, implementationContext );
}
else {
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( setter.getParameterTypes()[0] ) );
methodVisitor.visitTypeInsn(
Opcodes.CHECKCAST,
Type.getInternalName( setter.getParameterTypes()[0] )
);
}
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
Type.getInternalName( clazz ),
setter.getDeclaringClass().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
Type.getInternalName( setter.getDeclaringClass() ),
setter.getName(),
Type.getMethodDescriptor( setter ),
false
setter.getDeclaringClass().isInterface()
);
if ( setter.getReturnType() != void.class ) {
// Setters could return something which we have to ignore
switch ( setter.getReturnType().getTypeName() ) {
case "long":
case "double":
methodVisitor.visitInsn( Opcodes.POP2 );
break;
default:
methodVisitor.visitInsn( Opcodes.POP );
break;
}
}
}
methodVisitor.visitInsn( Opcodes.RETURN );
return new Size( 4, instrumentedMethod.getStackSize() );
@ -222,58 +263,84 @@ public class BytecodeProviderImpl implements BytecodeProvider {
}
private static void findAccessors(
Class clazz,
Class<?> clazz,
String[] getterNames,
String[] setterNames,
Class[] types,
Class<?>[] types,
Method[] getters,
Method[] setters) {
final int length = types.length;
if ( setterNames.length != length || getterNames.length != length ) {
throw new BulkAccessorException( "bad number of accessors" );
throw new HibernateException( "bad number of accessors" );
}
final Class[] getParam = new Class[0];
final Class[] setParam = new Class[1];
final Class<?>[] getParam = new Class[0];
final Class<?>[] setParam = new Class[1];
for ( int i = 0; i < length; i++ ) {
if ( getterNames[i] != null ) {
final Method getter = findAccessor( clazz, getterNames[i], getParam, i );
final Method getter = findAccessor( clazz, getterNames[i], getParam );
if ( getter.getReturnType() != types[i] ) {
throw new BulkAccessorException( "wrong return type: " + getterNames[i], i );
throw new HibernateException( "wrong return type: " + getterNames[i] );
}
getters[i] = getter;
}
if ( setterNames[i] != null ) {
setParam[0] = types[i];
setters[i] = findAccessor( clazz, setterNames[i], setParam, i );
setters[i] = ReflectHelper.setterMethodOrNullBySetterName( clazz, setterNames[i], types[i] );
if ( setters[i] == null ) {
throw new HibernateException(
String.format(
"cannot find an accessor [%s] on type [%s]",
setterNames[i],
clazz.getName()
)
);
}
else if ( Modifier.isPrivate( setters[i].getModifiers() ) ) {
throw new PrivateAccessorException( "private accessor [" + setterNames[i] + "]" );
}
}
}
}
@SuppressWarnings("unchecked")
private static Method findAccessor(Class clazz, String name, Class[] params, int index)
throws BulkAccessorException {
private static Method findAccessor(Class<?> containerClazz, String name, Class<?>[] params)
throws PrivateAccessorException {
Class<?> clazz = containerClazz;
try {
final Method method = clazz.getDeclaredMethod( name, params );
if ( Modifier.isPrivate( method.getModifiers() ) ) {
throw new BulkAccessorException( "private property", index );
}
return method;
return clazz.getMethod( name, params );
}
catch (NoSuchMethodException e) {
throw new BulkAccessorException( "cannot find an accessor", index );
// Ignore if we didn't find a public method
}
do {
try {
final Method method = clazz.getDeclaredMethod( name, params );
if ( Modifier.isPrivate( method.getModifiers() ) ) {
throw new PrivateAccessorException( "private accessor [" + name + "]" );
}
return method;
}
catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
}
} while ( clazz != null );
throw new HibernateException(
String.format(
"cannot find an accessor [%s] on type [%s]",
name,
containerClazz.getName()
)
);
}
private static Constructor<?> findConstructor(Class clazz) {
private static Constructor<?> findConstructor(Class<?> clazz) {
try {
return clazz.getDeclaredConstructor();
}
catch (NoSuchMethodException e) {
throw new HibernateException( e );
return null;
}
}

View File

@ -0,0 +1,16 @@
/*
* 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.bytecode.internal.bytebuddy;
import org.hibernate.HibernateException;
public class PrivateAccessorException extends HibernateException {
public PrivateAccessorException(String message) {
super( message );
}
}

View File

@ -1798,4 +1798,9 @@ public interface CoreMessageLogger extends BasicLogger {
@Message(value = "The database version version for the Cockroach Dialect could not be determined. The minimum supported version (%s) has been set instead.", id = 512)
void unableToDetermineCockroachDatabaseVersion(String minimumVersion);
@LogMessage(level = WARN)
@Message(value = "Unable to create the ReflectionOptimizer for [%s]",
id = 513)
void unableToGenerateReflectionOptimizer(String className, @Cause Throwable cause);
}

View File

@ -662,6 +662,60 @@ public final class ReflectHelper {
return setter; // might be null
}
public static Method setterMethodOrNullBySetterName(final Class containerClass, final String setterName, final Class propertyType) {
Class checkClass = containerClass;
Method setter = null;
// check containerClass, and then its super types (if any)
while ( setter == null && checkClass != null ) {
if ( checkClass.equals( Object.class ) ) {
break;
}
setter = setterOrNullBySetterName( checkClass, setterName, propertyType );
// if no setter found yet, check all implemented interfaces
if ( setter == null ) {
setter = setterOrNullBySetterName( checkClass.getInterfaces(), setterName, propertyType );
}
else {
ensureAccessibility( setter );
}
checkClass = checkClass.getSuperclass();
}
return setter; // might be null
}
private static Method setterOrNullBySetterName(Class[] interfaces, String setterName, Class propertyType) {
Method setter = null;
for ( int i = 0; setter == null && i < interfaces.length; ++i ) {
final Class anInterface = interfaces[i];
setter = setterOrNullBySetterName( anInterface, setterName, propertyType );
if ( setter == null ) {
// if no setter found yet, check all implemented interfaces of interface
setter = setterOrNullBySetterName( anInterface.getInterfaces(), setterName, propertyType );
}
}
return setter;
}
private static Method setterOrNullBySetterName(Class theClass, String setterName, Class propertyType) {
Method potentialSetter = null;
for ( Method method : theClass.getDeclaredMethods() ) {
final String methodName = method.getName();
if ( method.getParameterCount() == 1 && methodName.equals( setterName ) ) {
potentialSetter = method;
if ( propertyType == null || method.getParameterTypes()[0].equals( propertyType ) ) {
break;
}
}
}
return potentialSetter;
}
public static Method findSetterMethod(final Class containerClass, final String propertyName, final Class propertyType) {
final Method setter = setterMethodOrNull( containerClass, propertyName, propertyType );
if ( setter == null ) {

View File

@ -36,7 +36,10 @@ public class EmbeddableInstantiatorPojoOptimized extends AbstractPojoInstantiato
public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
final Object embeddable = instantiationOptimizer.newInstance();
final EmbeddableMappingType embeddableMapping = embeddableMappingAccess.get();
embeddableMapping.setValues( embeddable, valuesAccess.getValues() );
final Object[] values = valuesAccess.getValues();
if ( values != null ) {
embeddableMapping.setValues( embeddable, values );
}
return embeddable;
}
}

View File

@ -184,7 +184,7 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
return null;
}
if ( hasCustomAccessors() ) {
if ( hasCustomAccessors() || bootDescriptor.getCustomInstantiator() != null ) {
return null;
}

View File

@ -294,15 +294,6 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent
PersistentClass bootType,
BytecodeProvider bytecodeProvider,
SessionFactoryImplementor sessionFactory) {
final Class<?> javaTypeToReflect;
if ( proxyFactory != null ) {
assert proxyJtd != null;
javaTypeToReflect = proxyJtd.getJavaTypeClass();
}
else {
javaTypeToReflect = mappedJtd.getJavaTypeClass();
}
final List<String> getterNames = new ArrayList<>();
final List<String> setterNames = new ArrayList<>();
final List<Class<?>> getterTypes = new ArrayList<>();
@ -330,7 +321,7 @@ public class EntityRepresentationStrategyPojoStandard implements EntityRepresent
}
return bytecodeProvider.getReflectionOptimizer(
javaTypeToReflect,
mappedJtd.getJavaTypeClass(),
getterNames.toArray( EMPTY_STRING_ARRAY ),
setterNames.toArray( EMPTY_STRING_ARRAY ),
getterTypes.toArray( EMPTY_CLASS_ARRAY )

View File

@ -7,6 +7,7 @@
package org.hibernate.query.sqm.mutation.internal;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.jdbc.spi.JdbcServices;
@ -14,6 +15,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.RootClass;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
@ -87,6 +89,48 @@ public class SqmMutationStrategyHelper {
.getFallbackSqmInsertStrategy( rootEntityDescriptor, creationContext );
}
public static void visitCollectionTables(
EntityMappingType entityDescriptor,
Consumer<PluralAttributeMapping> consumer) {
if ( ! entityDescriptor.getEntityPersister().hasCollections() ) {
// none to clean-up
return;
}
entityDescriptor.visitSubTypeAttributeMappings(
attributeMapping -> {
if ( attributeMapping instanceof PluralAttributeMapping ) {
consumer.accept( (PluralAttributeMapping) attributeMapping );
}
else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
visitCollectionTables(
(EmbeddedAttributeMapping) attributeMapping,
consumer
);
}
}
);
}
private static void visitCollectionTables(
EmbeddedAttributeMapping attributeMapping,
Consumer<PluralAttributeMapping> consumer) {
attributeMapping.visitSubParts(
modelPart -> {
if ( modelPart instanceof PluralAttributeMapping ) {
consumer.accept( (PluralAttributeMapping) modelPart );
}
else if ( modelPart instanceof EmbeddedAttributeMapping ) {
visitCollectionTables(
(EmbeddedAttributeMapping) modelPart,
consumer
);
}
},
null
);
}
public static void cleanUpCollectionTables(
EntityMappingType entityDescriptor,
BiFunction<TableReference, PluralAttributeMapping, Predicate> restrictionProducer,
@ -108,10 +152,50 @@ public class SqmMutationStrategyHelper {
executionContext
);
}
else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
cleanUpCollectionTables(
(EmbeddedAttributeMapping) attributeMapping,
entityDescriptor,
restrictionProducer,
jdbcParameterBindings,
executionContext
);
}
}
);
}
private static void cleanUpCollectionTables(
EmbeddedAttributeMapping attributeMapping,
EntityMappingType entityDescriptor,
BiFunction<TableReference, PluralAttributeMapping, Predicate> restrictionProducer,
JdbcParameterBindings jdbcParameterBindings,
ExecutionContext executionContext) {
attributeMapping.visitSubParts(
modelPart -> {
if ( modelPart instanceof PluralAttributeMapping ) {
cleanUpCollectionTable(
(PluralAttributeMapping) modelPart,
entityDescriptor,
restrictionProducer,
jdbcParameterBindings,
executionContext
);
}
else if ( modelPart instanceof EmbeddedAttributeMapping ) {
cleanUpCollectionTables(
(EmbeddedAttributeMapping) modelPart,
entityDescriptor,
restrictionProducer,
jdbcParameterBindings,
executionContext
);
}
},
null
);
}
private static void cleanUpCollectionTable(
PluralAttributeMapping attributeMapping,
EntityMappingType entityDescriptor,

View File

@ -14,10 +14,12 @@ import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -70,76 +72,77 @@ public class CteDeleteHandler extends AbstractCteMutationHandler implements Dele
false
)
);
getEntityDescriptor().visitSubTypeAttributeMappings(
attribute -> {
if ( attribute instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute;
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
// Ensure that the FK target columns are available
final boolean useFkTarget = !( pluralAttribute.getKeyDescriptor()
.getTargetPart() instanceof EntityIdentifierMapping );
if ( useFkTarget ) {
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections(
mutatingTableGroup.getNavigablePath(),
mutatingTableGroup,
sqmConverter,
(selection, jdbcMapping) -> {
idSelectStatement.getDomainResultDescriptors().add(
new BasicResult<>(
selection.getValuesArrayPosition(),
null,
jdbcMapping
)
);
}
);
}
// this collection has a separate collection table, meaning it is one of:
// 1) element-collection
// 2) many-to-many
// 3) one-to many using a dedicated join-table
//
// in all of these cases, we should clean up the matching rows in the
// collection table
final String tableExpression = pluralAttribute.getSeparateCollectionTable();
final CteTable dmlResultCte = new CteTable(
getCteTableName( pluralAttribute ),
idSelectCte.getCteTable().getCteColumns()
SqmMutationStrategyHelper.visitCollectionTables(
(EntityMappingType) updatingTableGroup.getModelPart(),
pluralAttribute -> {
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
// Ensure that the FK target columns are available
final boolean useFkTarget = !( pluralAttribute.getKeyDescriptor()
.getTargetPart() instanceof EntityIdentifierMapping );
if ( useFkTarget ) {
final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup();
pluralAttribute.getKeyDescriptor().getTargetPart().applySqlSelections(
mutatingTableGroup.getNavigablePath(),
mutatingTableGroup,
sqmConverter,
(selection, jdbcMapping) -> {
idSelectStatement.getDomainResultDescriptors().add(
new BasicResult<>(
selection.getValuesArrayPosition(),
null,
jdbcMapping
)
);
}
);
final NamedTableReference dmlTableReference = new NamedTableReference(
tableExpression,
DeleteStatement.DEFAULT_ALIAS,
true,
factory
);
final List<ColumnReference> columnReferences = new ArrayList<>( idSelectCte.getCteTable().getCteColumns().size() );
pluralAttribute.getKeyDescriptor().visitKeySelectables(
(index, selectable) -> columnReferences.add(
new ColumnReference(
dmlTableReference,
selectable,
factory
)
)
);
final MutationStatement dmlStatement = new DeleteStatement(
dmlTableReference,
createIdSubQueryPredicate(
columnReferences,
idSelectCte,
useFkTarget ? pluralAttribute.getKeyDescriptor().getTargetPart() : null,
factory
),
columnReferences
);
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
}
// this collection has a separate collection table, meaning it is one of:
// 1) element-collection
// 2) many-to-many
// 3) one-to many using a dedicated join-table
//
// in all of these cases, we should clean up the matching rows in the
// collection table
final String tableExpression = pluralAttribute.getSeparateCollectionTable();
final CteTable dmlResultCte = new CteTable(
getCteTableName( pluralAttribute ),
idSelectCte.getCteTable().getCteColumns()
);
final NamedTableReference dmlTableReference = new NamedTableReference(
tableExpression,
DeleteStatement.DEFAULT_ALIAS,
true,
factory
);
final List<ColumnReference> columnReferences = new ArrayList<>( idSelectCte.getCteTable()
.getCteColumns()
.size() );
pluralAttribute.getKeyDescriptor().visitKeySelectables(
(index, selectable) -> columnReferences.add(
new ColumnReference(
dmlTableReference,
selectable,
factory
)
)
);
final MutationStatement dmlStatement = new DeleteStatement(
dmlTableReference,
createIdSubQueryPredicate(
columnReferences,
idSelectCte,
useFkTarget ? pluralAttribute.getKeyDescriptor().getTargetPart() : null,
factory
),
columnReferences
);
statement.addCteStatement( new CteStatement( dmlResultCte, dmlStatement ) );
}
}
);
sqmConverter.getProcessingStateStack().pop();
getEntityDescriptor().visitConstraintOrderedTables(

View File

@ -23,6 +23,7 @@ import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter;
import org.hibernate.query.sqm.mutation.internal.DeleteHandler;
import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
@ -90,43 +91,40 @@ public class InlineDeleteHandler implements DeleteHandler {
// delete from the tables
final MutableInteger valueIndexCounter = new MutableInteger();
entityDescriptor.visitSubTypeAttributeMappings(
attribute -> {
if ( attribute instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttribute = (PluralAttributeMapping) attribute;
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
// this collection has a separate collection table, meaning it is one of:
// 1) element-collection
// 2) many-to-many
// 3) one-to many using a dedicated join-table
//
// in all of these cases, we should clean up the matching rows in the
// collection table
final ModelPart fkTargetPart = pluralAttribute.getKeyDescriptor().getTargetPart();
final int valueIndex;
if ( fkTargetPart instanceof EntityIdentifierMapping ) {
valueIndex = 0;
}
else {
if ( valueIndexCounter.get() == 0 ) {
valueIndexCounter.set( entityDescriptor.getIdentifierMapping().getJdbcTypeCount() );
}
valueIndex = valueIndexCounter.get();
valueIndexCounter.plus( fkTargetPart.getJdbcTypeCount() );
}
executeDelete(
pluralAttribute.getSeparateCollectionTable(),
entityDescriptor,
() -> fkTargetPart::forEachSelectable,
idsAndFks,
valueIndex,
fkTargetPart,
jdbcParameterBindings,
executionContext
);
SqmMutationStrategyHelper.visitCollectionTables(
entityDescriptor,
pluralAttribute -> {
if ( pluralAttribute.getSeparateCollectionTable() != null ) {
// this collection has a separate collection table, meaning it is one of:
// 1) element-collection
// 2) many-to-many
// 3) one-to many using a dedicated join-table
//
// in all of these cases, we should clean up the matching rows in the
// collection table
final ModelPart fkTargetPart = pluralAttribute.getKeyDescriptor().getTargetPart();
final int valueIndex;
if ( fkTargetPart instanceof EntityIdentifierMapping ) {
valueIndex = 0;
}
else {
if ( valueIndexCounter.get() == 0 ) {
valueIndexCounter.set( entityDescriptor.getIdentifierMapping().getJdbcTypeCount() );
}
valueIndex = valueIndexCounter.get();
valueIndexCounter.plus( fkTargetPart.getJdbcTypeCount() );
}
executeDelete(
pluralAttribute.getSeparateCollectionTable(),
entityDescriptor,
() -> fkTargetPart::forEachSelectable,
idsAndFks,
valueIndex,
fkTargetPart,
jdbcParameterBindings,
executionContext
);
}
}
);

View File

@ -6,16 +6,18 @@
*/
package org.hibernate.orm.test.bootstrap.binding.annotations.embedded;
import java.util.Collection;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
@Embeddable
public class InternetFavorites {
@Embedded
@ElementCollection
Collection<URLFavorite> links;
@Embedded
@ElementCollection
Collection<String> ideas;
public Collection<String> getIdeas() {