Skip to content

Commit 7f42111

Browse files
committed
cleanups and sync with core for ForeignKeys
1 parent c133e0d commit 7f42111

File tree

3 files changed

+120
-83
lines changed

3 files changed

+120
-83
lines changed

hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ForeignKeys.java

Lines changed: 96 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,23 @@
1010

1111
import org.hibernate.TransientObjectException;
1212
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
13-
import org.hibernate.engine.internal.ManagedTypeHelper;
1413
import org.hibernate.engine.internal.NonNullableTransientDependencies;
1514
import org.hibernate.engine.spi.EntityEntry;
15+
import org.hibernate.engine.spi.PersistenceContext;
16+
import org.hibernate.engine.spi.SelfDirtinessTracker;
1617
import org.hibernate.engine.spi.SessionImplementor;
1718
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1819
import org.hibernate.internal.util.StringHelper;
1920
import org.hibernate.persister.entity.EntityPersister;
2021
import org.hibernate.proxy.HibernateProxy;
2122
import org.hibernate.proxy.LazyInitializer;
23+
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
2224
import org.hibernate.type.CompositeType;
2325
import org.hibernate.type.EntityType;
2426
import org.hibernate.type.Type;
2527

28+
import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
29+
import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker;
2630
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
2731
import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture;
2832
import 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,

hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -302,20 +302,27 @@ private CompletionStage<Object> currentVersion(SharedSessionContractImplementor
302302
}
303303
}
304304

305-
@SuppressWarnings("unchecked")
306-
default <E, T> CompletionStage<T> reactiveInitializeLazyProperty(Attribute<E, T> field, E entity, SharedSessionContractImplementor session) {
307-
String fieldName = field.getName();
308-
Object result = initializeLazyProperty( fieldName, entity, session );
309-
if (result instanceof CompletionStage) {
305+
default <E, T> CompletionStage<T> reactiveInitializeLazyProperty(
306+
Attribute<E, T> field, E entity,
307+
SharedSessionContractImplementor session) {
308+
return reactiveInitializeLazyProperty( field.getName(), entity, session );
309+
}
310+
311+
default <E, T> CompletionStage<T> reactiveInitializeLazyProperty(
312+
String field,
313+
E entity,
314+
SharedSessionContractImplementor session) {
315+
final Object result = initializeLazyProperty( field, entity, session );
316+
if ( result instanceof CompletionStage ) {
310317
return (CompletionStage<T>) result;
311318
}
312-
else if (result instanceof PersistentCollection) {
319+
else if ( result instanceof PersistentCollection ) {
313320
// Hibernate core doesn't set the field when it's a
314321
// collection. That's inconsistent with what happens
315322
// for other lazy fields, so let's set the field here
316-
String[] propertyNames = getPropertyNames();
317-
for (int index=0; index<propertyNames.length; index++) {
318-
if ( propertyNames[index].equals(fieldName) ) {
323+
final String[] propertyNames = getPropertyNames();
324+
for ( int index=0; index<propertyNames.length; index++ ) {
325+
if ( propertyNames[index].equals( field ) ) {
319326
setPropertyValue( entity, index, result );
320327
break;
321328
}
@@ -326,7 +333,7 @@ else if (result instanceof PersistentCollection) {
326333
// is transparent there. That's too painful in our
327334
// case, since it would make the user have to call
328335
// fetch() twice, so fetch it here.
329-
PersistentCollection collection = (PersistentCollection) result;
336+
final PersistentCollection<?> collection = (PersistentCollection<?>) result;
330337
return collection.wasInitialized()
331338
? completedFuture( (T) collection )
332339
: ((ReactiveSession) session).reactiveInitializeCollection( collection, false )

hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import jakarta.persistence.metamodel.Attribute;
2020

21-
import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
2221

2322
/**
2423
* A reactive {@link EntityPersister}. Supports non-blocking insert/update/delete operations.
@@ -141,12 +140,15 @@ CompletionStage<Void> reactiveProcessUpdateGenerated(
141140
*/
142141
CompletionStage<Object[]> reactiveGetDatabaseSnapshot(Object id, SharedSessionContractImplementor session);
143142

144-
default <E, T> CompletionStage<T> reactiveInitializeLazyProperty(
143+
<E, T> CompletionStage<T> reactiveInitializeLazyProperty(
145144
Attribute<E, T> field,
146145
E entity,
147-
SharedSessionContractImplementor session) {
148-
return nullFuture();
149-
}
146+
SharedSessionContractImplementor session);
147+
148+
<E, T> CompletionStage<T> reactiveInitializeLazyProperty(
149+
String field,
150+
E entity,
151+
SharedSessionContractImplementor session);
150152

151153
CompletionStage<Object> reactiveInitializeEnhancedEntityUsedAsProxy(
152154
Object entity,

0 commit comments

Comments
 (0)