10
10
11
11
import org .hibernate .TransientObjectException ;
12
12
import org .hibernate .bytecode .enhance .spi .LazyPropertyInitializer ;
13
- import org .hibernate .engine .internal .ManagedTypeHelper ;
14
13
import org .hibernate .engine .internal .NonNullableTransientDependencies ;
15
14
import org .hibernate .engine .spi .EntityEntry ;
15
+ import org .hibernate .engine .spi .PersistenceContext ;
16
+ import org .hibernate .engine .spi .SelfDirtinessTracker ;
16
17
import org .hibernate .engine .spi .SessionImplementor ;
17
18
import org .hibernate .engine .spi .SharedSessionContractImplementor ;
18
19
import org .hibernate .internal .util .StringHelper ;
19
20
import org .hibernate .persister .entity .EntityPersister ;
20
21
import org .hibernate .proxy .HibernateProxy ;
21
22
import org .hibernate .proxy .LazyInitializer ;
23
+ import org .hibernate .reactive .persister .entity .impl .ReactiveEntityPersister ;
22
24
import org .hibernate .type .CompositeType ;
23
25
import org .hibernate .type .EntityType ;
24
26
import org .hibernate .type .Type ;
25
27
28
+ import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
29
+ import static org .hibernate .engine .internal .ManagedTypeHelper .processIfSelfDirtinessTracker ;
26
30
import static org .hibernate .reactive .util .impl .CompletionStages .completedFuture ;
27
31
import static org .hibernate .reactive .util .impl .CompletionStages .falseFuture ;
28
32
import static org .hibernate .reactive .util .impl .CompletionStages .loop ;
@@ -111,48 +115,60 @@ else if ( type.isEntityType() ) {
111
115
else {
112
116
// if we're dealing with a lazy property, it may need to be
113
117
// 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 );
118
137
}
119
138
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 );
124
139
}
125
140
}
126
141
else if ( type .isAnyType () ) {
127
- returnedStage = isNullifiable ( null , value ).thenApply ( trans -> trans ? null : value );
142
+ returnedStage = isNullifiable ( null , value ).thenApply ( trans -> trans ? null : value );
128
143
}
129
144
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 ();
134
149
CompletionStage <Boolean > loop = falseFuture ();
135
150
for ( int i = 0 ; i < subValues .length ; i ++ ) {
136
151
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
+ );
151
167
}
152
168
returnedStage = loop .thenApply ( substitute -> {
153
169
if ( substitute ) {
154
170
// todo : need to account for entity mode on the CompositeType interface :(
155
- actype .setPropertyValues ( value , subValues );
171
+ compositeType .setPropertyValues ( value , subValues );
156
172
}
157
173
return value ;
158
174
} );
@@ -162,21 +178,23 @@ else if ( type.isComponentType() ) {
162
178
}
163
179
164
180
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
+ }
166
189
return returnedValue ;
167
190
} );
168
191
}
169
192
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 ();
180
198
}
181
199
182
200
/**
@@ -192,39 +210,49 @@ private CompletionStage<Boolean> isNullifiable(final String entityName, Object o
192
210
return falseFuture ();
193
211
}
194
212
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
+ ) );
202
232
}
203
233
else {
204
234
//unwrap it
205
- object = li . getImplementation ( session ) ;
235
+ object = entity ;
206
236
}
207
237
}
208
238
209
239
// if it was a reference to self, don't need to nullify
210
240
// unless we are using native id generation, in which
211
241
// case we definitely need to nullify
212
242
if ( object == self ) {
213
- return completedFuture ( isEarlyInsert || ( isDelete && session .getJdbcServices ().getDialect ().hasSelfReferentialForeignKeyBug () ) );
243
+ return completedFuture ( isEarlyInsert
244
+ || isDelete && session .getJdbcServices ().getDialect ().hasSelfReferentialForeignKeyBug () );
214
245
}
215
246
216
247
// See if the entity is already bound to this session, if not look at the
217
248
// entity identifier and assume that the entity is persistent if the
218
249
// id is not "unsaved" (that is, we rely on foreign keys to keep
219
250
// database integrity)
220
251
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 ) );
228
256
}
229
257
}
230
258
@@ -242,7 +270,7 @@ private CompletionStage<Boolean> isNullifiable(final String entityName, Object o
242
270
* @return {@code true} if the given entity is not transient (meaning it is either detached/persistent)
243
271
*/
244
272
public static CompletionStage <Boolean > isNotTransient (String entityName , Object entity , Boolean assumed , SessionImplementor session ) {
245
- if ( entity instanceof HibernateProxy ) {
273
+ if ( isHibernateProxy ( entity ) ) {
246
274
return trueFuture ();
247
275
}
248
276
@@ -251,7 +279,6 @@ public static CompletionStage<Boolean> isNotTransient(String entityName, Object
251
279
}
252
280
253
281
// todo : shouldn't assumed be revered here?
254
-
255
282
return isTransient ( entityName , entity , assumed , session )
256
283
.thenApply ( trans -> !trans );
257
284
}
@@ -296,7 +323,8 @@ public static CompletionStage<Boolean> isTransient(String entityName, Object ent
296
323
}
297
324
298
325
// hit the database, after checking the session cache for a snapshot
299
- ReactivePersistenceContextAdapter persistenceContext = (ReactivePersistenceContextAdapter ) session .getPersistenceContextInternal ();
326
+ ReactivePersistenceContextAdapter persistenceContext =
327
+ (ReactivePersistenceContextAdapter ) session .getPersistenceContextInternal ();
300
328
Object id = persister .getIdentifier ( entity , session );
301
329
return persistenceContext .reactiveGetDatabaseSnapshot ( id , persister ).thenApply ( Objects ::isNull );
302
330
}
@@ -394,8 +422,8 @@ private static CompletionStage<Void> collectNonNullableTransientEntities(
394
422
if ( !isNullable && !entityType .isOneToOne () ) {
395
423
return nullifier
396
424
.isNullifiable ( entityType .getAssociatedEntityName (), value )
397
- .thenAccept ( isNullifiable -> {
398
- if ( isNullifiable ) {
425
+ .thenAccept ( nullifiable -> {
426
+ if ( nullifiable ) {
399
427
nonNullableTransientEntities .add ( propertyName , value );
400
428
}
401
429
} );
@@ -405,20 +433,20 @@ else if ( type.isAnyType() ) {
405
433
if ( !isNullable ) {
406
434
return nullifier
407
435
.isNullifiable ( null , value )
408
- .thenAccept ( isNullifiable -> {
409
- if ( isNullifiable ) {
436
+ .thenAccept ( nullifiable -> {
437
+ if ( nullifiable ) {
410
438
nonNullableTransientEntities .add ( propertyName , value );
411
439
}
412
440
} );
413
441
}
414
442
}
415
443
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 ();
418
446
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 ();
422
450
return loop ( 0 , subtypes .length ,
423
451
i -> collectNonNullableTransientEntities (
424
452
nullifier ,
0 commit comments