Some reflection optimizer related fixes
This commit is contained in:
parent
2a93aa5467
commit
4fdbb3d5f6
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
|
|||
return null;
|
||||
}
|
||||
|
||||
if ( hasCustomAccessors() ) {
|
||||
if ( hasCustomAccessors() || bootDescriptor.getCustomInstantiator() != null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue