1010
1111import org .hibernate .TransientObjectException ;
1212import org .hibernate .bytecode .enhance .spi .LazyPropertyInitializer ;
13- import org .hibernate .engine .internal .ManagedTypeHelper ;
1413import org .hibernate .engine .internal .NonNullableTransientDependencies ;
1514import org .hibernate .engine .spi .EntityEntry ;
15+ import org .hibernate .engine .spi .PersistenceContext ;
16+ import org .hibernate .engine .spi .SelfDirtinessTracker ;
1617import org .hibernate .engine .spi .SessionImplementor ;
1718import org .hibernate .engine .spi .SharedSessionContractImplementor ;
1819import org .hibernate .internal .util .StringHelper ;
1920import org .hibernate .persister .entity .EntityPersister ;
2021import org .hibernate .proxy .HibernateProxy ;
2122import org .hibernate .proxy .LazyInitializer ;
23+ import org .hibernate .reactive .persister .entity .impl .ReactiveEntityPersister ;
2224import org .hibernate .type .CompositeType ;
2325import org .hibernate .type .EntityType ;
2426import org .hibernate .type .Type ;
2527
28+ import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
29+ import static org .hibernate .engine .internal .ManagedTypeHelper .processIfSelfDirtinessTracker ;
2630import static org .hibernate .reactive .util .impl .CompletionStages .completedFuture ;
2731import static org .hibernate .reactive .util .impl .CompletionStages .falseFuture ;
2832import static org .hibernate .reactive .util .impl .CompletionStages .loop ;
@@ -111,48 +115,60 @@ else if ( type.isEntityType() ) {
111115 else {
112116 // if we're dealing with a lazy property, it may need to be
113117 // initialized to determine if the value is nullifiable
114- if ( isDelete
115- && value == LazyPropertyInitializer .UNFETCHED_PROPERTY
116- && !session .getPersistenceContextInternal ().isNullifiableEntityKeysEmpty () ) {
117- throw new UnsupportedOperationException ( "lazy property initialization not supported" );
118+ if ( needToInitialize ( value , entityType ) ) {
119+ returnedStage = ( (ReactiveEntityPersister ) persister )
120+ .reactiveInitializeLazyProperty ( propertyName , self , session )
121+ .thenCompose ( possiblyInitializedValue -> {
122+ if ( possiblyInitializedValue == null ) {
123+ // The uninitialized value was initialized to null
124+ return nullFuture ();
125+ }
126+ else {
127+ // If the value is not nullifiable, make sure that the
128+ // possibly initialized value is returned.
129+ return isNullifiable ( entityType .getAssociatedEntityName (), value )
130+ .thenApply ( trans -> trans ? null : value );
131+ } }
132+ );
133+ }
134+ else {
135+ returnedStage = isNullifiable ( entityType .getAssociatedEntityName (), value )
136+ .thenApply ( trans -> trans ? null : value );
118137 }
119138
120- // If the value is not nullifiable, make sure that the
121- // possibly initialized value is returned.
122- returnedStage = isNullifiable ( entityType .getAssociatedEntityName (), value )
123- .thenApply ( trans -> trans ? null : value );
124139 }
125140 }
126141 else if ( type .isAnyType () ) {
127- returnedStage = isNullifiable ( null , value ).thenApply ( trans -> trans ? null : value );
142+ returnedStage = isNullifiable ( null , value ).thenApply ( trans -> trans ? null : value );
128143 }
129144 else if ( type .isComponentType () ) {
130- final CompositeType actype = (CompositeType ) type ;
131- final Object [] subValues = actype .getPropertyValues ( value , session );
132- final Type [] subtypes = actype .getSubtypes ();
133- final String [] subPropertyNames = actype .getPropertyNames ();
145+ final CompositeType compositeType = (CompositeType ) type ;
146+ final Object [] subValues = compositeType .getPropertyValues ( value , session );
147+ final Type [] subtypes = compositeType .getSubtypes ();
148+ final String [] subPropertyNames = compositeType .getPropertyNames ();
134149 CompletionStage <Boolean > loop = falseFuture ();
135150 for ( int i = 0 ; i < subValues .length ; i ++ ) {
136151 final int index = i ;
137- loop = loop
138- .thenCompose ( substitute -> nullifyTransientReferences (
139- subValues [index ],
140- StringHelper .qualify ( propertyName , subPropertyNames [index ] ),
141- subtypes [index ]
142- )
143- .thenApply ( replacement -> {
144- if ( replacement != subValues [index ] ) {
145- subValues [index ] = replacement ;
146- return Boolean .TRUE ;
147- }
148- return substitute ;
149- } )
150- );
152+ loop = loop .thenCompose ( substitute -> nullifyTransientReferences (
153+ subValues [index ],
154+ StringHelper .qualify ( propertyName , subPropertyNames [index ] ),
155+ subtypes [index ]
156+ )
157+ .thenApply ( replacement -> {
158+ if ( replacement != subValues [index ] ) {
159+ subValues [index ] = replacement ;
160+ return true ;
161+ }
162+ else {
163+ return substitute ;
164+ }
165+ } )
166+ );
151167 }
152168 returnedStage = loop .thenApply ( substitute -> {
153169 if ( substitute ) {
154170 // todo : need to account for entity mode on the CompositeType interface :(
155- actype .setPropertyValues ( value , subValues );
171+ compositeType .setPropertyValues ( value , subValues );
156172 }
157173 return value ;
158174 } );
@@ -162,21 +178,23 @@ else if ( type.isComponentType() ) {
162178 }
163179
164180 return returnedStage .thenApply ( returnedValue -> {
165- trackDirt ( value , propertyName , returnedValue );
181+ // value != returnedValue if either:
182+ // 1) returnedValue was nullified (set to null);
183+ // or 2) returnedValue was initialized, but not nullified.
184+ // When bytecode-enhancement is used for dirty-checking, the change should
185+ // only be tracked when returnedValue was nullified (1)).
186+ if ( value != returnedValue && returnedValue == null ) {
187+ processIfSelfDirtinessTracker ( self , SelfDirtinessTracker ::$$_hibernate_trackChange , propertyName );
188+ }
166189 return returnedValue ;
167190 } );
168191 }
169192
170- private void trackDirt (Object value , String propertyName , Object returnedValue ) {
171- // value != returnedValue if either:
172- // 1) returnedValue was nullified (set to null);
173- // or 2) returnedValue was initialized, but not nullified.
174- // When bytecode-enhancement is used for dirty-checking, the change should
175- // only be tracked when returnedValue was nullified (1)).
176- if ( value != returnedValue && returnedValue == null
177- && ManagedTypeHelper .isSelfDirtinessTracker ( self ) ) {
178- ManagedTypeHelper .asSelfDirtinessTracker ( self ).$$_hibernate_trackChange ( propertyName );
179- }
193+ private boolean needToInitialize (Object value , Type type ) {
194+ return isDelete
195+ && value == LazyPropertyInitializer .UNFETCHED_PROPERTY
196+ && type .isEntityType ()
197+ && !session .getPersistenceContextInternal ().isNullifiableEntityKeysEmpty ();
180198 }
181199
182200 /**
@@ -192,39 +210,49 @@ private CompletionStage<Boolean> isNullifiable(final String entityName, Object o
192210 return falseFuture ();
193211 }
194212
195- if ( object instanceof HibernateProxy ) {
196- // if its an uninitialized proxy it can't be transient
197- final LazyInitializer li = ( (HibernateProxy ) object ).getHibernateLazyInitializer ();
198- if ( li .getImplementation ( session ) == null ) {
199- return falseFuture ();
200- // ie. we never have to null out a reference to
201- // an uninitialized proxy
213+ final PersistenceContext persistenceContext = session .getPersistenceContextInternal ();
214+
215+ final LazyInitializer lazyInitializer = HibernateProxy .extractLazyInitializer ( object );
216+ if ( lazyInitializer != null ) {
217+ // if it's an uninitialized proxy it can only be
218+ // transient if we did an unloaded-delete on the
219+ // proxy itself, in which case there is no entry
220+ // for it, but its key has already been registered
221+ // as nullifiable
222+ Object entity = lazyInitializer .getImplementation ( session );
223+ if ( entity == null ) {
224+ // an unloaded proxy might be scheduled for deletion
225+ completedFuture ( persistenceContext .containsDeletedUnloadedEntityKey (
226+ session .generateEntityKey (
227+ lazyInitializer .getIdentifier (),
228+ session .getFactory ().getMappingMetamodel ()
229+ .getEntityDescriptor ( lazyInitializer .getEntityName () )
230+ )
231+ ) );
202232 }
203233 else {
204234 //unwrap it
205- object = li . getImplementation ( session ) ;
235+ object = entity ;
206236 }
207237 }
208238
209239 // if it was a reference to self, don't need to nullify
210240 // unless we are using native id generation, in which
211241 // case we definitely need to nullify
212242 if ( object == self ) {
213- return completedFuture ( isEarlyInsert || ( isDelete && session .getJdbcServices ().getDialect ().hasSelfReferentialForeignKeyBug () ) );
243+ return completedFuture ( isEarlyInsert
244+ || isDelete && session .getJdbcServices ().getDialect ().hasSelfReferentialForeignKeyBug () );
214245 }
215246
216247 // See if the entity is already bound to this session, if not look at the
217248 // entity identifier and assume that the entity is persistent if the
218249 // id is not "unsaved" (that is, we rely on foreign keys to keep
219250 // database integrity)
220251
221- final EntityEntry entityEntry = session .getPersistenceContextInternal ().getEntry ( object );
222- if ( entityEntry == null ) {
223- return isTransient ( entityName , object , null , session );
224- }
225- else {
226- return completedFuture ( entityEntry .isNullifiable ( isEarlyInsert , session ) );
227- }
252+ final EntityEntry entityEntry = persistenceContext .getEntry ( object );
253+ return entityEntry == null
254+ ? isTransient ( entityName , object , null , session )
255+ : completedFuture ( entityEntry .isNullifiable ( isEarlyInsert , session ) );
228256 }
229257 }
230258
@@ -242,7 +270,7 @@ private CompletionStage<Boolean> isNullifiable(final String entityName, Object o
242270 * @return {@code true} if the given entity is not transient (meaning it is either detached/persistent)
243271 */
244272 public static CompletionStage <Boolean > isNotTransient (String entityName , Object entity , Boolean assumed , SessionImplementor session ) {
245- if ( entity instanceof HibernateProxy ) {
273+ if ( isHibernateProxy ( entity ) ) {
246274 return trueFuture ();
247275 }
248276
@@ -251,7 +279,6 @@ public static CompletionStage<Boolean> isNotTransient(String entityName, Object
251279 }
252280
253281 // todo : shouldn't assumed be revered here?
254-
255282 return isTransient ( entityName , entity , assumed , session )
256283 .thenApply ( trans -> !trans );
257284 }
@@ -296,7 +323,8 @@ public static CompletionStage<Boolean> isTransient(String entityName, Object ent
296323 }
297324
298325 // hit the database, after checking the session cache for a snapshot
299- ReactivePersistenceContextAdapter persistenceContext = (ReactivePersistenceContextAdapter ) session .getPersistenceContextInternal ();
326+ ReactivePersistenceContextAdapter persistenceContext =
327+ (ReactivePersistenceContextAdapter ) session .getPersistenceContextInternal ();
300328 Object id = persister .getIdentifier ( entity , session );
301329 return persistenceContext .reactiveGetDatabaseSnapshot ( id , persister ).thenApply ( Objects ::isNull );
302330 }
@@ -394,8 +422,8 @@ private static CompletionStage<Void> collectNonNullableTransientEntities(
394422 if ( !isNullable && !entityType .isOneToOne () ) {
395423 return nullifier
396424 .isNullifiable ( entityType .getAssociatedEntityName (), value )
397- .thenAccept ( isNullifiable -> {
398- if ( isNullifiable ) {
425+ .thenAccept ( nullifiable -> {
426+ if ( nullifiable ) {
399427 nonNullableTransientEntities .add ( propertyName , value );
400428 }
401429 } );
@@ -405,20 +433,20 @@ else if ( type.isAnyType() ) {
405433 if ( !isNullable ) {
406434 return nullifier
407435 .isNullifiable ( null , value )
408- .thenAccept ( isNullifiable -> {
409- if ( isNullifiable ) {
436+ .thenAccept ( nullifiable -> {
437+ if ( nullifiable ) {
410438 nonNullableTransientEntities .add ( propertyName , value );
411439 }
412440 } );
413441 }
414442 }
415443 else if ( type .isComponentType () ) {
416- final CompositeType actype = (CompositeType ) type ;
417- final boolean [] subValueNullability = actype .getPropertyNullability ();
444+ final CompositeType compositeType = (CompositeType ) type ;
445+ final boolean [] subValueNullability = compositeType .getPropertyNullability ();
418446 if ( subValueNullability != null ) {
419- final String [] subPropertyNames = actype .getPropertyNames ();
420- final Object [] subvalues = actype .getPropertyValues ( value , session );
421- final Type [] subtypes = actype .getSubtypes ();
447+ final String [] subPropertyNames = compositeType .getPropertyNames ();
448+ final Object [] subvalues = compositeType .getPropertyValues ( value , session );
449+ final Type [] subtypes = compositeType .getSubtypes ();
422450 return loop ( 0 , subtypes .length ,
423451 i -> collectNonNullableTransientEntities (
424452 nullifier ,
0 commit comments