2929import org .hibernate .metamodel .mapping .AttributeMapping ;
3030import org .hibernate .persister .collection .CollectionPersister ;
3131import org .hibernate .persister .entity .EntityPersister ;
32- import org .hibernate .proxy .HibernateProxy ;
3332import org .hibernate .reactive .logging .impl .Log ;
3433import org .hibernate .reactive .logging .impl .LoggerFactory ;
3534import org .hibernate .reactive .session .ReactiveSession ;
4039import org .hibernate .type .EntityType ;
4140import org .hibernate .type .Type ;
4241
42+ import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
4343import static org .hibernate .pretty .MessageHelper .infoString ;
4444import static org .hibernate .reactive .util .impl .CompletionStages .loop ;
4545import static org .hibernate .reactive .util .impl .CompletionStages .voidFuture ;
@@ -111,9 +111,9 @@ public static CompletionStage<?> fetchLazyAssociationsBeforeCascade(
111111 * Cascade an action from the parent entity instance to all its children.
112112 */
113113 public CompletionStage <Void > cascade () throws HibernateException {
114- return voidFuture ().thenCompose (v -> {
114+ return voidFuture ().thenCompose ( v -> {
115115 CacheMode cacheMode = eventSource .getCacheMode ();
116- if ( action == CascadingActions .DELETE ) {
116+ if ( action == CascadingActions .DELETE ) {
117117 eventSource .setCacheMode ( CacheMode .GET );
118118 }
119119 eventSource .getPersistenceContextInternal ().incrementCascadeLevel ();
@@ -125,18 +125,23 @@ public CompletionStage<Void> cascade() throws HibernateException {
125125 }
126126
127127 private CompletionStage <Void > cascadeInternal () throws HibernateException {
128-
129128 if ( persister .hasCascades () || action .requiresNoCascadeChecking () ) { // performance opt
130129 final boolean traceEnabled = LOG .isTraceEnabled ();
131130 if ( traceEnabled ) {
132131 LOG .tracev ( "Processing cascade {0} for: {1}" , action , persister .getEntityName () );
133132 }
134133 final PersistenceContext persistenceContext = eventSource .getPersistenceContextInternal ();
134+ final EntityEntry entry = persistenceContext .getEntry ( parent );
135+ if ( entry != null && entry .getLoadedState () == null && entry .getStatus () == Status .MANAGED && persister .getBytecodeEnhancementMetadata ()
136+ .isEnhancedForLazyLoading () ) {
137+ return voidFuture ();
138+ }
135139
136140 final Type [] types = persister .getPropertyTypes ();
137141 final String [] propertyNames = persister .getPropertyNames ();
138142 final CascadeStyle [] cascadeStyles = persister .getPropertyCascadeStyles ();
139143 final boolean hasUninitializedLazyProperties = persister .hasUninitializedLazyProperties ( parent );
144+
140145 for ( int i = 0 ; i < types .length ; i ++) {
141146 final CascadeStyle style = cascadeStyles [ i ];
142147 final String propertyName = propertyNames [ i ];
@@ -154,7 +159,7 @@ private CompletionStage<Void> cascadeInternal() throws HibernateException {
154159 // If parent is a detached entity being merged,
155160 // then parent will not be in the PersistenceContext
156161 // (so lazy attributes must not be initialized).
157- if ( persistenceContext . getEntry ( parent ) == null ) {
162+ if ( entry == null ) {
158163 // parent was not in the PersistenceContext
159164 continue ;
160165 }
@@ -295,91 +300,88 @@ private void cascadeLogicalOneToOneOrphanRemoval(
295300 final String propertyName ,
296301 final boolean isCascadeDeleteEnabled ) throws HibernateException {
297302
298- // potentially we need to handle orphan deletes for one-to-ones here...
299- if ( isLogicalOneToOne ( type ) ) {
300- // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require
301- // orphan checking
302- if ( style .hasOrphanDelete () && action .deleteOrphans () ) {
303- // value is orphaned if loaded state for this property shows not null
304- // because it is currently null.
305- final PersistenceContext persistenceContext = eventSource .getPersistenceContextInternal ();
306- final EntityEntry entry = persistenceContext .getEntry ( parent );
307- if ( entry != null && entry .getStatus () != Status .SAVING ) {
308- Object loadedValue ;
309- if ( componentPath == null ) {
310- // association defined on entity
311- loadedValue = entry .getLoadedValue ( propertyName );
303+ // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require
304+ // orphan checking
305+ if ( style .hasOrphanDelete () && action .deleteOrphans () ) {
306+ // value is orphaned if loaded state for this property shows not null
307+ // because it is currently null.
308+ final PersistenceContext persistenceContext = eventSource .getPersistenceContextInternal ();
309+ final EntityEntry entry = persistenceContext .getEntry ( parent );
310+ if ( entry != null && entry .getStatus () != Status .SAVING ) {
311+ Object loadedValue ;
312+ if ( componentPath == null ) {
313+ // association defined on entity
314+ loadedValue = entry .getLoadedValue ( propertyName );
315+ }
316+ else {
317+ // association defined on component
318+ // Since the loadedState in the EntityEntry is a flat domain type array
319+ // We first have to extract the component object and then ask the component type
320+ // recursively to give us the value of the sub-property of that object
321+ final AttributeMapping propertyType = entry .getPersister ().findAttributeMapping ( componentPath .get ( 0 ) );
322+ if ( propertyType instanceof ComponentType ) {
323+ loadedValue = entry .getLoadedValue ( componentPath .get ( 0 ) );
324+ ComponentType componentType = (ComponentType ) propertyType ;
325+ if ( componentPath .size () != 1 ) {
326+ for ( int i = 1 ; i < componentPath .size (); i ++ ) {
327+ final int subPropertyIndex = componentType .getPropertyIndex ( componentPath .get ( i ) );
328+ loadedValue = componentType .getPropertyValue ( loadedValue , subPropertyIndex );
329+ componentType = (ComponentType ) componentType .getSubtypes ()[subPropertyIndex ];
330+ }
331+ }
332+
333+ loadedValue = componentType .getPropertyValue ( loadedValue , componentType .getPropertyIndex ( propertyName ) );
312334 }
313335 else {
314- // association defined on component
315- // Since the loadedState in the EntityEntry is a flat domain type array
316- // We first have to extract the component object and then ask the component type
317- // recursively to give us the value of the sub-property of that object
318- final AttributeMapping propertyType = entry .getPersister ().findAttributeMapping ( componentPath .get ( 0 ) );
319- if ( propertyType instanceof ComponentType ) {
320- loadedValue = entry .getLoadedValue ( componentPath .get ( 0 ) );
321- ComponentType componentType = (ComponentType ) propertyType ;
322- if ( componentPath .size () != 1 ) {
323- for ( int i = 1 ; i < componentPath .size (); i ++ ) {
324- final int subPropertyIndex = componentType .getPropertyIndex ( componentPath .get ( i ) );
325- loadedValue = componentType .getPropertyValue ( loadedValue , subPropertyIndex );
326- componentType = (ComponentType ) componentType .getSubtypes ()[subPropertyIndex ];
327- }
328- }
336+ // Association is probably defined in an element collection, so we can't do orphan removals
337+ loadedValue = null ;
338+ }
339+ }
329340
330- loadedValue = componentType .getPropertyValue ( loadedValue , componentType .getPropertyIndex ( propertyName ) );
331- }
332- else {
333- // Association is probably defined in an element collection, so we can't do orphan removals
334- loadedValue = null ;
341+ // orphaned if the association was nulled (child == null) or receives a new value while the
342+ // entity is managed (without first nulling and manually flushing).
343+ if ( child == null || loadedValue != null && child != loadedValue ) {
344+ EntityEntry valueEntry = persistenceContext .getEntry ( loadedValue );
345+
346+ if ( valueEntry == null && isHibernateProxy ( loadedValue ) ) {
347+ // un-proxy and re-associate for cascade operation
348+ // useful for @OneToOne defined as FetchType.LAZY
349+ loadedValue = persistenceContext .unproxyAndReassociate ( loadedValue );
350+ valueEntry = persistenceContext .getEntry ( loadedValue );
351+
352+ // HHH-11965
353+ // Should the unwrapped proxy value be equal via reference to the entity's property value
354+ // provided by the 'child' variable, we should not trigger the orphan removal of the
355+ // associated one-to-one.
356+ if ( child == loadedValue ) {
357+ // do nothing
358+ return ;
335359 }
336360 }
337361
338- // orphaned if the association was nulled (child == null) or receives a new value while the
339- // entity is managed (without first nulling and manually flushing).
340- if ( child == null || loadedValue != null && child != loadedValue ) {
341- EntityEntry valueEntry = persistenceContext .getEntry ( loadedValue );
342-
343- if ( valueEntry == null && loadedValue instanceof HibernateProxy ) {
344- // un-proxy and re-associate for cascade operation
345- // useful for @OneToOne defined as FetchType.LAZY
346- loadedValue = persistenceContext .unproxyAndReassociate ( loadedValue );
347- valueEntry = persistenceContext .getEntry ( loadedValue );
348-
349- // HHH-11965
350- // Should the unwrapped proxy value be equal via reference to the entity's property value
351- // provided by the 'child' variable, we should not trigger the orphan removal of the
352- // associated one-to-one.
353- if ( child == loadedValue ) {
354- // do nothing
355- return ;
356- }
362+ if ( valueEntry != null ) {
363+ final EntityPersister persister = valueEntry .getPersister ();
364+ final String entityName = persister .getEntityName ();
365+ if ( LOG .isTraceEnabled () ) {
366+ LOG .tracev (
367+ "Deleting orphaned entity instance: {0}" ,
368+ infoString ( entityName , persister .getIdentifier ( loadedValue , eventSource ) )
369+ );
357370 }
358371
359- if ( valueEntry != null ) {
360- final EntityPersister persister = valueEntry .getPersister ();
361- final String entityName = persister .getEntityName ();
362- if ( LOG .isTraceEnabled () ) {
363- LOG .tracev (
364- "Deleting orphaned entity instance: {0}" ,
365- infoString ( entityName , persister .getIdentifier ( loadedValue , eventSource ) )
366- );
367- }
368-
369- final Object loaded = loadedValue ;
370- if ( type .isAssociationType ()
371- && ( (AssociationType ) type ).getForeignKeyDirection ().equals (TO_PARENT ) ) {
372- // If FK direction is to-parent, we must remove the orphan *before* the queued update(s)
373- // occur. Otherwise, replacing the association on a managed entity, without manually
374- // nulling and flushing, causes FK constraint violations.
375- stage = stage .thenCompose ( v -> ( (ReactiveSession ) eventSource )
376- .reactiveRemoveOrphanBeforeUpdates ( entityName , loaded ) );
377- }
378- else {
379- // Else, we must delete after the updates.
380- stage = stage .thenCompose ( v -> ( (ReactiveSession ) eventSource )
381- .reactiveRemove ( entityName , loaded , isCascadeDeleteEnabled , DeleteContext .create () ) );
382- }
372+ final Object loaded = loadedValue ;
373+ if ( type .isAssociationType ()
374+ && ( (AssociationType ) type ).getForeignKeyDirection ().equals (TO_PARENT ) ) {
375+ // If FK direction is to-parent, we must remove the orphan *before* the queued update(s)
376+ // occur. Otherwise, replacing the association on a managed entity, without manually
377+ // nulling and flushing, causes FK constraint violations.
378+ stage = stage .thenCompose ( v -> ( (ReactiveSession ) eventSource )
379+ .reactiveRemoveOrphanBeforeUpdates ( entityName , loaded ) );
380+ }
381+ else {
382+ // Else, we must delete after the updates.
383+ stage = stage .thenCompose ( v -> ( (ReactiveSession ) eventSource )
384+ .reactiveRemove ( entityName , loaded , isCascadeDeleteEnabled , DeleteContext .create () ) );
383385 }
384386 }
385387 }
0 commit comments