29
29
import org .hibernate .metamodel .mapping .AttributeMapping ;
30
30
import org .hibernate .persister .collection .CollectionPersister ;
31
31
import org .hibernate .persister .entity .EntityPersister ;
32
- import org .hibernate .proxy .HibernateProxy ;
33
32
import org .hibernate .reactive .logging .impl .Log ;
34
33
import org .hibernate .reactive .logging .impl .LoggerFactory ;
35
34
import org .hibernate .reactive .session .ReactiveSession ;
40
39
import org .hibernate .type .EntityType ;
41
40
import org .hibernate .type .Type ;
42
41
42
+ import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
43
43
import static org .hibernate .pretty .MessageHelper .infoString ;
44
44
import static org .hibernate .reactive .util .impl .CompletionStages .loop ;
45
45
import static org .hibernate .reactive .util .impl .CompletionStages .voidFuture ;
@@ -111,9 +111,9 @@ public static CompletionStage<?> fetchLazyAssociationsBeforeCascade(
111
111
* Cascade an action from the parent entity instance to all its children.
112
112
*/
113
113
public CompletionStage <Void > cascade () throws HibernateException {
114
- return voidFuture ().thenCompose (v -> {
114
+ return voidFuture ().thenCompose ( v -> {
115
115
CacheMode cacheMode = eventSource .getCacheMode ();
116
- if ( action == CascadingActions .DELETE ) {
116
+ if ( action == CascadingActions .DELETE ) {
117
117
eventSource .setCacheMode ( CacheMode .GET );
118
118
}
119
119
eventSource .getPersistenceContextInternal ().incrementCascadeLevel ();
@@ -125,18 +125,23 @@ public CompletionStage<Void> cascade() throws HibernateException {
125
125
}
126
126
127
127
private CompletionStage <Void > cascadeInternal () throws HibernateException {
128
-
129
128
if ( persister .hasCascades () || action .requiresNoCascadeChecking () ) { // performance opt
130
129
final boolean traceEnabled = LOG .isTraceEnabled ();
131
130
if ( traceEnabled ) {
132
131
LOG .tracev ( "Processing cascade {0} for: {1}" , action , persister .getEntityName () );
133
132
}
134
133
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
+ }
135
139
136
140
final Type [] types = persister .getPropertyTypes ();
137
141
final String [] propertyNames = persister .getPropertyNames ();
138
142
final CascadeStyle [] cascadeStyles = persister .getPropertyCascadeStyles ();
139
143
final boolean hasUninitializedLazyProperties = persister .hasUninitializedLazyProperties ( parent );
144
+
140
145
for ( int i = 0 ; i < types .length ; i ++) {
141
146
final CascadeStyle style = cascadeStyles [ i ];
142
147
final String propertyName = propertyNames [ i ];
@@ -154,7 +159,7 @@ private CompletionStage<Void> cascadeInternal() throws HibernateException {
154
159
// If parent is a detached entity being merged,
155
160
// then parent will not be in the PersistenceContext
156
161
// (so lazy attributes must not be initialized).
157
- if ( persistenceContext . getEntry ( parent ) == null ) {
162
+ if ( entry == null ) {
158
163
// parent was not in the PersistenceContext
159
164
continue ;
160
165
}
@@ -295,91 +300,88 @@ private void cascadeLogicalOneToOneOrphanRemoval(
295
300
final String propertyName ,
296
301
final boolean isCascadeDeleteEnabled ) throws HibernateException {
297
302
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 ) );
312
334
}
313
335
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
+ }
329
340
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 ;
335
359
}
336
360
}
337
361
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
+ );
357
370
}
358
371
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 () ) );
383
385
}
384
386
}
385
387
}
0 commit comments