diff --git a/README.md b/README.md index e2113509d..6b59aaab1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Hibernate Reactive has been tested with: - CockroachDB 22.1 - MS SQL Server 2019 - Oracle 21.3 -- [Hibernate ORM][] 6.3.2.Final +- [Hibernate ORM][] 6.4.0.Final - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.0 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.0 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.0 diff --git a/build.gradle b/build.gradle index 6bd47671a..9032a3485 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ version = projectVersion // ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT ext { if ( !project.hasProperty('hibernateOrmVersion') ) { - hibernateOrmVersion = '6.3.2.Final' + hibernateOrmVersion = '6.4.0.Final' } if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) { // Same as ORM as default diff --git a/gradle.properties b/gradle.properties index b0d63e2b4..45eac1069 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false #enableMavenLocalRepo = true # Override default Hibernate ORM version -#hibernateOrmVersion = 6.2.3.Final +#hibernateOrmVersion = 6.4.0.Final # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from # a remote repository -#hibernateOrmGradlePluginVersion = 6.2.3.Final +#hibernateOrmGradlePluginVersion = 6.4.0.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java index 1b63da2c3..bb0666ebe 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java @@ -29,7 +29,6 @@ import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.proxy.HibernateProxy; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; @@ -40,6 +39,7 @@ import org.hibernate.type.EntityType; import org.hibernate.type.Type; +import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -111,9 +111,9 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( * Cascade an action from the parent entity instance to all its children. */ public CompletionStage cascade() throws HibernateException { - return voidFuture().thenCompose(v -> { + return voidFuture().thenCompose( v -> { CacheMode cacheMode = eventSource.getCacheMode(); - if ( action==CascadingActions.DELETE ) { + if ( action == CascadingActions.DELETE ) { eventSource.setCacheMode( CacheMode.GET ); } eventSource.getPersistenceContextInternal().incrementCascadeLevel(); @@ -125,18 +125,23 @@ public CompletionStage cascade() throws HibernateException { } private CompletionStage cascadeInternal() throws HibernateException { - if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); } final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); + final EntityEntry entry = persistenceContext.getEntry( parent ); + if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED && persister.getBytecodeEnhancementMetadata() + .isEnhancedForLazyLoading() ) { + return voidFuture(); + } final Type[] types = persister.getPropertyTypes(); final String[] propertyNames = persister.getPropertyNames(); final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); + for ( int i = 0; i < types.length; i++) { final CascadeStyle style = cascadeStyles[ i ]; final String propertyName = propertyNames[ i ]; @@ -154,7 +159,7 @@ private CompletionStage cascadeInternal() throws HibernateException { // If parent is a detached entity being merged, // then parent will not be in the PersistenceContext // (so lazy attributes must not be initialized). - if ( persistenceContext.getEntry( parent ) == null ) { + if ( entry == null ) { // parent was not in the PersistenceContext continue; } @@ -295,91 +300,88 @@ private void cascadeLogicalOneToOneOrphanRemoval( final String propertyName, final boolean isCascadeDeleteEnabled) throws HibernateException { - // potentially we need to handle orphan deletes for one-to-ones here... - if ( isLogicalOneToOne( type ) ) { - // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require - // orphan checking - if ( style.hasOrphanDelete() && action.deleteOrphans() ) { - // value is orphaned if loaded state for this property shows not null - // because it is currently null. - final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); - final EntityEntry entry = persistenceContext.getEntry( parent ); - if ( entry != null && entry.getStatus() != Status.SAVING ) { - Object loadedValue; - if ( componentPath == null ) { - // association defined on entity - loadedValue = entry.getLoadedValue( propertyName ); + // We have a physical or logical one-to-one. See if the attribute cascade settings and action-type require + // orphan checking + if ( style.hasOrphanDelete() && action.deleteOrphans() ) { + // value is orphaned if loaded state for this property shows not null + // because it is currently null. + final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); + final EntityEntry entry = persistenceContext.getEntry( parent ); + if ( entry != null && entry.getStatus() != Status.SAVING ) { + Object loadedValue; + if ( componentPath == null ) { + // association defined on entity + loadedValue = entry.getLoadedValue( propertyName ); + } + else { + // association defined on component + // Since the loadedState in the EntityEntry is a flat domain type array + // We first have to extract the component object and then ask the component type + // recursively to give us the value of the sub-property of that object + final AttributeMapping propertyType = entry.getPersister().findAttributeMapping( componentPath.get( 0) ); + if ( propertyType instanceof ComponentType) { + loadedValue = entry.getLoadedValue( componentPath.get( 0 ) ); + ComponentType componentType = (ComponentType) propertyType; + if ( componentPath.size() != 1 ) { + for ( int i = 1; i < componentPath.size(); i++ ) { + final int subPropertyIndex = componentType.getPropertyIndex( componentPath.get( i ) ); + loadedValue = componentType.getPropertyValue( loadedValue, subPropertyIndex ); + componentType = (ComponentType) componentType.getSubtypes()[subPropertyIndex]; + } + } + + loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) ); } else { - // association defined on component - // Since the loadedState in the EntityEntry is a flat domain type array - // We first have to extract the component object and then ask the component type - // recursively to give us the value of the sub-property of that object - final AttributeMapping propertyType = entry.getPersister().findAttributeMapping( componentPath.get( 0) ); - if ( propertyType instanceof ComponentType) { - loadedValue = entry.getLoadedValue( componentPath.get( 0 ) ); - ComponentType componentType = (ComponentType) propertyType; - if ( componentPath.size() != 1 ) { - for ( int i = 1; i < componentPath.size(); i++ ) { - final int subPropertyIndex = componentType.getPropertyIndex( componentPath.get( i ) ); - loadedValue = componentType.getPropertyValue( loadedValue, subPropertyIndex ); - componentType = (ComponentType) componentType.getSubtypes()[subPropertyIndex]; - } - } + // Association is probably defined in an element collection, so we can't do orphan removals + loadedValue = null; + } + } - loadedValue = componentType.getPropertyValue( loadedValue, componentType.getPropertyIndex( propertyName ) ); - } - else { - // Association is probably defined in an element collection, so we can't do orphan removals - loadedValue = null; + // orphaned if the association was nulled (child == null) or receives a new value while the + // entity is managed (without first nulling and manually flushing). + if ( child == null || loadedValue != null && child != loadedValue ) { + EntityEntry valueEntry = persistenceContext.getEntry( loadedValue ); + + if ( valueEntry == null && isHibernateProxy( loadedValue ) ) { + // un-proxy and re-associate for cascade operation + // useful for @OneToOne defined as FetchType.LAZY + loadedValue = persistenceContext.unproxyAndReassociate( loadedValue ); + valueEntry = persistenceContext.getEntry( loadedValue ); + + // HHH-11965 + // Should the unwrapped proxy value be equal via reference to the entity's property value + // provided by the 'child' variable, we should not trigger the orphan removal of the + // associated one-to-one. + if ( child == loadedValue ) { + // do nothing + return; } } - // orphaned if the association was nulled (child == null) or receives a new value while the - // entity is managed (without first nulling and manually flushing). - if ( child == null || loadedValue != null && child != loadedValue ) { - EntityEntry valueEntry = persistenceContext.getEntry( loadedValue ); - - if ( valueEntry == null && loadedValue instanceof HibernateProxy ) { - // un-proxy and re-associate for cascade operation - // useful for @OneToOne defined as FetchType.LAZY - loadedValue = persistenceContext.unproxyAndReassociate( loadedValue ); - valueEntry = persistenceContext.getEntry( loadedValue ); - - // HHH-11965 - // Should the unwrapped proxy value be equal via reference to the entity's property value - // provided by the 'child' variable, we should not trigger the orphan removal of the - // associated one-to-one. - if ( child == loadedValue ) { - // do nothing - return; - } + if ( valueEntry != null ) { + final EntityPersister persister = valueEntry.getPersister(); + final String entityName = persister.getEntityName(); + if ( LOG.isTraceEnabled() ) { + LOG.tracev( + "Deleting orphaned entity instance: {0}", + infoString( entityName, persister.getIdentifier( loadedValue, eventSource ) ) + ); } - if ( valueEntry != null ) { - final EntityPersister persister = valueEntry.getPersister(); - final String entityName = persister.getEntityName(); - if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Deleting orphaned entity instance: {0}", - infoString( entityName, persister.getIdentifier( loadedValue, eventSource ) ) - ); - } - - final Object loaded = loadedValue; - if ( type.isAssociationType() - && ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) { - // If FK direction is to-parent, we must remove the orphan *before* the queued update(s) - // occur. Otherwise, replacing the association on a managed entity, without manually - // nulling and flushing, causes FK constraint violations. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) ); - } - else { - // Else, we must delete after the updates. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) ); - } + final Object loaded = loadedValue; + if ( type.isAssociationType() + && ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) { + // If FK direction is to-parent, we must remove the orphan *before* the queued update(s) + // occur. Otherwise, replacing the association on a managed entity, without manually + // nulling and flushing, causes FK constraint violations. + stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) + .reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) ); + } + else { + // Else, we must delete after the updates. + stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) + .reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java index c178203e8..b43d2753c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java @@ -135,7 +135,7 @@ protected void logFlushResults(FlushEvent event) { persistenceContext.getCollectionEntriesSize() ); new EntityPrinter( session.getFactory() ).toString( - persistenceContext.getEntitiesByKey().entrySet() + persistenceContext.getEntityHoldersByKey().entrySet() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java index 1035075fb..5704f3faf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java @@ -5,9 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.collectionInfoString; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; @@ -29,6 +26,9 @@ import org.hibernate.sql.results.internal.ResultsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + public class DefaultReactiveInitializeCollectionEventListener implements InitializeCollectionEventListener { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); @@ -144,7 +144,7 @@ private boolean initializeCollectionFromCache( final SessionFactoryImplementor factory = source.getFactory(); final CollectionDataAccess cacheAccessStrategy = persister.getCacheAccessStrategy(); final Object ck = cacheAccessStrategy.generateCacheKey( id, persister, factory, source.getTenantIdentifier() ); - final Object ce = CacheHelper.fromSharedCache( source, ck, cacheAccessStrategy ); + final Object ce = CacheHelper.fromSharedCache( source, ck, persister, cacheAccessStrategy ); final StatisticsImplementor statistics = factory.getStatistics(); if ( statistics.isStatisticsEnabled() ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index 4303aec7c..a68754536 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -60,12 +60,12 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event) throws Hibern @Override public void onRefresh(RefreshEvent event) throws HibernateException { - throw new UnsupportedOperationException(); + throw LOG.nonReactiveMethodCall( "reactiveOnRefresh" ); } @Override public void onRefresh(RefreshEvent event, RefreshContext refreshedAlready) throws HibernateException { - throw new UnsupportedOperationException(); + throw LOG.nonReactiveMethodCall( "reactiveOnRefresh" ); } /** @@ -83,16 +83,25 @@ public CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContex if ( detached ) { // Hibernate Reactive doesn't support detached instances in refresh() - throw new IllegalArgumentException("unmanaged instance passed to refresh()"); + throw new IllegalArgumentException( "Unmanaged instance passed to refresh()" ); } - return ( (ReactiveSession) source ).reactiveFetch( event.getObject(), true ) + return ( (ReactiveSession) source ) + .reactiveFetch( event.getObject(), true ) .thenCompose( entity -> reactiveOnRefresh( event, refreshedAlready, entity ) ); } - private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready, Object entity) { + private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshContext refreshedAlready, Object object) { final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); + if ( persistenceContext.reassociateIfUninitializedProxy( object ) ) { + if ( isTransient( event, source, object ) ) { + source.setReadOnly( object, source.isDefaultReadOnly() ); + } + return voidFuture(); + } + + Object entity = persistenceContext.unproxyAndReassociate( object ); if ( !refreshedAlready.add( entity) ) { LOG.trace( "Already refreshed" ); return voidFuture(); @@ -169,6 +178,11 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte } ); } + private static boolean isTransient(RefreshEvent event, EventSource source, Object object) { + final String entityName = event.getEntityName(); + return entityName != null ? !source.contains( entityName, object) : !source.contains(object); + } + private static void evictEntity(Object entity, EntityPersister persister, Object id, EventSource source) { if ( persister.canWriteToCache() ) { Object previousVersion = null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java index 383cb8ab8..96016f86f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java @@ -5,11 +5,10 @@ */ package org.hibernate.reactive.loader.ast.internal; -import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.sql.exec.internal.BaseExecutionContext; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; /** * Copy and paste of {@link org.hibernate.loader.ast.internal.ExecutionContextWithSubselectFetchHandler} @@ -25,9 +24,9 @@ public ExecutionContextWithSubselectFetchHandler(SharedSessionContractImplemento } @Override - public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) { + public void registerLoadingEntityHolder(EntityHolder holder) { if ( subSelectFetchableKeysHandler != null ) { - subSelectFetchableKeysHandler.addKey( entityKey, entry ); + subSelectFetchableKeysHandler.addKey( holder ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java index 8606f6b65..90edf7d0d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java @@ -12,7 +12,7 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.CollectionKey; -import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -31,7 +31,6 @@ import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; /** @@ -144,9 +143,8 @@ public CollectionKey getCollectionKey() { } @Override - public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) { - subSelectFetchableKeysHandler.addKey( entityKey, entry ); + public void registerLoadingEntityHolder(EntityHolder holder) { + subSelectFetchableKeysHandler.addKey( holder ); } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java index 9278bc01f..83c3292f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdLoadPlan.java @@ -10,7 +10,7 @@ import org.hibernate.LockOptions; import org.hibernate.engine.spi.CollectionKey; -import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -30,7 +30,6 @@ import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; public class ReactiveSingleIdLoadPlan extends SingleIdLoadPlan> { @@ -121,8 +120,8 @@ public Object getEntityId() { } @Override - public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) { - ExecutionContext.super.registerLoadingEntityEntry( entityKey, entry ); + public void registerLoadingEntityHolder(EntityHolder holder) { + ExecutionContext.super.registerLoadingEntityHolder( holder ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/SingleIdExecutionContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/SingleIdExecutionContext.java index 38310febf..f3cb06e1c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/SingleIdExecutionContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/SingleIdExecutionContext.java @@ -6,13 +6,12 @@ package org.hibernate.reactive.loader.ast.internal; import org.hibernate.LockOptions; -import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.sql.exec.internal.BaseExecutionContext; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; /** * A copy of {@link org.hibernate.loader.ast.internal.SingleIdExecutionContext} in Hibernate ORM. @@ -65,8 +64,7 @@ public LockOptions getLockOptions() { } @Override - public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) { - subSelectFetchableKeysHandler.addKey( entityKey, entry ); + public void registerLoadingEntityHolder(EntityHolder holder) { + subSelectFetchableKeysHandler.addKey( holder ); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java index 13ca55f41..3acdff04c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java @@ -6,13 +6,16 @@ package org.hibernate.reactive.metamodel.mapping.internal; import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; +import org.hibernate.reactive.sql.results.internal.ReactiveEntityDelayedFetchImpl; import org.hibernate.reactive.sql.results.internal.domain.ReactiveCircularFetchImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroupProducer; +import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; @@ -48,12 +51,17 @@ public EntityFetch generateFetch( if ( entityFetch instanceof EntityFetchJoinedImpl ) { return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) entityFetch ); } - if (entityFetch instanceof EntityFetchSelectImpl) { + if ( entityFetch instanceof EntityFetchSelectImpl ) { return new ReactiveEntityFetchSelectImpl( (EntityFetchSelectImpl) entityFetch ); } return entityFetch; } + @Override + public AttributeMetadata getAttributeMetadata() { + return super.getAttributeMetadata(); + } + @Override public Fetch resolveCircularFetch( NavigablePath fetchablePath, @@ -67,6 +75,22 @@ public Fetch resolveCircularFetch( return fetch; } + @Override + protected EntityFetch buildEntityDelayedFetch( + FetchParent fetchParent, + ToOneAttributeMapping fetchedAttribute, + NavigablePath navigablePath, + DomainResult keyResult, + boolean selectByUniqueKey) { + return new ReactiveEntityDelayedFetchImpl( + fetchParent, + fetchedAttribute, + navigablePath, + keyResult, + selectByUniqueKey + ); + } + @Override public ReactiveToOneAttributeMapping copy( ManagedMappingType declaringType, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java index 3e6738d7b..ddea801a6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java @@ -16,7 +16,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.AbstractEntityPersister; -import org.hibernate.persister.entity.mutation.DeleteCoordinator; +import org.hibernate.persister.entity.mutation.DeleteCoordinatorStandard; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; @@ -30,7 +30,7 @@ import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -public class ReactiveDeleteCoordinator extends DeleteCoordinator { +public class ReactiveDeleteCoordinator extends DeleteCoordinatorStandard { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); @@ -60,7 +60,7 @@ public CompletionStage coordinateReactiveDelete(Object entity, Object id, } @Override - protected void doDynamicDelete(Object entity, Object id, Object[] loadedState, SharedSessionContractImplementor session) { + protected void doDynamicDelete(Object entity, Object id, Object rowId, Object[] loadedState, SharedSessionContractImplementor session) { stage = new CompletableFuture<>(); final MutationOperationGroup operationGroup = generateOperationGroup( null, loadedState, true, session ); final ReactiveMutationExecutor mutationExecutor = mutationExecutor( session, operationGroup ); @@ -72,10 +72,7 @@ protected void doDynamicDelete(Object entity, Object id, Object[] loadedState, S mutationExecutor.getPreparedStatementDetails( tableName ); } } - - applyLocking( null, loadedState, mutationExecutor, session ); - applyId( id, null, mutationExecutor, getStaticDeleteGroup(), session ); - + applyDynamicDeleteTableDetails( id, rowId, loadedState, mutationExecutor, operationGroup, session ); mutationExecutor.executeReactive( entity, null, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java index 7ff39d5be..346faa49b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java @@ -29,6 +29,7 @@ import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.reactive.persister.entity.mutation.GeneratorValueUtil.generateValue; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @Internal @@ -47,16 +48,17 @@ public Object coordinateInsert(Object id, Object[] values, Object entity, Shared public CompletionStage coordinateReactiveInsert(Object id, Object[] currentValues, Object entity, SharedSessionContractImplementor session) { return reactivePreInsertInMemoryValueGeneration( currentValues, entity, session ) - .thenCompose( v -> entityPersister().getEntityMetamodel().isDynamicInsert() - ? doDynamicInserts( id, currentValues, entity, session ) - : doStaticInserts( id, currentValues, entity, session ) - ); + .thenCompose( needsDynamicInsert -> { + final boolean forceIdentifierBinding = entityPersister().getGenerator().generatedOnExecution() && id != null; + return entityPersister().getEntityMetamodel().isDynamicInsert() || needsDynamicInsert || forceIdentifierBinding + ? doDynamicInserts( id, currentValues, entity, session, forceIdentifierBinding ) + : doStaticInserts( id, currentValues, entity, session ); + } ); } - private CompletionStage reactivePreInsertInMemoryValueGeneration(Object[] currentValues, Object entity, SharedSessionContractImplementor session) { - CompletionStage stage = voidFuture(); - + private CompletionStage reactivePreInsertInMemoryValueGeneration(Object[] currentValues, Object entity, SharedSessionContractImplementor session) { final EntityMetamodel entityMetamodel = entityPersister().getEntityMetamodel(); + CompletionStage stage = falseFuture(); if ( entityMetamodel.hasPreInsertGeneratedValues() ) { final Generator[] generators = entityMetamodel.getGenerators(); for ( int i = 0; i < generators.length; i++ ) { @@ -67,11 +69,20 @@ private CompletionStage reactivePreInsertInMemoryValueGeneration(Object[] && generator.generatesOnInsert() ) { final Object currentValue = currentValues[i]; final BeforeExecutionGenerator beforeGenerator = (BeforeExecutionGenerator) generator; - stage = stage.thenCompose( v -> generateValue( session, entity, currentValue, beforeGenerator, INSERT ) - .thenAccept( generatedValue -> { - currentValues[index] = generatedValue; - entityPersister().setValue( entity, index, generatedValue ); - } ) ); + stage = stage + .thenCompose( foundStateDependentGenerator -> generateValue( + session, + entity, + currentValue, + beforeGenerator, + INSERT + ) + .thenApply( generatedValue -> { + currentValues[index] = generatedValue; + entityPersister().setValue( entity, index, generatedValue ); + return foundStateDependentGenerator || beforeGenerator.generatedOnExecution(); + } ) + ); } } } @@ -126,9 +137,14 @@ protected CompletionStage decomposeForReactiveInsert( } @Override - protected CompletionStage doDynamicInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session) { + protected CompletionStage doDynamicInserts( + Object id, + Object[] values, + Object object, + SharedSessionContractImplementor session, + boolean forceIdentifierBinding) { final boolean[] insertability = getPropertiesToInsert( values ); - final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability ); + final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, object, session, forceIdentifierBinding ); final ReactiveMutationExecutor mutationExecutor = getReactiveMutationExecutor( session, insertGroup ); final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java index 04b50217a..0f20ad292 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java @@ -294,6 +294,7 @@ protected void doDynamicUpdate( this.updateResultStage = new CompletableFuture<>(); // Create the JDBC operation descriptors final MutationOperationGroup dynamicUpdateGroup = generateDynamicUpdateGroup( + entity, id, rowId, oldValues, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProvider.java index c340859b2..cf4ad315f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcMultiTenantConnectionProvider.java @@ -15,14 +15,14 @@ * * @author Gavin King */ -public class NoJdbcMultiTenantConnectionProvider extends AbstractMultiTenantConnectionProvider { +public class NoJdbcMultiTenantConnectionProvider extends AbstractMultiTenantConnectionProvider { @Override protected ConnectionProvider getAnyConnectionProvider() { throw new UnsupportedOperationException("Not using JDBC"); } @Override - protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) { + protected ConnectionProvider selectConnectionProvider(T tenantIdentifier) { throw new UnsupportedOperationException("Not using JDBC"); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index f1899fb96..ca82ff284 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.query.sqm.internal; import java.sql.PreparedStatement; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -16,9 +17,9 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan; @@ -27,7 +28,6 @@ import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.SqmTranslation; import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; @@ -35,13 +35,18 @@ import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.SqlSelectionImpl; @@ -49,14 +54,13 @@ import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.usingLockingAndPaging; public class ReactiveSimpleDeleteQueryPlan extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - - private JdbcOperationQueryDelete jdbcDelete; private final EntityMappingType entityDescriptor; private final SqmDeleteStatement sqmDelete; private final DomainParameterXref domainParameterXref; - private SqmTranslation sqmInterpretation; + private JdbcOperationQueryMutation jdbcOperation; + private SqmTranslation sqmInterpretation; private Map, Map, List>> jdbcParamsXref; public ReactiveSimpleDeleteQueryPlan( @@ -70,12 +74,9 @@ public ReactiveSimpleDeleteQueryPlan( } @Override - protected SqlAstTranslator createDeleteTranslator(DomainQueryExecutionContext executionContext) { + protected SqlAstTranslator createTranslator(DomainQueryExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createSimpleDeleteTranslator( + final SqmTranslator translator = factory.getQueryEngine().getSqmTranslatorFactory().createMutationTranslator( sqmDelete, executionContext.getQueryOptions(), domainParameterXref, @@ -84,15 +85,39 @@ protected SqlAstTranslator createDeleteTranslator(Doma factory ); - sqmInterpretation = translator.translate(); + sqmInterpretation = (SqmTranslation) translator.translate(); this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildDeleteTranslator( factory, sqmInterpretation.getSqlAst() ); + return factory.getJdbcServices() + .getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, mutationStatement() ); + } + + private MutationStatement mutationStatement() { + if ( entityDescriptor.getSoftDeleteMapping() == null ) { + return sqmInterpretation.getSqlAst(); + } + final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); + final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); + final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); + final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping ); + //noinspection rawtypes,unchecked + final JdbcLiteral jdbcLiteral = new JdbcLiteral( + columnMapping.getDeletedLiteralValue(), + columnMapping.getJdbcMapping() + ); + final Assignment assignment = new Assignment( columnReference, jdbcLiteral ); + + return new UpdateStatement( + targetTable, + Collections.singletonList( assignment ), + sqlDeleteAst.getRestriction() + ); } @Override @@ -100,9 +125,9 @@ public CompletionStage executeReactiveUpdate(DomainQueryExecutionContex BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); - SqlAstTranslator deleteTranslator = null; - if ( jdbcDelete == null ) { - deleteTranslator = createDeleteTranslator( executionContext ); + SqlAstTranslator sqlAstTranslator = null; + if ( jdbcOperation == null ) { + sqlAstTranslator = createTranslator( executionContext ); } final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( @@ -122,13 +147,13 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter StandardReactiveJdbcMutationExecutor.INSTANCE .executeReactive( - jdbcDelete, + jdbcOperation, jdbcParameterBindings, session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, ReactiveSimpleDeleteQueryPlan::doNothing, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index 6455a5f0f..aaf1be3e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -143,7 +142,6 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec final int size = getSqmStatement().getInsertionTargetPaths().size(); final List, Assignment>> targetPathColumns = new ArrayList<>( size ); final List targetPathCteColumns = new ArrayList<>( size ); - final Map, MappingModelExpressible> paramTypeResolutions = new LinkedHashMap<>(); final NamedTableReference entityTableReference = new NamedTableReference( getCteTable().getTableExpression(), TemporaryTable.DEFAULT_ALIAS, @@ -178,14 +176,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec }, sqmInsertStatement, entityDescriptor, - insertingTableGroup, - (sqmParameter, mappingType, jdbcParameters) -> { - parameterResolutions.computeIfAbsent( - sqmParameter, - k -> new ArrayList<>( 1 ) - ).add( jdbcParameters ); - paramTypeResolutions.put( sqmParameter, mappingType ); - } + insertingTableGroup ); final boolean assignsId = targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ); @@ -576,9 +567,9 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) factory.getRuntimeMetamodels().getMappingMetamodel(), navigablePath -> sqmConverter.getMutatingTableGroup(), new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") + @Override public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); + return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); } }, executionContext.getSession() diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java index eafbff628..c83a6883f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java @@ -14,23 +14,38 @@ import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.sql.results.spi.ResultsConsumer; + +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; /** * @see org.hibernate.query.spi.SelectQueryPlan */ public interface ReactiveSelectQueryPlan extends SelectQueryPlan { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - @Override default List performList(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "performReactiveList" ); + throw make( Log.class, MethodHandles.lookup() ) + .nonReactiveMethodCall( "performReactiveList" ); } @Override default ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "" ); + throw make( Log.class, MethodHandles.lookup() ) + .nonReactiveMethodCall( "" ); + } + + default T executeQuery(DomainQueryExecutionContext executionContext, ResultsConsumer resultsConsumer) { + throw make( Log.class, MethodHandles.lookup() ) + .nonReactiveMethodCall( "reactiveExecuteQuery" ); + } + + /** + * Execute the query + */ + default CompletionStage reactiveExecuteQuery(DomainQueryExecutionContext executionContext, ResultsConsumer resultsConsumer) { + return failedFuture( new UnsupportedOperationException() ); } /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java index 7b3278992..bed4725c5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java @@ -46,8 +46,12 @@ public interface ReactiveStatelessSession extends ReactiveQueryProducer, Reactiv CompletionStage reactiveRefresh(Object entity); + CompletionStage reactiveRefresh(String entityName, Object entity); + CompletionStage reactiveRefresh(Object entity, LockMode lockMode); + CompletionStage reactiveRefresh(String entityName, Object entity, LockMode lockMode); + CompletionStage reactiveInsertAll(Object... entities); CompletionStage reactiveInsertAll(int batchSize, Object... entities); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 6ae79f209..ea67e89e1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -5,16 +5,15 @@ */ package org.hibernate.reactive.session.impl; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.TransientObjectException; import org.hibernate.UnknownEntityTypeException; -import org.hibernate.UnknownProfileException; import org.hibernate.UnresolvableObjectException; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; @@ -36,6 +35,7 @@ import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.StatelessSessionImpl; import org.hibernate.jpa.spi.NativeQueryTupleTransformer; +import org.hibernate.loader.ast.spi.CascadingFetchProfile; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; @@ -60,7 +60,6 @@ import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.pool.BatchingConnection; @@ -84,6 +83,7 @@ import jakarta.persistence.criteria.CriteriaUpdate; import static java.lang.Boolean.TRUE; +import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.Versioning.incrementVersion; @@ -92,14 +92,18 @@ import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.loader.ast.spi.CascadingFetchProfile.REFRESH; +import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.id.impl.IdentifierGeneration.castToIdentifierType; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister.forceInitialize; import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * An {@link ReactiveStatelessSession} implemented by extension of @@ -109,23 +113,9 @@ */ public class ReactiveStatelessSessionImpl extends StatelessSessionImpl implements ReactiveStatelessSession { - private static final LoadQueryInfluencers NO_INFLUENCERS = new LoadQueryInfluencers() { - @Override - public String getInternalFetchProfile() { - return null; - } - - @Override - public void setInternalFetchProfile(String internalFetchProfile) { - } - - @Override - public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { - return false; - } - }; + private static final Log LOG = make( Log.class, lookup() ); - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private final LoadQueryInfluencers influencers; private final ReactiveConnection reactiveConnection; @@ -138,6 +128,7 @@ public ReactiveStatelessSessionImpl(SessionFactoryImpl factory, SessionCreationO reactiveConnection = connection; persistenceContext = new ReactivePersistenceContextAdapter( this ); batchingHelperSession = new ReactiveStatelessSessionImpl( factory, options, reactiveConnection, persistenceContext ); + influencers = new LoadQueryInfluencers( factory ); } /** @@ -155,6 +146,7 @@ private ReactiveStatelessSessionImpl( ? connection : new BatchingConnection( connection, batchSize ); batchingHelperSession = this; + influencers = new LoadQueryInfluencers( factory ); } private LockMode getNullSafeLockMode(LockMode lockMode) { @@ -221,7 +213,7 @@ public CompletionStage reactiveGet(Class entityClass, Object @Override public LoadQueryInfluencers getLoadQueryInfluencers() { - return NO_INFLUENCERS; + return influencers; } @Override @@ -334,14 +326,28 @@ private CompletionStage executeReactiveUpdate(Object entity) { @Override public CompletionStage reactiveRefresh(Object entity) { - return reactiveRefresh( entity, LockMode.NONE ); + return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE ); + } + + @Override + public CompletionStage reactiveRefresh(String entityName, Object entity) { + return reactiveRefresh( entityName, entity, LockMode.NONE ); } @Override public CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { - final ReactiveEntityPersister persister = getEntityPersister( null, entity ); + return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE ); + } + + @Override + public CompletionStage reactiveRefresh(String entityName, Object entity, LockMode lockMode) { + final ReactiveEntityPersister persister = getEntityPersister( entityName, entity ); final Object id = persister.getIdentifier( entity, this ); + if ( LOG.isTraceEnabled() ) { + LOG.tracev( "Refreshing transient {0}", infoString( persister, id, getFactory() ) ); + } + if ( persister.canWriteToCache() ) { final EntityDataAccess cacheAccess = persister.getCacheAccessStrategy(); if ( cacheAccess != null ) { @@ -355,18 +361,26 @@ public CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { } } - final String previousFetchProfile = getLoadQueryInfluencers().getInternalFetchProfile(); - getLoadQueryInfluencers().setInternalFetchProfile( "refresh" ); - return persister.reactiveLoad( id, entity, getNullSafeLockMode( lockMode ), this ) + return fromInternalFetchProfile( REFRESH, () -> persister.reactiveLoad( id, entity, getNullSafeLockMode( lockMode ), this ) ) .thenAccept( result -> { if ( getPersistenceContext().isLoadFinished() ) { getPersistenceContext().clear(); } UnresolvableObjectException.throwIfNull( result, id, persister.getEntityName() ); - } ) - .whenComplete( (v, e) -> getLoadQueryInfluencers().setInternalFetchProfile( previousFetchProfile ) ); + } ); } + private CompletionStage fromInternalFetchProfile( + CascadingFetchProfile cascadingFetchProfile, + Supplier> supplier) { + CascadingFetchProfile previous = getLoadQueryInfluencers().getEnabledCascadingFetchProfile(); + return voidFuture() + .thenCompose( v -> { + getLoadQueryInfluencers().setEnabledCascadingFetchProfile( cascadingFetchProfile ); + return supplier.get(); + } ) + .whenComplete( (o, throwable) -> getLoadQueryInfluencers().setEnabledCascadingFetchProfile( previous ) ); + } /** * @see StatelessSessionImpl#upsert(Object) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 5649d7e37..901e25c41 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -115,7 +115,7 @@ public CompletionStage executeQuery( persistenceContext.setDefaultReadOnly( readOnly ); } - return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, statementCreator, resultsConsumer ) + return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, statementCreator, resultsConsumer ) .thenCompose( list -> ( (ReactivePersistenceContextAdapter) persistenceContext ) // only initialize non-lazy collections after everything else has been refreshed .reactiveInitializeNonLazyCollections() diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java index 5b2b86cd4..e470f7b22 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java @@ -11,6 +11,7 @@ import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -29,7 +30,6 @@ import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.stat.spi.StatisticsImplementor; @@ -82,36 +82,32 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState @Override public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( isMissing() || isEntityInitialized() ) { - return voidFuture(); + if ( !isMissing() && !isEntityInitialized() ) { + final LazyInitializer lazyInitializer = extractLazyInitializer( getEntityInstance() ); + return voidFuture() + .thenCompose( v -> { + if ( lazyInitializer != null ) { + return lazyInitialize( rowProcessingState, lazyInitializer ); + } + else { + // FIXME: Read from cache if possible + return initializeEntity( getEntityInstance(), rowProcessingState ) + .thenAccept( ignore -> setEntityInstanceForNotify( getEntityInstance() ) ); + } + } ) + .thenAccept( o -> { + notifyResolutionListeners( getEntityInstanceForNotify() ); + setEntityInitialized( true ); + } ); } - - preLoad( rowProcessingState ); - - final LazyInitializer lazyInitializer = extractLazyInitializer( getEntityInstance() ); - return voidFuture() - .thenCompose( v -> { - if ( lazyInitializer != null ) { - return lazyInitialize( rowProcessingState, lazyInitializer ); - } - else { - // FIXME: Read from cache if possible - return initializeEntity( getEntityInstance(), rowProcessingState ) - .thenAccept( ignore -> setEntityInstanceForNotify( getEntityInstance() ) ); - } - } ) - .thenAccept( o -> { - notifyResolutionListeners( getEntityInstanceForNotify() ); - setEntityInitialized( true ); - } ); + return voidFuture(); } - private CompletionStage lazyInitialize( - ReactiveRowProcessingState rowProcessingState, - LazyInitializer lazyInitializer) { + private CompletionStage lazyInitialize(ReactiveRowProcessingState rowProcessingState, LazyInitializer lazyInitializer) { final SharedSessionContractImplementor session = rowProcessingState.getSession(); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - Object instance = persistenceContext.getEntity( getEntityKey() ); + final EntityHolder holder = persistenceContext.getEntityHolder( getEntityKey() ); + Object instance = holder.getEntity(); if ( instance == null ) { return resolveInstance( rowProcessingState, lazyInitializer, persistenceContext ); } @@ -124,9 +120,9 @@ private CompletionStage resolveInstance( ReactiveRowProcessingState rowProcessingState, LazyInitializer lazyInitializer, PersistenceContext persistenceContext) { - final Object instance = resolveInstance( + final Object instance = super.resolveInstance( getEntityKey().getIdentifier(), - persistenceContext.getLoadContexts().findLoadingEntityEntry( getEntityKey() ), + persistenceContext.getEntityHolder( getEntityKey() ), rowProcessingState ); return initializeEntity( instance, rowProcessingState ) @@ -136,28 +132,6 @@ private CompletionStage resolveInstance( } ); } - private Object resolveInstance( - Object entityIdentifier, - LoadingEntityEntry existingLoadingEntry, - RowProcessingState rowProcessingState) { - if ( isOwningInitializer() ) { - assert existingLoadingEntry == null || existingLoadingEntry.getEntityInstance() == null; - return resolveEntityInstance( entityIdentifier, rowProcessingState ); - } - else { - // the entity is already being loaded elsewhere - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ), - existingLoadingEntry.getEntityInitializer() - ); - } - return existingLoadingEntry.getEntityInstance(); - } - } - private CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { if ( !skipInitialization( toInitialize, rowProcessingState ) ) { assert consistentInstance( toInitialize, rowProcessingState ); 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 b06c2cff2..24d3f1194 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 @@ -8,6 +8,7 @@ import java.util.concurrent.CompletionStage; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; @@ -24,7 +25,6 @@ import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.entity.EntityInitializer; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.type.Type; @@ -72,10 +72,9 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); final PersistenceContext persistenceContext = session.getPersistenceContext(); - final LoadingEntityEntry loadingEntityLocally = persistenceContext.getLoadContexts() - .findLoadingEntityEntry( entityKey ); - if ( loadingEntityLocally != null ) { - setEntityInstance( loadingEntityLocally.getEntityInstance() ); + final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); + if ( holder != null && holder.getEntity() != null ) { + setEntityInstance( persistenceContext.proxyFor( holder ) ); } if ( getEntityInstance() == null ) { setEntityInstance( persistenceContext.getEntity( entityKey ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java index d2a75d9d6..53048a3c9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java @@ -5,16 +5,9 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; -import java.lang.invoke.MethodHandles; - import org.hibernate.FetchNotFoundException; import org.hibernate.LockMode; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; @@ -33,11 +26,9 @@ public class ReactiveEntityJoinedFetchInitializer extends ReactiveAbstractEntity private static final String CONCRETE_NAME = ReactiveEntityJoinedFetchInitializer.class.getSimpleName(); - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final EntityValuedFetchable referencedFetchable; private final DomainResultAssembler keyAssembler; private final NotFoundAction notFoundAction; - private final boolean isEnhancedForLazyLoading; public ReactiveEntityJoinedFetchInitializer( EntityResultGraphNode resultDescriptor, @@ -61,17 +52,7 @@ public ReactiveEntityJoinedFetchInitializer( ); this.referencedFetchable = referencedFetchable; this.notFoundAction = notFoundAction; - this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); - - if ( getConcreteDescriptor() != null ) { - this.isEnhancedForLazyLoading = getConcreteDescriptor() - .getBytecodeEnhancementMetadata() - .isEnhancedForLazyLoading(); - } - else { - this.isEnhancedForLazyLoading = false; - } } @Override @@ -113,18 +94,6 @@ public void resolveKey(RowProcessingState rowProcessingState) { } } - @Override - protected Object getProxy(PersistenceContext persistenceContext) { - ModelPart referencedModelPart = getInitializedPart(); - if ( referencedModelPart instanceof ToOneAttributeMapping ) { - final boolean unwrapProxy = ( (ToOneAttributeMapping) referencedModelPart ).isUnwrapProxy() && isEnhancedForLazyLoading; - if ( unwrapProxy ) { - return null; - } - } - return super.getProxy( persistenceContext ); - } - @Override protected String getSimpleConcreteImplName() { return CONCRETE_NAME; 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 45af27613..d648d2d11 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 @@ -9,11 +9,11 @@ import java.util.concurrent.CompletionStage; import org.hibernate.FetchNotFoundException; +import org.hibernate.Hibernate; import org.hibernate.annotations.NotFoundAction; -import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; @@ -30,12 +30,11 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; +import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; -import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.internal.log.LoggingHelper.toLoggableString; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -101,6 +100,12 @@ public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingSta return voidFuture(); } + final EntityInitializer parentEntityInitializer = parentAccess.findFirstEntityInitializer(); + if ( parentEntityInitializer != null && parentEntityInitializer.isEntityInitialized() ) { + isInitialized = true; + return voidFuture(); + } + if ( !isAttributeAssignableToConcreteDescriptor() ) { return voidFuture(); } @@ -125,45 +130,39 @@ public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingSta final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - entityInstance = persistenceContext.getEntity( entityKey ); - if ( entityInstance != null ) { - if ( isPersistentAttributeInterceptable( entityInstance ) ) { - final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entityInstance ).$$_hibernate_getInterceptor(); - if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( entityInstance, null ); - } - } - isInitialized = true; - return voidFuture(); - } - - final LoadingEntityEntry existingLoadingEntry = session - .getPersistenceContext() - .getLoadContexts() - .findLoadingEntityEntry( entityKey ); - - if ( existingLoadingEntry != null ) { - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( + final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); + if ( holder != null ) { + if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Found existing loading entry [%s] - using loading instance", CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ) + toLoggableString( + getNavigablePath(), + entityIdentifier + ) ); } - this.entityInstance = existingLoadingEntry.getEntityInstance(); - - if ( existingLoadingEntry.getEntityInitializer() != this ) { + entityInstance = holder.getEntity(); + if ( holder.getEntityInitializer() == null ) { + if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { + isInitialized = true; + return voidFuture(); + } + } + else if ( holder.getEntityInitializer() != this ) { // the entity is already being loaded elsewhere - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( + if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), - existingLoadingEntry.getEntityInitializer() + holder.getEntityInitializer() ); } - - // EARLY EXIT!!! + isInitialized = true; + return voidFuture(); + } + else if ( entityInstance == null ) { isInitialized = true; return voidFuture(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java new file mode 100644 index 000000000..c025903f6 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java @@ -0,0 +1,37 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.internal; + +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; + +public class ReactiveEntityDelayedFetchImpl extends EntityDelayedFetchImpl { + public ReactiveEntityDelayedFetchImpl( + FetchParent fetchParent, + ToOneAttributeMapping fetchedAttribute, + NavigablePath navigablePath, + DomainResult keyResult, + boolean selectByUniqueKey) { + super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey ); + } + + @Override + protected Initializer buildEntityDelayedFetchInitializer( + FetchParentAccess parentAccess, + NavigablePath navigablePath, + ToOneAttributeMapping entityValuedModelPart, + boolean selectByUniqueKey, + DomainResultAssembler resultAssembler) { + return new ReactiveEntityDelayedFetchInitializer( parentAccess, navigablePath, entityValuedModelPart, selectByUniqueKey, resultAssembler ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java index 965d77010..d2cb94727 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java @@ -127,9 +127,7 @@ private CompletionStage coordinateInitializers(ReactiveRowProcessingState } @Override - @SuppressWarnings("ForLoopReplaceableByForEach") public void finishUp(JdbcValuesSourceProcessingState processingState) { - processingState.registerSubselect(); initializers.endLoading( processingState.getExecutionContext() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java index 24f783aae..5a563c093 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java @@ -102,19 +102,22 @@ public CompletionStage> consume( : new Results<>( domainResultJavaType ); Supplier> addToResultsSupplier = addToResultsSupplier( results, rowReader, rowProcessingState, processingOptions, isEntityResultType ); + final int[] readRows = { 0 }; return whileLoop( () -> rowProcessingState.next() .thenCompose( hasNext -> { if ( hasNext ) { return addToResultsSupplier.get() .thenApply( unused -> { rowProcessingState.finishRowProcessing(); + readRows[0]++; return true; } ); + } return falseFuture(); } ) ) - .thenApply( v -> finishUp( results, jdbcValuesSourceProcessingState, rowReader, persistenceContext, queryOptions ) ) + .thenApply( v -> finishUp( results, jdbcValuesSourceProcessingState, rowReader, persistenceContext, queryOptions, readRows[0] ) ) .handle( (list, ex) -> { end( jdbcValues, session, jdbcValuesSourceProcessingState, rowReader, persistenceContext, ex ); return list; @@ -175,10 +178,11 @@ private List finishUp( Results results, JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, ReactiveRowReader rowReader, PersistenceContext persistenceContext, - QueryOptions queryOptions) { + QueryOptions queryOptions, + int readRows) { try { rowReader.finishUp( jdbcValuesSourceProcessingState ); - jdbcValuesSourceProcessingState.finishUp(); + jdbcValuesSourceProcessingState.finishUp( readRows > 0 ); } finally { persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState );