HHH-14387 Alternative fix for deletion of bytecode lazy collections, by creating PersistentCollection for deletedState

This commit is contained in:
Christian Beikov 2022-08-30 17:14:50 +02:00
parent 41ac1f8e88
commit a83ff54671
2 changed files with 46 additions and 39 deletions

View File

@ -13,11 +13,9 @@ import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
@ -81,11 +79,6 @@ public final class Cascade {
final EntityPersister persister, final EntityPersister persister,
final Object parent, final Object parent,
final T anything) throws HibernateException { final T anything) throws HibernateException {
if ( action == CascadingActions.DELETE && cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE ) {
// Before deleting an entity, ensure CollectionEntry objects for uninitialized lazy collections exist,
// otherwise these collections are not properly deleted and this leads to FK violations
registerUninitializedLazyCollectionEntries( eventSource, persister, parent );
}
if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt
final boolean traceEnabled = LOG.isTraceEnabled(); final boolean traceEnabled = LOG.isTraceEnabled();
if ( traceEnabled ) { if ( traceEnabled ) {
@ -202,30 +195,6 @@ public final class Cascade {
} }
} }
private static void registerUninitializedLazyCollectionEntries(EventSource eventSource, EntityPersister persister, Object parent) {
if ( !persister.hasCollections() || !persister.hasUninitializedLazyProperties( parent ) ) {
return;
}
final Type[] types = persister.getPropertyTypes();
final String[] propertyNames = persister.getPropertyNames();
final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata();
for ( int i = 0; i < types.length; i++) {
if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) {
final CollectionType collectionType = (CollectionType) types[i];
final CollectionPersister collectionDescriptor = persister.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getCollectionDescriptor( collectionType.getRole() );
if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) {
final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() );
// This will make sure that a CollectionEntry exists
collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false );
}
}
}
}
/** /**
* Cascade an action to the child or children * Cascade an action to the child or children
*/ */

View File

@ -12,6 +12,8 @@ import org.hibernate.LockMode;
import org.hibernate.TransientObjectException; import org.hibernate.TransientObjectException;
import org.hibernate.action.internal.EntityDeleteAction; import org.hibernate.action.internal.EntityDeleteAction;
import org.hibernate.action.internal.OrphanRemovalAction; import org.hibernate.action.internal.OrphanRemovalAction;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
import org.hibernate.classic.Lifecycle; import org.hibernate.classic.Lifecycle;
import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.Cascade;
import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.CascadePoint;
@ -32,8 +34,11 @@ import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper; import org.hibernate.pretty.MessageHelper;
import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper; import org.hibernate.type.TypeHelper;
@ -251,7 +256,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
? persister.getValues(entity) //i.e. the entity came in from update() ? persister.getValues(entity) //i.e. the entity came in from update()
: entityEntry.getLoadedState(); : entityEntry.getLoadedState();
final Object[] deletedState = createDeletedState( persister, currentState, session ); final Object[] deletedState = createDeletedState( persister, entity, currentState, session );
entityEntry.setDeletedState( deletedState ); entityEntry.setDeletedState( deletedState );
session.getInterceptor().onDelete( session.getInterceptor().onDelete(
@ -313,13 +318,46 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
//persistenceContext.removeDatabaseSnapshot(key); //persistenceContext.removeDatabaseSnapshot(key);
} }
private Object[] createDeletedState(EntityPersister persister, Object[] currentState, EventSource session) { private Object[] createDeletedState(
Type[] propTypes = persister.getPropertyTypes(); EntityPersister persister,
final Object[] deletedState = new Object[propTypes.length]; Object parent,
// TypeFactory.deepCopy( currentState, propTypes, persister.getPropertyUpdateability(), deletedState, session ); Object[] currentState,
boolean[] copyability = new boolean[propTypes.length]; EventSource eventSource) {
final Type[] types = persister.getPropertyTypes();
final Object[] deletedState = new Object[types.length];
if ( !persister.hasCollections() || !persister.hasUninitializedLazyProperties( parent ) ) {
boolean[] copyability = new boolean[types.length];
java.util.Arrays.fill( copyability, true ); java.util.Arrays.fill( copyability, true );
TypeHelper.deepCopy( currentState, propTypes, copyability, deletedState, session ); TypeHelper.deepCopy( currentState, types, copyability, deletedState, eventSource );
return deletedState;
}
final String[] propertyNames = persister.getPropertyNames();
final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata();
for ( int i = 0; i < types.length; i++) {
if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) {
final CollectionType collectionType = (CollectionType) types[i];
final CollectionPersister collectionDescriptor = persister.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getCollectionDescriptor( collectionType.getRole() );
if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) {
final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() );
// This will make sure that a CollectionEntry exists
deletedState[i] = collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false );
}
else {
deletedState[i] = currentState[i];
}
}
else if ( currentState[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY
|| currentState[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
deletedState[i] = currentState[i];
}
else {
deletedState[i] = types[i].deepCopy( currentState[i], eventSource.getFactory() );
}
}
return deletedState; return deletedState;
} }