diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6da68667..8192be43b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.1.8.Final" -hibernateOrmGradlePluginVersion = "7.1.8.Final" +hibernateOrmVersion = "7.1.9.Final" +hibernateOrmGradlePluginVersion = "7.1.9.Final" jacksonDatabindVersion = "2.20.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java index c40ee20d3..3a0d2e723 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java @@ -100,109 +100,124 @@ public CompletionStage reactiveResolveInstance(EntityDelayedFetchInitializ final RowProcessingState rowProcessingState = data.getRowProcessingState(); data.setEntityIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); - CompletionStage stage = voidFuture(); if ( data.getEntityIdentifier() == null ) { data.setInstance( null ); data.setState( State.MISSING ); + return voidFuture(); } - else { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - - final EntityPersister entityPersister = referencedModelPart.getEntityMappingType().getEntityPersister(); - final EntityPersister concreteDescriptor; - if ( getDiscriminatorAssembler() != null ) { - concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), entityPersister ); - if ( concreteDescriptor == null ) { - // If we find no discriminator it means there's no entity in the target table - if ( !referencedModelPart.isOptional() ) { - throw new FetchNotFoundException( entityPersister.getEntityName(), data.getEntityIdentifier() ); - } - data.setInstance( null ); - data.setState( State.MISSING ); - return voidFuture(); - } - } - else { - concreteDescriptor = entityPersister; - } - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - if ( isSelectByUniqueKey() ) { - final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); - final Type uniqueKeyPropertyType = uniqueKeyPropertyName == null - ? concreteDescriptor.getIdentifierType() - : session.getFactory().getRuntimeMetamodels().getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - uniqueKeyPropertyName, - data.getEntityIdentifier(), - uniqueKeyPropertyType, - session.getFactory() - ); - data.setInstance( persistenceContext.getEntity( euk ) ); - if ( data.getInstance() == null ) { - // For unique-key mappings, we always use bytecode-laziness if possible, - // because we can't generate a proxy based on the unique key yet - if ( referencedModelPart.isLazy() ) { - data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); - } - else { - stage = stage - .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( - uniqueKeyPropertyName, - data.getEntityIdentifier(), - session - ) ) - .thenAccept( data::setInstance ) - .thenAccept( v -> { - // If the entity was not in the Persistence Context, but was found now, - // add it to the Persistence Context - if ( data.getInstance() != null ) { - persistenceContext.addEntity( euk, data.getInstance() ); - } - } ); - } + final EntityPersister entityPersister = referencedModelPart.getEntityMappingType().getEntityPersister(); + final EntityPersister concreteDescriptor; + if ( getDiscriminatorAssembler() != null ) { + concreteDescriptor = determineConcreteEntityDescriptor( + rowProcessingState, + getDiscriminatorAssembler(), + entityPersister + ); + if ( concreteDescriptor == null ) { + // If we find no discriminator it means there's no entity in the target table + if ( !referencedModelPart.isOptional() ) { + throw new FetchNotFoundException( entityPersister.getEntityName(), data.getEntityIdentifier() ); } - stage = stage.thenAccept( v -> { - if ( data.getInstance() != null ) { - data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); - } - } ); + data.setInstance( null ); + data.setState( State.MISSING ); + return voidFuture(); } - else { - final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); - final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); - if ( holder != null && holder.getEntity() != null ) { - data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); - } - // For primary key based mappings we only use bytecode-laziness if the attribute is optional, - // because the non-optionality implies that it is safe to have a proxy - else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { + } + else { + concreteDescriptor = entityPersister; + } + + return initialize( data, null, concreteDescriptor ); + } + + private CompletionStage initialize( + ReactiveEntityDelayedFetchInitializerData data, + EntityKey entityKey, + EntityPersister concreteDescriptor) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + if ( isSelectByUniqueKey() ) { + final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); + final Type uniqueKeyPropertyType = uniqueKeyPropertyName == null + ? concreteDescriptor.getIdentifierType() + : session.getFactory() + .getRuntimeMetamodels() + .getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + uniqueKeyPropertyName, + data.getEntityIdentifier(), + uniqueKeyPropertyType, + session.getFactory() + ); + data.setInstance( persistenceContext.getEntity( euk ) ); + CompletionStage stage = voidFuture(); + if ( data.getInstance() == null ) { + // For unique-key mappings, we always use bytecode-laziness if possible, + // because we can't generate a proxy based on the unique key yet + if ( referencedModelPart.isLazy() ) { data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { - stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup - .extract( session ) - .reactiveInternalLoad( - concreteDescriptor.getEntityName(), - data.getEntityIdentifier(), - false, - false - ) + stage = stage + .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) + .reactiveLoadByUniqueKey( + uniqueKeyPropertyName, + data.getEntityIdentifier(), + session + ) ) .thenAccept( data::setInstance ) - ); + .thenAccept( v -> { + // If the entity was not in the Persistence Context, but was found now, + // add it to the Persistence Context + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); + } + } ); + } + } + return stage.thenAccept( v -> { + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } - stage = stage - .thenAccept( v -> { - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); - if ( lazyInitializer != null ) { - lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); - } - } ); + } ); + } + else { + CompletionStage stage = voidFuture(); + final EntityKey ek = entityKey == null + ? new EntityKey( data.getEntityIdentifier(), concreteDescriptor ) + : entityKey; + final EntityHolder holder = persistenceContext.getEntityHolder( ek ); + if ( holder != null && holder.getEntity() != null ) { + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); + } + // For primary key based mappings we only use bytecode-laziness if the attribute is optional, + // because the non-optionality implies that it is safe to have a proxy + else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + } + else { + stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup + .extract( session ) + .reactiveInternalLoad( + concreteDescriptor.getEntityName(), + data.getEntityIdentifier(), + false, + false + ) + .thenAccept( data::setInstance ) + ); } + return stage + .thenAccept( v -> { + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); + if ( lazyInitializer != null ) { + lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); + } + } ); } - return stage; } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index c6ee36363..5b573bb25 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -51,6 +51,7 @@ import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage; import static org.hibernate.reactive.util.impl.CompletionStages.trueFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -329,14 +330,26 @@ private void postResolveInstance(ReactiveEntityInitializerData data) { @Override public CompletionStage reactiveInitializeInstance(EntityInitializerData data) { - if ( data.getState() != State.RESOLVED ) { - return voidFuture(); - } - if ( !skipInitialization( data ) ) { - assert consistentInstance( data ); - return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); + if ( data.getState() == State.RESOLVED ) { + if ( !skipInitialization( data ) ) { + assert consistentInstance( data ); + return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); + } + else { + if ( data.getRowProcessingState().needsResolveState() ) { + // A sub-initializer might have taken responsibility for this entity, + // but we still need to resolve the state to correctly populate a query cache + resolveState( data ); + } + final ReactiveEntityInitializerData reactiveData = (ReactiveEntityInitializerData) data; + if ( getEntityDescriptor().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && reactiveData.getEntityHolder().getEntityInitializer() != this + && reactiveData.getEntityHolder().isInitialized() ) { + updateInitializedEntityInstance( data ); + } + } + data.setState( State.INITIALIZED ); } - data.setState( State.INITIALIZED ); return voidFuture(); } @@ -348,7 +361,7 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI return reactiveExtractConcreteTypeStateValues( data ) .thenAccept( resolvedEntityState -> { - + rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingEntityHolder( data.getEntityHolder() ); preLoad( data, resolvedEntityState ); if ( isPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ) ) { @@ -372,10 +385,6 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; - // from the perspective of Hibernate, an entity is read locked as soon as it is read - // so regardless of the requested lock mode, we upgrade to at least the read level - final LockMode lockModeToAcquire = data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); - final EntityEntry entityEntry = persistenceContext.addEntry( data.getEntityInstanceForNotify(), Status.LOADING, @@ -383,7 +392,7 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI rowId, data.getEntityKey().getIdentifier(), version, - lockModeToAcquire, + lockModeToAcquire( data ), true, data.getConcreteDescriptor(), false @@ -404,16 +413,15 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI statistics.loadEntity( data.getConcreteDescriptor().getEntityName() ); } } - updateCaches( - data, - session, - session.getPersistenceContextInternal(), - resolvedEntityState, - version - ); + updateCaches( data, session, session.getPersistenceContextInternal(), resolvedEntityState, version ); } ); } + // Hibernate ORM has a similar method, but it checks if we are in a transaction first + private static LockMode lockModeToAcquire(ReactiveEntityInitializerData data) { + return data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); + } + protected CompletionStage reactiveExtractConcreteTypeStateValues(ReactiveEntityInitializerData data) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); final Object[] values = new Object[data.getConcreteDescriptor().getNumberOfAttributeMappings()]; @@ -447,7 +455,9 @@ protected CompletionStage reactiveResolveEntityInstance1(ReactiveEntityIni else { data.setInstance( proxy ); if ( Hibernate.isInitialized( data.getInstance() ) ) { - data.setState( State.INITIALIZED ); + if ( data.getEntityHolder().isInitialized() ) { + data.setState( State.INITIALIZED ); + } data.setEntityInstanceForNotify( Hibernate.unproxy( data.getInstance() ) ); } else { @@ -755,7 +765,6 @@ private CompletionStage initializeId(ReactiveEntityInitializerData data return trueFuture(); } else { - //noinspection unchecked final Initializer initializer = (Initializer) getIdentifierAssembler().getInitializer(); if ( initializer != null ) { final InitializerData subData = initializer.getData( rowProcessingState ); @@ -807,8 +816,7 @@ public CompletionStage forEachReactiveSubInitializer( BiFunction, RowProcessingState, CompletionStage> consumer, InitializerData data) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); - return voidFuture() - .thenCompose( v -> { + return supplyStage( () -> { if ( getKeyAssembler() != null ) { final Initializer initializer = getKeyAssembler().getInitializer(); if ( initializer != null ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index 6d5ea5418..94ee455a4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java @@ -128,9 +128,15 @@ protected CompletionStage reactiveInitialize(EntitySelectFetchInitializerD final RowProcessingState rowProcessingState = data.getRowProcessingState(); final SharedSessionContractImplementor session = rowProcessingState.getSession(); final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); + return initialiaze( data, persistenceContext.getEntityHolder( entityKey ), (ReactiveQueryProducer) session, persistenceContext ); + } + + private CompletionStage initialiaze( + ReactiveEntitySelectFetchInitializerData data, + EntityHolder holder, + ReactiveQueryProducer session, + PersistenceContext persistenceContext) { if ( holder != null ) { data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); if ( holder.getEntityInitializer() == null ) { @@ -154,7 +160,7 @@ else if ( data.getInstance() == null ) { data.setState( State.INITIALIZED ); final String entityName = concreteDescriptor.getEntityName(); - return ( (ReactiveQueryProducer) session ).reactiveInternalLoad( + return session.reactiveInternalLoad( entityName, data.getEntityIdentifier(), true, @@ -176,10 +182,10 @@ else if ( data.getInstance() == null ) { throw new FetchNotFoundException( entityName, data.getEntityIdentifier() ); } } - rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible( + persistenceContext.claimEntityHolderIfPossible( new EntityKey( data.getEntityIdentifier(), concreteDescriptor ), null, - rowProcessingState.getJdbcValuesSourceProcessingState(), + data.getRowProcessingState().getJdbcValuesSourceProcessingState(), this ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java index 388baec9a..98772286a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.util.impl.CompletionStages; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -117,6 +118,7 @@ public void testEagerParentFetch(VertxTestContext context) { } @Test + @Disabled("A regression after the upgrade to ORM 7.1.9.Final: https://github.com/hibernate/hibernate-reactive/issues/2808") public void testEagerFetchQuery(VertxTestContext context) { Node basik = new Node( "Child" );