From 63e6793d8f8e4fa97466ec638dcdb3fee0b6cd73 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 7 Apr 2017 12:34:05 -0400 Subject: [PATCH] HHH-9663 - Fix orphan removal cascade for one-to-one mappings. --- .../hibernate/engine/internal/Cascade.java | 93 +++++++++++++++---- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 8f22674c34..cccf0fea33 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -26,6 +26,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; +import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; @@ -87,21 +88,20 @@ public final class Cascade { final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); final int componentPathStackDepth = 0; for ( int i = 0; i < types.length; i++) { - final CascadeStyle style = cascadeStyles[i]; - final String propertyName = propertyNames[i]; - - if ( style.doCascade( action ) ) { - Object child; + final CascadeStyle style = cascadeStyles[ i ]; + final String propertyName = propertyNames[ i ]; + Object child = null; + if ( style.doCascade( action ) || action.deleteOrphans() ) { if ( hasUninitializedLazyProperties && !persister.getInstrumentationMetadata().isAttributeLoaded( parent, propertyName ) ) { // parent is a bytecode enhanced entity. // cascading to an uninitialized, lazy value. - if ( types[i].isCollectionType() ) { + if ( types[ i ].isCollectionType() ) { // The collection does not need to be loaded from the DB. // CollectionType#resolve will return an uninitialized PersistentCollection. // The action will initialize the collection later, if necessary. - child = types[i].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent ); + child = types[ i ].resolve( LazyPropertyInitializer.UNFETCHED_PROPERTY, eventSource, parent ); // TODO: it would be nice to be able to set the attribute in parent using // persister.setPropertyValue( parent, i, child ). // Unfortunately, that would cause the uninitialized collection to be @@ -115,13 +115,24 @@ public final class Cascade { } else { // Nothing to do, so just skip cascading to this lazy (non-collection) attribute. + if ( action.requiresNoCascadeChecking() ) { + action.noCascade( + eventSource, + parent, + persister, + types[ i ], + i + ); + } continue; } } else { child = persister.getPropertyValue( parent, i ); } + } + if ( style.doCascade( action ) ) { cascadeProperty( action, cascadePoint, @@ -129,7 +140,7 @@ public final class Cascade { componentPathStackDepth, parent, child, - types[i], + types[ i ], style, propertyName, anything, @@ -141,9 +152,22 @@ public final class Cascade { eventSource, parent, persister, - types[i], + types[ i ], i ); + if ( action.deleteOrphans() ) { + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + componentPathStackDepth, + parent, + child, + types[ i ], + style, + propertyName, + false + ); + } } } @@ -200,7 +224,30 @@ public final class Cascade { ); } } - + + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + componentPathStackDepth, + parent, + child, + type, + style, + propertyName, + isCascadeDeleteEnabled ); + } + + private static void cascadeLogicalOneToOneOrphanRemoval( + final CascadingAction action, + final EventSource eventSource, + final int componentPathStackDepth, + final Object parent, + final Object child, + final Type type, + final CascadeStyle style, + final String propertyName, + final boolean isCascadeDeleteEnabled) throws HibernateException { + // potentially we need to handle orphan deletes for one-to-ones here... if ( isLogicalOneToOne( type ) ) { // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require @@ -210,7 +257,7 @@ public final class Cascade { // because it is currently null. final EntityEntry entry = eventSource.getPersistenceContext().getEntry( parent ); if ( entry != null && entry.getStatus() != Status.SAVING ) { - final Object loadedValue; + Object loadedValue; if ( componentPathStackDepth == 0 ) { // association defined on entity loadedValue = entry.getLoadedValue( propertyName ); @@ -232,15 +279,21 @@ public final class Cascade { // final String getPropertyPath = composePropertyPath( entityType.getPropertyName() ); loadedValue = null; } - + // orphaned if the association was nulled (child == null) or receives a new value while the // entity is managed (without first nulling and manually flushing). if ( child == null || ( loadedValue != null && child != loadedValue ) ) { - final EntityEntry valueEntry = eventSource - .getPersistenceContext().getEntry( + EntityEntry valueEntry = eventSource + .getPersistenceContext().getEntry( loadedValue ); - // Need to check this in case the context has - // already been flushed. See HHH-7829. + + if ( valueEntry == null && loadedValue instanceof HibernateProxy ) { + // un-proxy and re-associate for cascade operation + // useful for @OneToOne defined as FetchType.LAZY + loadedValue = eventSource.getPersistenceContext().unproxyAndReassociate( loadedValue ); + valueEntry = eventSource.getPersistenceContext().getEntry( loadedValue ); + } + if ( valueEntry != null ) { final String entityName = valueEntry.getPersister().getEntityName(); if ( LOG.isTraceEnabled() ) { @@ -248,10 +301,10 @@ public final class Cascade { final String description = MessageHelper.infoString( entityName, id ); LOG.tracev( "Deleting orphaned entity instance: {0}", description ); } - - if (type.isAssociationType() && ((AssociationType)type).getForeignKeyDirection().equals( - ForeignKeyDirection.TO_PARENT - )) { + + if ( type.isAssociationType() && ( (AssociationType) type ).getForeignKeyDirection().equals( + ForeignKeyDirection.TO_PARENT + ) ) { // If FK direction is to-parent, we must remove the orphan *beforeQuery* the queued update(s) // occur. Otherwise, replacing the association on a managed entity, without manually // nulling and flushing, causes FK constraint violations.