From f03234ea695b7551d9e0109a9cab86e7c21bc7da Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 5 Jun 2024 15:02:03 +0200 Subject: [PATCH 1/6] [#1935] Fixes for Hibernate ORM 6.5.3.Final --- .../ReactiveToOneAttributeMapping.java | 5 +++++ ...veInformationSchemaBasedExtractorImpl.java | 19 +++++++++++++++++++ .../ReactiveConnectionPoolTest.java | 6 ++++++ 3 files changed, 30 insertions(+) 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 3acdff04c..09acc9bb9 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 @@ -97,4 +97,9 @@ public ReactiveToOneAttributeMapping copy( TableGroupProducer declaringTableGroupProducer) { return new ReactiveToOneAttributeMapping( super.copy( declaringType, declaringTableGroupProducer ) ); } + + @Override + protected Object lazyInitialize(Object domainValue) { + return domainValue; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java index 5daafc28e..8ba013cb5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.provider.service; +import java.lang.invoke.MethodHandles; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -14,6 +15,8 @@ import org.hibernate.boot.model.TruthValue; import org.hibernate.boot.model.naming.DatabaseIdentifier; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.tool.schema.extract.internal.AbstractInformationExtractorImpl; import org.hibernate.tool.schema.extract.internal.ColumnInformationImpl; import org.hibernate.tool.schema.extract.spi.ColumnInformation; @@ -29,6 +32,8 @@ */ public abstract class AbstractReactiveInformationSchemaBasedExtractorImpl extends AbstractInformationExtractorImpl { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + public AbstractReactiveInformationSchemaBasedExtractorImpl(ExtractionContext extractionContext) { super( extractionContext ); } @@ -154,6 +159,20 @@ protected T processCatalogsResultSet(ExtractionContext.ResultSetProcessor ); } + @Override + protected T processCrossReferenceResultSet( + String parentCatalog, + String parentSchema, + String parentTable, + String foreignCatalog, + String foreignSchema, + String foreignTable, + ExtractionContext.ResultSetProcessor processor) { + // This method has been added as fix for https://hibernate.atlassian.net/browse/HHH-18221 + // The issue is only for Informix that we don't currently support. + throw LOG.notYetImplemented(); + } + @Override protected T processSchemaResultSet( String catalog, diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java index 1e755aa62..babae4bd4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java @@ -12,6 +12,7 @@ import org.hibernate.engine.jdbc.internal.JdbcServicesImpl; import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.pool.ReactiveConnectionPool; @@ -64,6 +65,11 @@ private ReactiveConnectionPool configureAndStartPool(Map config) public SqlStatementLogger getSqlStatementLogger() { return new SqlStatementLogger(); } + + @Override + public SqlExceptionHelper getSqlExceptionHelper() { + return new SqlExceptionHelper( true ); + } } ); DefaultSqlClientPool reactivePool = new DefaultSqlClientPool(); reactivePool.injectServices( registryExtension.getServiceRegistry() ); From 8f05a8c8f3718ef616e78ffe138f50affb002317 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 11 Jun 2024 10:57:26 +0200 Subject: [PATCH 2/6] [#1932] Support ReactiveSelectionQuery#getResultCount for HQL I need some changes in ORM before I can support it for native queries. --- .../org/hibernate/reactive/mutiny/Mutiny.java | 11 ++++ .../reactive/mutiny/impl/MutinyQueryImpl.java | 5 ++ .../mutiny/impl/MutinySelectionQueryImpl.java | 5 ++ .../query/ReactiveSelectionQuery.java | 2 + .../spi/ReactiveAbstractSelectionQuery.java | 16 +++++- .../sql/internal/ReactiveNativeQueryImpl.java | 10 ++++ .../ConcreteSqmSelectReactiveQueryPlan.java | 55 +++++++++++++++++- .../sqm/internal/ReactiveQuerySqmImpl.java | 11 ++++ .../ReactiveSqmSelectionQueryImpl.java | 6 ++ .../sqm/spi/ReactiveSelectQueryPlan.java | 3 +- .../internal/ReactiveInitializersList.java | 6 ++ .../spi/ReactiveSingleResultConsumer.java | 46 +++++++++++++++ .../org/hibernate/reactive/stage/Stage.java | 56 ++++++++++++------- .../reactive/stage/impl/StageQueryImpl.java | 5 ++ .../stage/impl/StageSelectionQueryImpl.java | 5 ++ 15 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index 22ec48da1..65b0ad906 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -194,6 +194,17 @@ interface SelectionQuery extends AbstractQuery { */ Uni getSingleResultOrNull(); + /** + * Determine the size of the query result list that would be + * returned by calling {@link #getResultList()} with no + * {@linkplain #getFirstResult() offset} or + * {@linkplain #getMaxResults() limit} applied to the query. + * + * @return the size of the list that would be returned + */ + @Incubating + Uni getResultCount(); + /** * Asynchronously execute this query, returning the query results * as a {@link List}, via a {@link Uni}. If the query diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java index 9cb3a0287..d0608ba5f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java @@ -47,6 +47,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public Uni getResultCount() { + return uni( delegate::getReactiveResultCount ); + } + @Override public Uni> getResultList() { return uni( delegate::getReactiveResultList ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java index 0d037e0ed..b001e618a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java @@ -45,6 +45,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public Uni getResultCount() { + return uni( delegate::getReactiveResultCount ); + } + @Override public Uni> getResultList() { return uni( delegate::getReactiveResultList ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java index 81508c13b..2b347b3de 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java @@ -49,6 +49,8 @@ default CompletionStage> getReactiveResultList() { CompletionStage getReactiveSingleResultOrNull(); + CompletionStage getReactiveResultCount(); + CompletionStage reactiveUnique(); CompletionStage> reactiveUniqueResultOptional(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java index 69ebe52a1..f01b5d315 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java @@ -21,6 +21,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.hql.internal.QuerySplitter; +import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; +import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; @@ -33,6 +35,7 @@ import org.hibernate.reactive.query.sqm.internal.AggregatedSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.internal.ConcreteSqmSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; +import org.hibernate.reactive.sql.results.spi.ReactiveSingleResultConsumer; import org.hibernate.sql.results.internal.TupleMetadata; import jakarta.persistence.NoResultException; @@ -146,6 +149,17 @@ public CompletionStage getReactiveSingleResult() { .exceptionally( this::convertException ); } + public CompletionStage getReactiveResultsCount(SqmSelectStatement sqmStatement, DomainQueryExecutionContext domainQueryExecutionContext) { + final DelegatingDomainQueryExecutionContext context = new DelegatingDomainQueryExecutionContext( domainQueryExecutionContext ) { + @Override + public QueryOptions getQueryOptions() { + return QueryOptions.NONE; + } + }; + return buildConcreteSelectQueryPlan( sqmStatement.createCountQuery(), Long.class, getQueryOptions() ) + .reactiveExecuteQuery( context, new ReactiveSingleResultConsumer<>() ); + } + private R reactiveSingleResult(List list) { if ( list.isEmpty() ) { throw new NoResultException( String.format( "No result found for query [%s]", getQueryString() ) ); @@ -269,7 +283,7 @@ private ReactiveSelectQueryPlan buildAggregatedSelectQueryPlan(SqmSelectState return new AggregatedSelectReactiveQueryPlan<>( aggregatedQueryPlans ); } - private ReactiveSelectQueryPlan buildConcreteSelectQueryPlan( + public ReactiveSelectQueryPlan buildConcreteSelectQueryPlan( SqmSelectStatement concreteSqmStatement, Class resultType, QueryOptions queryOptions) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index a441b4b55..1c97f847a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -93,6 +93,16 @@ private T getNull() { return null; } + @Override + public long getResultCount() { + throw LOG.nonReactiveMethodCall( "getReactiveResultCount()" ); + } + + @Override + public CompletionStage getReactiveResultCount() { + throw LOG.notYetImplemented(); + } + private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSessionContractImplementor session) { return new ReactiveAbstractSelectionQuery<>( this::getQueryOptions, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 16135b9a2..858e2d209 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -36,6 +36,7 @@ import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; @@ -59,6 +60,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQueryPlan implements ReactiveSelectQueryPlan { + private final SqmInterpreter> executeQueryInterpreter; private final SqmInterpreter, Void> listInterpreter; private final RowTransformer rowTransformer; @@ -80,6 +82,8 @@ public ConcreteSqmSelectReactiveQueryPlan( this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions ); this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> listInterpreter( hql, domainParameterXref, executionContext, sqmInterpretation, jdbcParameterBindings, rowTransformer ); + this.executeQueryInterpreter = (resultsConsumer, executionContext, sqmInterpretation, jdbcParameterBindings) -> + executeQueryInterpreter( hql, domainParameterXref, executionContext, sqmInterpretation, jdbcParameterBindings, rowTransformer, resultsConsumer ); } private static CompletionStage> listInterpreter( @@ -110,6 +114,40 @@ private static CompletionStage> listInterpreter( .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } + private static CompletionStage executeQueryInterpreter( + String hql, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext executionContext, + CacheableSqmInterpretation sqmInterpretation, + JdbcParameterBindings jdbcParameterBindings, + RowTransformer rowTransformer, + ReactiveResultsConsumer resultsConsumer) { + final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block + Supplier fetchHandlerSupplier = () -> SubselectFetch + .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); + return completedFuture( fetchHandlerSupplier ) + .thenApply( Supplier::get ) + .thenCompose( subSelectFetchKeyHandler -> session + .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) + .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE + .executeQuery( jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), + rowTransformer, + null, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareQueryStatement( sql, false, null ), + resultsConsumer + ) + ) + ) + .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); + } + @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { throw new UnsupportedOperationException(); @@ -119,10 +157,21 @@ public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, Doma public CompletionStage> reactivePerformList(DomainQueryExecutionContext executionContext) { return executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ? completedFuture( emptyList() ) - : withCacheableSqmInterpretation( executionContext, listInterpreter ); + : withCacheableSqmInterpretation( executionContext, null, listInterpreter ); + } + + @Override + public CompletionStage reactiveExecuteQuery( + DomainQueryExecutionContext executionContext, + ReactiveResultsConsumer resultsConsumer) { + return withCacheableSqmInterpretation( + executionContext, + resultsConsumer, + (SqmInterpreter>) (SqmInterpreter) executeQueryInterpreter + ); } - private CompletionStage withCacheableSqmInterpretation(DomainQueryExecutionContext executionContext, SqmInterpreter interpreter) { + private CompletionStage withCacheableSqmInterpretation(DomainQueryExecutionContext executionContext, X context, SqmInterpreter interpreter) { // NOTE : VERY IMPORTANT - intentional double-lock checking // The other option would be to leverage `java.util.concurrent.locks.ReadWriteLock` // to protect access. However, synchronized is much simpler here. We will verify @@ -162,7 +211,7 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } - return interpreter.interpret( null, executionContext, localCopy, jdbcParameterBindings ); + return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java index 3c23e6c76..fbe3295d6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java @@ -139,6 +139,17 @@ public CompletionStage getReactiveSingleResult() { return selectionQueryDelegate.getReactiveSingleResult(); } + @Override + public long getResultCount() { + throw LOG.nonReactiveMethodCall( "getReactiveResultCount()" ); + } + + @Override + public CompletionStage getReactiveResultCount() { + return selectionQueryDelegate + .getReactiveResultsCount( ( (SqmSelectStatement) getSqmStatement() ).createCountQuery(), this ); + } + @Override public CompletionStage getReactiveSingleResultOrNull() { return selectionQueryDelegate.getReactiveSingleResultOrNull(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java index 138f996a4..13e167697 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java @@ -212,6 +212,12 @@ public R getSingleResultOrNull() { return selectionQueryDelegate.getSingleResultOrNull(); } + @Override + public CompletionStage getReactiveResultCount() { + return selectionQueryDelegate + .getReactiveResultsCount( getSqmStatement().createCountQuery(), this ); + } + @Override public List getResultList() { return selectionQueryDelegate.getResultList(); 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 c83a6883f..6ef50764c 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,6 +14,7 @@ import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; import org.hibernate.sql.results.spi.ResultsConsumer; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -44,7 +45,7 @@ default T executeQuery(DomainQueryExecutionContext executionContext, Results /** * Execute the query */ - default CompletionStage reactiveExecuteQuery(DomainQueryExecutionContext executionContext, ResultsConsumer resultsConsumer) { + default CompletionStage reactiveExecuteQuery(DomainQueryExecutionContext executionContext, ReactiveResultsConsumer resultsConsumer) { return failedFuture( new UnsupportedOperationException() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index d861cddc4..e752a49ca 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -56,6 +56,12 @@ public void finishUpRow(final RowProcessingState rowProcessingState) { } } + public void startLoading(final RowProcessingState rowProcessingState) { + for ( int i = initializers.length - 1; i >= 0; i-- ) { + initializers[i].startLoading( rowProcessingState ); + } + } + public CompletionStage initializeInstance(final ReactiveRowProcessingState rowProcessingState) { return loop( initializers, initializer -> { if ( initializer instanceof ReactiveInitializer ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java new file mode 100644 index 000000000..ad3d17853 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -0,0 +1,46 @@ +/* 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.spi; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.Incubating; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; + +@Incubating +public class ReactiveSingleResultConsumer implements ReactiveResultsConsumer { + + @Override + public CompletionStage consume( + ReactiveValuesResultSet jdbcValues, + SharedSessionContractImplementor session, + JdbcValuesSourceProcessingOptions processingOptions, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + ReactiveRowProcessingState rowProcessingState, + ReactiveRowReader rowReader) { + rowReader.getReactiveInitializersList().startLoading( rowProcessingState ); + return rowProcessingState.next() + .thenCompose( hasNext -> rowReader + .reactiveReadRow( rowProcessingState, processingOptions ) + .thenApply( result -> { + rowProcessingState.finishRowProcessing( true ); + rowReader.finishUp( jdbcValuesSourceProcessingState ); + jdbcValuesSourceProcessingState.finishUp( false ); + return result; + } ) + ); + } + + @Override + public boolean canResultsBeCached() { + return false; + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index 5fc1d671e..47da9f6cf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -5,20 +5,18 @@ */ package org.hibernate.reactive.stage; -import jakarta.persistence.CacheRetrieveMode; -import jakarta.persistence.CacheStoreMode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.FlushModeType; -import jakarta.persistence.LockModeType; -import jakarta.persistence.Parameter; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.Metamodel; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import java.util.function.Function; + import org.hibernate.Cache; -import org.hibernate.*; +import org.hibernate.CacheMode; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.Incubating; +import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.AbstractPersistentCollection; import org.hibernate.collection.spi.PersistentCollection; @@ -38,16 +36,25 @@ import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.stat.Statistics; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; -import java.util.function.Function; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.Parameter; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; +import jakarta.persistence.metamodel.Metamodel; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.internal.util.LockModeConverter.convertToLockMode; -import static org.hibernate.jpa.internal.util.CacheModeHelper.*; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheMode; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheRetrieveMode; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheStoreMode; /** * An API for Hibernate Reactive where non-blocking operations are @@ -188,6 +195,17 @@ interface SelectionQuery extends AbstractQuery { */ CompletionStage getSingleResultOrNull(); + /** + * Determine the size of the query result list that would be + * returned by calling {@link #getResultList()} with no + * {@linkplain #getFirstResult() offset} or + * {@linkplain #getMaxResults() limit} applied to the query. + * + * @return the size of the list that would be returned + */ + @Incubating + CompletionStage getResultCount(); + /** * Asynchronously execute this query, returning the query results * as a {@link List}, via a {@link CompletionStage}. If the query diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java index 3a4948275..fb022c499 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java @@ -38,6 +38,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public CompletionStage getResultCount() { + return delegate.getReactiveResultCount(); + } + @Override public CompletionStage> getResultList() { return delegate.getReactiveResultList(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java index f28bfd9d0..6a1be5d08 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java @@ -41,6 +41,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public CompletionStage getResultCount() { + return delegate.getReactiveResultCount(); + } + @Override public CompletionStage> getResultList() { return delegate.getReactiveResultList(); From 9a73ead27e82ba462920d570e540aba8c3941d94 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 11 Jun 2024 10:58:54 +0200 Subject: [PATCH 3/6] [#1932] Test for `#getReactiveResultCount` with HQL --- .../org/hibernate/reactive/QueryTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java index 054264011..00e42e82e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java @@ -607,6 +607,70 @@ public void testSingleResultOrNullNonUniqueException(VertxTestContext context) { ); } + @Test + public void testSelectionQueryGetResultCountWithStage(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testQueryGetResultCountWithStage(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .setMaxResults( 1 ) + .setFirstResult( 1 ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testSelectionQueryGetResultCountWithMutiny(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testQueryGetResultCountWithMutiny(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( author1, author2 ) ) + .chain( () -> getMutinySessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .getResultCount() ) ) + .invoke( count -> assertEquals( 2L, count ) ) + .chain( () -> getMutinySessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .setMaxResults( 1 ) + .setFirstResult( 1 ) + .getResultCount() ) ) + .invoke( count -> assertEquals( 2L, count ) ) + ); + } + @NamedNativeQuery( name = SQL_NAMED_QUERY, resultClass = Object[].class, From 99fdcbbb907f81fb975e82277df24ba98a2a09e3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 10:13:43 +0200 Subject: [PATCH 4/6] [#1930] Add snapshot repository for the examples We didn't include it in the past because we wanted to keep the examples simpler. But, we need to be able to test the examples with the snapshots and it's disabled on the `main` branch as default --- examples/native-sql-example/build.gradle | 5 +++++ examples/session-example/build.gradle | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index 2d6cd5b94..fabdbc7fa 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -7,6 +7,11 @@ buildscript { // Useful for local development, it should be disabled otherwise mavenLocal() } + // Optional: Enables snapshots repository + // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep + if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } mavenCentral() } } diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 05e9dcc04..7cf7fb5f6 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -7,6 +7,11 @@ buildscript { // Useful for local development, it should be disabled otherwise mavenLocal() } + // Optional: Enables snapshots repository + // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep + if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } mavenCentral() } } From a1b22548a3533c46167a847dcd8e7c2bb5c07342 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 11:52:49 +0200 Subject: [PATCH 5/6] [#1949] Update project version to 2.4.0-SNAPSHOT --- gradle/version.properties | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/Db2ReactiveTest.java.qute | 2 +- tooling/jbang/Example.java | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gradle/version.properties b/gradle/version.properties index 06df703b4..61c0b3764 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=2.3.2-SNAPSHOT \ No newline at end of file +projectVersion=2.4.0-SNAPSHOT diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 9d4b00e23..da57a01d1 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:cockroachdb:1.19.8 diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index 7a4cf3c54..0d9c2d904 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:db2:1.19.8 diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index e7527d409..1d87dcd5d 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -9,7 +9,7 @@ //DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.9} //DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.9} //DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 39882a8ac..78e9026de 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:mariadb:1.19.8 diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 17c35ac24..0f7aa9b6b 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:mysql:1.19.8 diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 132f9440e..07df20583 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:postgresql:1.19.8 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 93efc3e85..25a6c1f1f 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -10,7 +10,7 @@ //DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.9} //DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.9} //DEPS io.vertx:vertx-unit:${vertx.version:4.5.9} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:postgresql:1.19.8 From 6fc8164f512714ea38d750064d8ccc24f9ccb7dc Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 12 Jul 2024 10:39:17 +0200 Subject: [PATCH 6/6] [#1949] Upgrade Hibernate ORM to 6.6.0.CR2 To make this work I had to refactor the Cascade class. I don't know what wasn't working, but a test was failing and couldn't figure out what was wrong. I think the class now is more similar to the one in Hibernate ORM, it's easier to debug, logs the messages in the correct order, and it fixes the issues I had. --- README.md | 4 +- build.gradle | 2 +- .../reactive/engine/ReactiveActionQueue.java | 63 +- .../reactive/engine/impl/Cascade.java | 639 +++++++------ .../reactive/engine/impl/CascadingAction.java | 15 +- .../engine/impl/CascadingActions.java | 113 ++- .../ReactiveCollectionRecreateAction.java | 28 +- .../impl/ReactiveCollectionUpdateAction.java | 49 +- .../ReactiveEntityIdentityInsertAction.java | 5 + .../impl/ReactiveEntityInsertAction.java | 48 +- .../ReactiveEntityRegularInsertAction.java | 10 + .../ReactivePersistenceContextAdapter.java | 17 +- ...AbstractReactiveFlushingEventListener.java | 95 +- .../AbstractReactiveSaveEventListener.java | 31 +- .../DefaultReactiveDeleteEventListener.java | 211 +++-- ...tiveInitializeCollectionEventListener.java | 17 +- .../DefaultReactiveLockEventListener.java | 8 +- .../DefaultReactiveMergeEventListener.java | 8 +- .../DefaultReactiveRefreshEventListener.java | 21 +- .../ReactiveGeneratedValuesHelper.java | 7 +- .../id/impl/BlockingIdentifierGenerator.java | 39 +- .../id/impl/ReactiveGeneratorWrapper.java | 4 +- .../ReactiveIdentifierGeneratorFactory.java | 12 - .../internal/DatabaseSnapshotExecutor.java | 12 +- .../ReactiveSingleIdArrayLoadPlan.java | 7 +- ...ctiveSingleIdEntityLoaderStandardImpl.java | 6 +- .../ReactiveStandardBatchLoaderFactory.java | 6 +- .../ReactivePluralAttributeMapping.java | 548 +++++++++++ .../ReactiveToOneAttributeMapping.java | 17 +- .../ReactiveAbstractPersisterDelegate.java | 50 +- ...ReactiveJoinedSubclassEntityPersister.java | 2 +- .../ReactiveSingleTableEntityPersister.java | 16 +- .../ReactiveUnionSubclassEntityPersister.java | 3 +- .../ConcreteSqmSelectReactiveQueryPlan.java | 34 +- .../session/impl/ReactiveSessionImpl.java | 5 +- .../StandardReactiveSelectExecutor.java | 209 +++-- .../exec/spi/ReactiveRowProcessingState.java | 66 +- .../sql/exec/spi/ReactiveSelectExecutor.java | 30 +- .../sql/exec/spi/ReactiveValuesResultSet.java | 49 +- .../graph/ReactiveDomainResultsAssembler.java | 2 +- .../results/graph/ReactiveInitializer.java | 50 +- .../ReactiveCollectionDomainResult.java | 94 ++ .../internal/ReactiveEmbeddableFetchImpl.java | 40 + ...eactiveEmbeddableForeignKeyResultImpl.java | 27 + .../ReactiveEmbeddableInitializerImpl.java | 91 ++ ...veNonAggregatedIdentifierMappingFetch.java | 69 ++ ...ggregatedIdentifierMappingInitializer.java | 108 +++ .../ReactiveAbstractEntityInitializer.java | 219 ----- .../internal/ReactiveEntityAssembler.java | 35 +- ...ReactiveEntityDelayedFetchInitializer.java | 158 +++- .../ReactiveEntityFetchJoinedImpl.java | 70 +- .../ReactiveEntityFetchSelectImpl.java | 7 +- .../ReactiveEntityInitializerImpl.java | 853 ++++++++++++++++++ .../ReactiveEntityJoinedFetchInitializer.java | 133 --- .../ReactiveEntityResultInitializer.java | 64 -- ...titySelectFetchByUniqueKeyInitializer.java | 104 +-- .../ReactiveEntitySelectFetchInitializer.java | 319 +++---- ...veEntitySelectFetchInitializerBuilder.java | 95 +- .../ReactiveDeferredResultSetAccess.java | 9 +- .../ReactiveDirectResultSetAccess.java | 5 + .../ReactiveEntityDelayedFetchImpl.java | 33 +- .../internal/ReactiveEntityResultImpl.java | 75 +- .../internal/ReactiveInitializersList.java | 114 +-- .../internal/ReactiveResultSetAccess.java | 9 + .../internal/ReactiveResultsHelper.java | 145 +-- .../ReactiveRowTransformerArrayImpl.java | 40 + .../internal/ReactiveStandardRowReader.java | 452 ++++++++-- .../domain/ReactiveCircularFetchImpl.java | 31 +- .../spi/ReactiveListResultsConsumer.java | 126 ++- .../sql/results/spi/ReactiveRowReader.java | 14 - .../spi/ReactiveSingleResultConsumer.java | 4 +- .../ReactiveArrayJdbcTypeConstructor.java | 2 +- .../jdbc/ReactiveOracleArrayJdbcType.java | 81 +- .../EagerOneToOneAssociationTest.java | 78 +- .../org/hibernate/reactive/OrderTest.java | 2 + .../hibernate/reactive/SoftDeleteTest.java | 43 +- .../org/hibernate/reactive/UriConfigTest.java | 57 +- .../reactive/UriPoolConfiguration.java | 39 - .../ReactiveConnectionPoolTest.java | 2 +- .../reactive/containers/OracleDatabase.java | 5 +- .../schema/SchemaUpdateOracleTestBase.java | 2 +- 81 files changed, 4359 insertions(+), 2053 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java delete mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java diff --git a/README.md b/README.md index 31dba7666..62ab172f5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Learn more at . Hibernate Reactive has been tested with: -- Java 11, 17, 20, 21 +- Java 11, 17, 20, 21, 22 - PostgreSQL 16 - MySQL 8 - MariaDB 11 @@ -37,7 +37,7 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 6.5.2.Final +- [Hibernate ORM][] 6.6.0.CR2 - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.9 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.9 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.9 diff --git a/build.gradle b/build.gradle index 8b37fa1eb..815345bc4 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.5.2.Final' + hibernateOrmVersion = '6.6.0.CR2' } if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) { // Same as ORM as default diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java index 144978be0..034c9f583 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java @@ -241,16 +241,8 @@ public CompletionStage addAction(ReactiveEntityInsertAction action) { return addInsertAction( action ); } - private CompletionStage addInsertAction( ReactiveEntityInsertAction insert) { - CompletionStage ret = voidFuture(); - if ( insert.isEarlyInsert() ) { - // For early inserts, must execute inserts before finding non-nullable transient entities. - // TODO: find out why this is necessary - LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); - ret = ret.thenCompose( v -> executeInserts() ); - } - - return ret + private CompletionStage addInsertAction(ReactiveEntityInsertAction insert) { + return executeEarlyInsertsIfRequired( insert ) .thenCompose( v -> insert.reactiveFindNonNullableTransientEntities() ) .thenCompose( nonNullables -> { if ( nonNullables == null ) { @@ -270,40 +262,51 @@ private CompletionStage addInsertAction( ReactiveEntityInsertAction insert } ); } + private CompletionStage executeEarlyInsertsIfRequired(ReactiveEntityInsertAction insert) { + if ( insert.isEarlyInsert() ) { + // For early inserts, must execute inserts before finding non-nullable transient entities. + // TODO: find out why this is necessary + LOG.tracev( + "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", + insert + ); + return executeInserts(); + } + return voidFuture(); + } + private CompletionStage addResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { - CompletionStage ret; if ( insert.isEarlyInsert() ) { - LOG.trace( "Executing insertions before resolved early-insert" ); - ret = executeInserts() - .thenCompose( v -> { + // For early inserts, must execute inserts before finding non-nullable transient entities. + LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); + return executeInserts().thenCompose( v -> { LOG.debug( "Executing identity-insert immediately" ); return execute( insert ); - } ); + } ) + .thenCompose( v -> postResolvedEntityInsertAction( insert ) ); } else { LOG.trace( "Adding resolved non-early insert action." ); OrderedActions.EntityInsertAction.ensureInitialized( this ); this.insertions.add( new ReactiveEntityInsertActionHolder( insert ) ); - ret = voidFuture(); + return postResolvedEntityInsertAction( insert ); } + } - return ret.thenCompose( v -> { - if ( !insert.isVeto() ) { - CompletionStage comp = insert.reactiveMakeEntityManaged(); - if ( unresolvedInsertions == null ) { - return comp; - } - else { - return comp.thenCompose( vv -> loop( + private CompletionStage postResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { + if ( !insert.isVeto() ) { + return insert.reactiveMakeEntityManaged().thenCompose( v -> { + if ( unresolvedInsertions != null ) { + return loop( unresolvedInsertions.resolveDependentActions( insert.getInstance(), session.getSharedContract() ), resolvedAction -> addResolvedEntityInsertAction( (ReactiveEntityRegularInsertAction) resolvedAction ) - ) ); + ); } - } - else { - throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); - } - } ); + return voidFuture(); + } ); + } + + throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); } private static String[] convertTimestampSpaces(Serializable[] spaces) { 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 bb0666ebe..36466b556 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 @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.engine.impl; -import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -23,6 +22,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; @@ -30,17 +30,21 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; +import static java.lang.invoke.MethodHandles.lookup; +import static java.util.Collections.EMPTY_LIST; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.type.ForeignKeyDirection.TO_PARENT; @@ -54,35 +58,11 @@ * @author Gavin King * @see CascadingAction */ -public final class Cascade { +public final class Cascade { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private static final Log LOG = make( Log.class, lookup() ); - private final CascadingAction action; - private final EntityPersister persister; - private final Object parent; - private final EventSource eventSource; - private final C context; - private CascadePoint cascadePoint; - - private CompletionStage stage = voidFuture(); - - /** - * @param persister The parent's entity persister - * @param parent The parent reference. - */ - public Cascade(final CascadingAction action, - final CascadePoint cascadePoint, - final EntityPersister persister, - final Object parent, - final C context, - final EventSource eventSource) { - this.action = action; - this.parent = parent; - this.persister = persister; - this.cascadePoint = cascadePoint; - this.eventSource = eventSource; - this.context = context; + private Cascade() { } public static CompletionStage fetchLazyAssociationsBeforeCascade( @@ -93,13 +73,16 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( CompletionStage beforeDelete = voidFuture(); if ( persister.hasCascades() ) { - CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - Object[] state = persister.getValues( entity ); + final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); + final Object[] state = persister.getValues( entity ); for (int i = 0; i < cascadeStyles.length; i++) { if ( cascadeStyles[i].doCascade( action.delegate() ) ) { Object fetchable = state[i]; if ( !Hibernate.isInitialized( fetchable ) ) { - beforeDelete = beforeDelete.thenCompose( v -> session.unwrap(ReactiveSession.class).reactiveFetch( fetchable, true ) ); + beforeDelete = beforeDelete.thenCompose( v -> session + .unwrap( ReactiveSession.class ) + .reactiveFetch( fetchable, true ) + ); } } } @@ -107,165 +90,220 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( return beforeDelete; } - /** - * Cascade an action from the parent entity instance to all its children. - */ - public CompletionStage cascade() throws HibernateException { - return voidFuture().thenCompose( v -> { - CacheMode cacheMode = eventSource.getCacheMode(); - if ( action == CascadingActions.DELETE ) { - eventSource.setCacheMode( CacheMode.GET ); - } - eventSource.getPersistenceContextInternal().incrementCascadeLevel(); - return cascadeInternal().whenComplete( (vv, e) -> { - eventSource.getPersistenceContextInternal().decrementCascadeLevel(); - eventSource.setCacheMode( cacheMode ); - } ); - } ); + public static CompletionStage cascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + CacheMode cacheMode = eventSource.getCacheMode(); + // In Hibernate actually set the cache before calling cascade for the remove, but this solution reduces some code + if ( action == CascadingActions.REMOVE ) { + eventSource.setCacheMode( CacheMode.GET ); + } + // Hibernate ORM actually increment/decrement the level before calling cascade, but keeping it here avoid extra + // code every time we need to cascade + eventSource.getPersistenceContextInternal().incrementCascadeLevel(); + return voidFuture() + .thenCompose( v -> cascadeInternal( action, cascadePoint, eventSource, persister, parent, anything ) ) + .whenComplete( (unused, throwable) -> { + eventSource.getPersistenceContextInternal().decrementCascadeLevel(); + eventSource.setCacheMode( cacheMode ); + } ); } - private CompletionStage cascadeInternal() throws HibernateException { - if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt + private static CompletionStage cascadeInternal( + CascadingAction action, + CascadePoint cascadePoint, + EventSource eventSource, + EntityPersister persister, + Object parent, + T anything) { + if ( persister.hasCascades() || action == CascadingActions.CHECK_ON_FLUSH ) { // 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 ]; - final boolean isUninitializedProperty = - hasUninitializedLazyProperties && - !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); - - final Type type = types[i]; - if ( style.doCascade( action.delegate() ) ) { - final Object child; - if ( isUninitializedProperty ) { - // parent is a bytecode enhanced entity. - // Cascade to an uninitialized, lazy value only if - // parent is managed in the PersistenceContext. - // If parent is a detached entity being merged, - // then parent will not be in the PersistenceContext - // (so lazy attributes must not be initialized). - if ( entry == null ) { - // parent was not in the PersistenceContext - continue; - } - if ( type.isCollectionType() ) { - // CollectionType#getCollection gets the PersistentCollection - // that corresponds to the uninitialized collection from the - // PersistenceContext. If not present, an uninitialized - // PersistentCollection will be added to the PersistenceContext. - // The action may initialize it later, if necessary. - // This needs to be done even when action.performOnLazyProperty() returns false. - final CollectionType collectionType = (CollectionType) type; - child = collectionType.getCollection( - collectionType.getKeyOfOwner( parent, eventSource ), - eventSource, - parent, - null - ); - } - else if ( type.isComponentType() ) { - // Hibernate does not support lazy embeddables, so this shouldn't happen. - throw new UnsupportedOperationException( - "Lazy components are not supported." - ); + return doCascade( action, cascadePoint, eventSource, persister, parent, anything ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); } - else if ( action.performOnLazyProperty() && type.isEntityType() ) { - // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() - // returns true. - LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() - .extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + } ); + } + return voidFuture(); + } - } - else { - // Nothing to do, so just skip cascading to this lazy attribute. - continue; - } + /** + * Cascade an action from the parent entity instance to all its children. + */ + private static CompletionStage doCascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + 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 ); + + CompletionStage stage = voidFuture(); + for ( int i = 0; i < types.length; i++ ) { + final CascadeStyle style = cascadeStyles[i]; + final String propertyName = propertyNames[i]; + final boolean isUninitializedProperty = hasUninitializedLazyProperties + && !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); + + final Type type = types[i]; + if ( style.doCascade( action.delegate() ) ) { + if ( isUninitializedProperty ) { + // parent is a bytecode enhanced entity. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistenceContext + // (so lazy attributes must not be initialized). + if ( entry == null ) { + // parent was not in the PersistenceContext + continue; + } + if ( type.isCollectionType() ) { + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) type; + Object child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + child, + type, + style, + propertyName, + anything, + false + ) ); + } + else if ( type.isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( "Lazy components are not supported." ); + } + else if ( action.performOnLazyProperty() && type.isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() + .extractInterceptor( parent ); + stage = stage + .thenCompose( v -> (CompletionStage) interceptor + .fetchAttribute( parent, propertyName ) + ) + .thenCompose( actualChild -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + actualChild, + type, + style, + propertyName, + anything, + false + ) ); } else { - child = persister.getValue( parent, i ); + // Nothing to do, so just skip cascading to this lazy attribute. + continue; } - cascadeProperty( + } + else { + Object child = persister.getValue( parent, i ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, null, + parent, child, type, style, propertyName, + anything, false - ); - } - else { - if ( action.requiresNoCascadeChecking() ) { - noCascade( eventSource, parent, persister, types, i ); - } - // If the property is uninitialized, then there cannot be any orphans. - if ( action.deleteOrphans() && !isUninitializedProperty ) { - cascadeLogicalOneToOneOrphanRemoval( - null, - persister.getValue( parent, i ), - type, - style, - propertyName, - false - ); - } + ) ); } } - - if ( traceEnabled ) { - LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); + else { + // If the property is uninitialized, then there cannot be any orphans. + if ( action.deleteOrphans() && !isUninitializedProperty ) { + final int index = i; + stage = stage.thenCompose( v -> cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + null, + parent, + persister.getValue( parent, index ), + type, + style, + propertyName, + false + ) ); + } } } - return stage; } - private void noCascade( - final EventSource eventSource, - final Object parent, - final EntityPersister persister, - final Type[] types, - final int i) { - stage = stage.thenCompose( v -> action.noCascade( eventSource, parent, persister, types[i], i ) ); - } - /** * Cascade an action to the child or children */ - private void cascadeProperty( + private static CompletionStage cascadeProperty( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, final String propertyName, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { if ( child != null ) { if ( type.isAssociationType() ) { final AssociationType associationType = (AssociationType) type; - if ( cascadeAssociationNow( cascadePoint, associationType ) ) { - cascadeAssociation( - componentPath, - child, - type, - style, - isCascadeDeleteEnabled - ); + final boolean unownedTransient = eventSource.getSessionFactory() + .getSessionFactoryOptions() + .isUnownedAssociationTransientCheck(); + if ( cascadeAssociationNow( action, cascadePoint, associationType, eventSource.getFactory(), unownedTransient ) ) { + final List path = componentPath; + return cascadeAssociation( action, cascadePoint, eventSource, path, parent, child, type, style, anything, isCascadeDeleteEnabled ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } else if ( type.isComponentType() ) { @@ -275,25 +313,39 @@ else if ( type.isComponentType() ) { if ( componentPath != null ) { componentPath.add( propertyName ); } - cascadeComponent( - componentPath, - child, - (CompositeType) type - ); + final List path = componentPath; + return cascadeComponent( action, cascadePoint, eventSource, path, parent, child, (CompositeType) type, anything ) + .thenRun( () -> { + if ( path != null ) { + path.remove( path.size() - 1 ); + } + } ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } + return cascadeLogicalOneToOne( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ); + } - cascadeLogicalOneToOneOrphanRemoval( - componentPath, - child, - type, - style, - propertyName, - isCascadeDeleteEnabled ); + private static CompletionStage cascadeLogicalOneToOne( + CascadingAction action, + EventSource eventSource, + List componentPath, + Object parent, + Object child, + Type type, + CascadeStyle style, + String propertyName, + boolean isCascadeDeleteEnabled) { + return isLogicalOneToOne( type ) + ? cascadeLogicalOneToOneOrphanRemoval( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) + : voidFuture(); } - private void cascadeLogicalOneToOneOrphanRemoval( + private static CompletionStage cascadeLogicalOneToOneOrphanRemoval( + final CascadingAction action, + final EventSource eventSource, final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, @@ -355,7 +407,7 @@ private void cascadeLogicalOneToOneOrphanRemoval( // associated one-to-one. if ( child == loadedValue ) { // do nothing - return; + return voidFuture(); } } @@ -369,24 +421,24 @@ private void cascadeLogicalOneToOneOrphanRemoval( ); } - final Object loaded = loadedValue; if ( type.isAssociationType() - && ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) { + && ( (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 ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemoveOrphanBeforeUpdates( entityName, loadedValue ); } else { // Else, we must delete after the updates. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemove( entityName, loadedValue, isCascadeDeleteEnabled, DeleteContext.create() ); } } } } } + return voidFuture(); } /** @@ -397,22 +449,55 @@ private void cascadeLogicalOneToOneOrphanRemoval( * * @return True if the attribute represents a logical one to one association */ - private boolean isLogicalOneToOne(Type type) { + private static boolean isLogicalOneToOne(Type type) { return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne(); } - private boolean cascadeAssociationNow(final CascadePoint cascadePoint, AssociationType associationType) { - return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ); + private static boolean cascadeAssociationNow( + CascadingAction action, + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory, + boolean unownedTransient) { + return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ) + // For check on flush, we should only check unowned associations when strictness is enforced + && ( action != CascadingActions.CHECK_ON_FLUSH || unownedTransient || !isUnownedAssociation( associationType, factory ) ); } - private void cascadeComponent( - List componentPath, + private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { + if ( associationType.isEntityType() ) { + if ( associationType instanceof ManyToOneType ) { + final ManyToOneType manyToOne = (ManyToOneType) associationType; + // logical one-to-one + non-null unique key property name indicates unowned + return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof OneToOneType ) { + final OneToOneType oneToOne = (OneToOneType) associationType; + // constrained false + non-null unique key property name indicates unowned + return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; + } + } + else if ( associationType.isCollectionType() ) { + // for collections, we can ask the persister if we're on the inverse side + return ( (CollectionType) associationType ).isInverse( factory ); + } + return false; + } + + private static CompletionStage cascadeComponent( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, - final CompositeType componentType) { + final CompositeType componentType, + final T anything) { Object[] children = null; final Type[] types = componentType.getSubtypes(); final String[] propertyNames = componentType.getPropertyNames(); + CompletionStage stage = voidFuture(); for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; @@ -422,77 +507,106 @@ private void cascadeComponent( // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); } - cascadeProperty( + final Object propertyChild = children[i]; + final Type propertyType = types[i]; + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, componentPath, - children[i], - types[i], + parent, + propertyChild, + propertyType, componentPropertyStyle, subPropertyName, + anything, false - ); + ) + ); } } + return stage; } - private void cascadeAssociation( - List componentPath, + private static CompletionStage cascadeAssociation( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { if ( type.isEntityType() || type.isAnyType() ) { - cascadeToOne( child, type, style, isCascadeDeleteEnabled ); + return cascadeToOne( action, eventSource, parent, child, type, style, anything, isCascadeDeleteEnabled ); } else if ( type.isCollectionType() ) { - cascadeCollection( + return cascadeCollection( + action, + cascadePoint, + eventSource, componentPath, + parent, child, style, + anything, (CollectionType) type ); } + return voidFuture(); } /** * Cascade an action to a collection */ - private void cascadeCollection( - List componentPath, + private static CompletionStage cascadeCollection( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CascadeStyle style, + final T anything, final CollectionType type) { - final CollectionPersister persister = - eventSource.getFactory().getMappingMetamodel() - .getCollectionDescriptor( type.getRole() ); + final CollectionPersister persister = eventSource.getFactory().getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ); final Type elemType = persister.getElementType(); - - CascadePoint elementsCascadePoint = cascadePoint; - if ( cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE ) { - cascadePoint = CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION; - } - //cascade to current collection elements if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) { - cascadeCollectionElements( - componentPath, - child, - type, - style, - elemType, - persister.isCascadeDeleteEnabled() + return cascadeCollectionElements( + action, + cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE + ? CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION + : cascadePoint, + eventSource, + componentPath, + parent, + child, + type, + style, + elemType, + anything, + persister.isCascadeDeleteEnabled() + ); } - - cascadePoint = elementsCascadePoint; + return voidFuture(); } /** * Cascade an action to a to-one association or any type */ - private void cascadeToOne( + private static CompletionStage cascadeToOne( + final CascadingAction action, + final EventSource eventSource, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { final String entityName = type.isEntityType() ? ( (EntityType) type ).getAssociatedEntityName() @@ -501,47 +615,66 @@ private void cascadeToOne( //not really necessary, but good for consistency... final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); persistenceContext.addChildParent( child, parent ); - stage = stage.thenCompose( v -> action.cascade( eventSource, child, entityName, context, isCascadeDeleteEnabled ) ) - .whenComplete( (vv, e) -> persistenceContext.removeChildParent( child ) ); + return voidFuture() + .thenCompose( v -> action.cascade( eventSource, child, entityName, anything, isCascadeDeleteEnabled ) ) + .whenComplete( (v, e) -> persistenceContext.removeChildParent( child ) ); } + return voidFuture(); } /** * Cascade to the collection elements */ - private void cascadeCollectionElements( - List componentPath, + private static CompletionStage cascadeCollectionElements( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CollectionType collectionType, final CascadeStyle style, final Type elemType, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { - final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) - && child != CollectionType.UNFETCHED_COLLECTION; - + final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) && child != CollectionType.UNFETCHED_COLLECTION; if ( reallyDoCascade ) { final boolean traceEnabled = LOG.isTraceEnabled(); - if ( traceEnabled ) { - LOG.tracev( "Cascade {0} for collection: {1}", action, collectionType.getRole() ); - } - - final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); - while ( itr.hasNext() ) { - cascadeProperty( - componentPath, - itr.next(), - elemType, - style, - null, - isCascadeDeleteEnabled - ); - } if ( traceEnabled ) { LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); } + + final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); + return loop( itr, (value, integer) -> cascadeProperty( + action, + cascadePoint, + eventSource, + componentPath, + parent, + value, + elemType, + style, + collectionType.getRole().substring( collectionType.getRole().lastIndexOf( '.' ) + 1 ), + anything, + isCascadeDeleteEnabled + ) ).thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); + } + } ).thenCompose( v -> doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ) ); } + return doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ); + } + + private static CompletionStage doDeleteOrphans( + CascadingAction action, + EventSource eventSource, + Object child, + CollectionType collectionType, + CascadeStyle style, + Type elemType) { final boolean deleteOrphans = style.hasOrphanDelete() && action.deleteOrphans() && elemType.isEntityType() @@ -558,39 +691,33 @@ private void cascadeCollectionElements( // 1. newly instantiated collections // 2. arrays (we can't track orphans for detached arrays) final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() ); - deleteOrphans( entityName, (PersistentCollection) child ); - - if ( traceEnabled ) { - LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); - } + return doDeleteOrphans( eventSource, entityName, (PersistentCollection) child ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); + } + } ); } + return voidFuture(); } /** * Delete any entities that were removed from the collection */ - private void deleteOrphans(String entityName, PersistentCollection pc) throws HibernateException { - //TODO: suck this logic into the collection! - final Collection orphans; + private static CompletionStage doDeleteOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { + final Collection orphans = getOrphans( eventSource, entityName, pc ); + final ReactiveSession session = (ReactiveSession) eventSource; + return loop( orphans, Objects::nonNull, orphan -> { + LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); + return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); + } ); + } + + private static Collection getOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { if ( pc.wasInitialized() ) { final CollectionEntry ce = eventSource.getPersistenceContextInternal().getCollectionEntry( pc ); - if ( ce == null ) { - return; - } - orphans = ce.getOrphans( entityName, pc ); - } - else { - orphans = pc.getQueuedOrphans( entityName ); + return ce == null ? EMPTY_LIST : ce.getOrphans( entityName, pc ); } - - ReactiveSession session = (ReactiveSession) eventSource; - stage = stage.thenCompose( v -> loop( - orphans, - Objects::nonNull, - orphan -> { - LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); - return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); - } - ) ); + return pc.getQueuedOrphans( entityName ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java index 7c6bdd749..a8707cc11 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java @@ -5,6 +5,9 @@ */ package org.hibernate.reactive.engine.impl; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; + import org.hibernate.HibernateException; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.EntityPersister; @@ -12,9 +15,6 @@ import org.hibernate.type.CollectionType; import org.hibernate.type.Type; -import java.util.Iterator; -import java.util.concurrent.CompletionStage; - /** * A {@link Stage.Session reactive session} operation that may * be cascaded from a parent entity to its children. A non-blocking counterpart @@ -63,13 +63,17 @@ Iterator getCascadableChildrenIterator( */ boolean deleteOrphans(); - /** * Does the specified cascading action require verification of no cascade validity? * * @return True if this action requires no-cascade verification; false otherwise. + * + * @deprecated No longer used */ - boolean requiresNoCascadeChecking(); + @Deprecated(since = "2.4", forRemoval = true) + default boolean requiresNoCascadeChecking() { + return false; + } /** * Called (in the case of {@link #requiresNoCascadeChecking} returning true) to validate @@ -81,6 +85,7 @@ Iterator getCascadableChildrenIterator( * @param propertyType The property type * @param propertyIndex The index of the property within the owner. */ + @Deprecated(since = "2.4", forRemoval = true) CompletionStage noCascade(EventSource session, Object parent, EntityPersister persister, Type propertyType, int propertyIndex); /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java index d194fe312..28289c022 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java @@ -10,10 +10,11 @@ import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.Internal; import org.hibernate.LockOptions; +import org.hibernate.TransientObjectException; import org.hibernate.TransientPropertyValueException; import org.hibernate.engine.spi.EntityEntry; - import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; @@ -29,6 +30,9 @@ import org.hibernate.type.Type; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; +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.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -48,7 +52,7 @@ private CascadingActions() { /** * @see org.hibernate.Session#remove(Object) */ - public static final CascadingAction DELETE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.DELETE ) { + public static final CascadingAction REMOVE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REMOVE ) { @Override public CompletionStage cascade( EventSource session, @@ -150,6 +154,58 @@ public CompletionStage noCascade( } }; + @Internal + public static final CascadingAction CHECK_ON_FLUSH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.CHECK_ON_FLUSH ) { + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + Void context, + boolean isCascadeDeleteEnabled) throws HibernateException { + if ( child != null ) { + return isChildTransient( session, child, entityName ).thenCompose( isTransient -> { + if ( isTransient ) { + return failedFuture( new TransientObjectException( + "persistent instance references an unsaved transient instance of '" + entityName + "' (save the transient instance before flushing)" ) ); + } + return voidFuture(); + } ); + } + return voidFuture(); + } + }; + + private static CompletionStage isChildTransient(EventSource session, Object child, String entityName) { + if ( isHibernateProxy( child ) ) { + // a proxy is always non-transient + // and ForeignKeys.isTransient() + // is not written to expect a proxy + // TODO: but the proxied entity might have been deleted! + return falseFuture(); + } + else { + final EntityEntry entry = session.getPersistenceContextInternal().getEntry( child ); + if ( entry != null ) { + // if it's associated with the session + // we are good, even if it's not yet + // inserted, since ordering problems + // are detected and handled elsewhere + boolean deleted = entry.getStatus().isDeletedOrGone(); + return completedFuture( deleted ); + } + else { + // TODO: check if it is a merged entity which has not yet been flushed + // Currently this throws if you directly reference a new transient + // instance after a call to merge() that results in its managed copy + // being scheduled for insertion, if the insert has not yet occurred. + // This is not terrible: it's more correct to "swap" the reference to + // point to the managed instance, but it's probably too heavy-handed. + return ForeignKeys.isTransient( entityName, child, null, session ); + } + } + } + /** * @see org.hibernate.Session#merge(Object) */ @@ -173,35 +229,35 @@ public CompletionStage cascade( * @see org.hibernate.Session#refresh(Object) */ public static final CascadingAction REFRESH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REFRESH ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - RefreshContext context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to refresh: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + RefreshContext context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to refresh: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); + } + }; /** * @see org.hibernate.Session#lock(Object, org.hibernate.LockMode) */ public static final CascadingAction LOCK = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.LOCK ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - LockOptions context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to lock: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + LockOptions context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to lock: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); + } + }; public abstract static class BaseCascadingAction implements CascadingAction { private final org.hibernate.engine.spi.CascadingAction delegate; @@ -225,11 +281,6 @@ public org.hibernate.engine.spi.CascadingAction delegate() { return delegate; } - @Override - public boolean requiresNoCascadeChecking() { - return delegate.requiresNoCascadeChecking(); - } - /** * @see BaseCascadingAction#noCascade(EventSource, Object, EntityPersister, Type, int) */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java index 5f2842183..008755086 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java @@ -31,22 +31,22 @@ public ReactiveCollectionRecreateAction(final PersistentCollection collection, f public CompletionStage reactiveExecute() { // this method is called when a new non-null collection is persisted // or when an existing (non-null) collection is moved to a new owner - final PersistentCollection collection = getCollection(); preRecreate(); + return ( (ReactiveCollectionPersister) getPersister() ) + .reactiveRecreate( getCollection(), getKey(), getSession() ) + .thenRun( this::afterRecreate ); + } + + private void afterRecreate() { + final PersistentCollection collection = getCollection(); final SharedSessionContractImplementor session = getSession(); - final ReactiveCollectionPersister persister = (ReactiveCollectionPersister) getPersister(); - return persister - .reactiveRecreate( collection, getKey(), session ) - .thenAccept( v -> { - // FIXME: I think we could move everything in a method reference call - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postRecreate(); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.recreateCollection( getPersister().getRole() ); - } - } ); + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postRecreate(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.recreateCollection( getPersister().getRole() ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java index 21129a1e5..eae872d56 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java @@ -26,6 +26,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -55,51 +56,59 @@ public CompletionStage reactiveExecute() { final SharedSessionContractImplementor session = getSession(); final ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) getPersister(); final CollectionPersister persister = getPersister(); - final PersistentCollection collection = getCollection(); + final PersistentCollection collection = getCollection(); final boolean affectedByFilters = persister.isAffectedByEnabledFilters( session ); preUpdate(); - final CompletionStage updateStage; + return createUpdateStage( collection, affectedByFilters, reactivePersister, key, session, persister ) + .thenRun( () -> { + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postUpdate(); + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( persister.getRole() ); + } + } ); + } + + private CompletionStage createUpdateStage( + PersistentCollection collection, + boolean affectedByFilters, + ReactiveCollectionPersister reactivePersister, + Object key, + SharedSessionContractImplementor session, + CollectionPersister persister) { if ( !collection.wasInitialized() ) { // If there were queued operations, they would have been processed // and cleared by now. // The collection should still be dirty. if ( !collection.isDirty() ) { - throw new AssertionFailure( "collection is not dirty" ); + return failedFuture( new AssertionFailure( "collection is not dirty" ) ); } //do nothing - we only need to notify the cache... - updateStage = voidFuture(); + return voidFuture(); } else if ( !affectedByFilters && collection.empty() ) { - updateStage = emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); + return emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); } else if ( collection.needsRecreate( persister ) ) { if ( affectedByFilters ) { - throw LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ); + return failedFuture( LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ) ); } - updateStage = emptySnapshot + return emptySnapshot ? reactivePersister.reactiveRecreate( collection, key, session ) : reactivePersister.reactiveRemove( key, session ) .thenCompose( v -> reactivePersister.reactiveRecreate( collection, key, session ) ); } else { - updateStage = voidFuture() - .thenCompose( v -> reactivePersister.reactiveDeleteRows( collection, key, session ) ) + return reactivePersister + .reactiveDeleteRows( collection, key, session ) .thenCompose( v -> reactivePersister.reactiveUpdateRows( collection, key, session ) ) .thenCompose( v -> reactivePersister.reactiveInsertRows( collection, key, session ) ); } - - return updateStage.thenAccept( v -> { - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postUpdate(); - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( persister.getRole() ); - } - } ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java index 69168c3b1..3dc3a0dc1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java @@ -118,6 +118,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java index ef48ba006..4838e6971 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java @@ -11,9 +11,11 @@ import org.hibernate.action.internal.AbstractEntityInsertAction; import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.engine.internal.Nullability; -import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.ComparableExecutable; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; @@ -21,6 +23,7 @@ import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.ReactiveExecutable; +import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -37,6 +40,7 @@ public interface ReactiveEntityInsertAction extends ReactiveExecutable, Comparab Object getInstance(); String getEntityName(); Object[] getState(); + Object getRowId(); EntityPersister getPersister(); boolean isExecuted(); @@ -62,7 +66,7 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { if ( !areTransientReferencesNullified() ) { return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), (SessionImplementor) getSession(), getPersister() ) .nullifyTransientReferences( getState() ) - .thenAccept( v-> { + .thenAccept( v -> { new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); setTransientReferencesNullified(); } ); @@ -79,27 +83,35 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { */ default CompletionStage reactiveMakeEntityManaged() { return reactiveNullifyTransientReferencesIfNotAlready() - .thenAccept( v -> getSession().getPersistenceContextInternal().addEntity( - getInstance(), - getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY, - getState(), - getEntityKey(), - Versioning.getVersion( getState(), getPersister() ), - LockMode.WRITE, - isExecuted(), - getPersister(), - isVersionIncrementDisabled() - )); + .thenAccept( v -> { + final Object version = getVersion( getState(), getPersister() ); + final PersistenceContext persistenceContextInternal = getSession().getPersistenceContextInternal(); + final EntityHolder entityHolder = persistenceContextInternal + .addEntityHolder( getEntityKey(), getInstance() ); + final EntityEntry entityEntry = persistenceContextInternal.addEntry( + getInstance(), + ( getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY ), + getState(), + getRowId(), + getEntityKey().getIdentifier(), + version, + LockMode.WRITE, + isExecuted(), + getPersister(), + isVersionIncrementDisabled() + ); + entityHolder.setEntityEntry( entityEntry ); + if ( isEarlyInsert() ) { + addCollectionsByKeyToPersistenceContext( persistenceContextInternal, getState() ); + } + }); } + void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects); + default CompletionStage reactiveFindNonNullableTransientEntities() { return ForeignKeys.findNonNullableTransientEntities( getPersister().getEntityName(), getInstance(), getState(), isEarlyInsert(), getSession() ); } AbstractEntityInsertAction asAbstractEntityInsertAction(); - - default int compareActionTo(ReactiveEntityInsertAction delegate) { - return asAbstractEntityInsertAction().compareTo( delegate.asAbstractEntityInsertAction() ); - } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java index f3598a09f..062f0374f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java @@ -124,6 +124,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; @@ -135,6 +140,11 @@ protected void markExecuted() { executed = true; } + @Override + public Object getRowId() { + return super.getRowId(); + } + @Override public boolean isExecuted() { return executed; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java index 66d0624e0..33750cc0d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java @@ -5,6 +5,11 @@ */ package org.hibernate.reactive.engine.impl; +import java.io.Serializable; +import java.util.HashMap; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + import org.hibernate.HibernateException; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.StatefulPersistenceContext; @@ -13,15 +18,13 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.ReactiveSession; -import java.io.Serializable; -import java.util.HashMap; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - +import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -30,6 +33,8 @@ */ public class ReactivePersistenceContextAdapter extends StatefulPersistenceContext { + private static final Log LOG = make( Log.class, lookup() ); + private HashMap entitySnapshotsByKey; /** @@ -71,7 +76,7 @@ public void initializeNonLazyCollections() { @Deprecated @Override public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { - throw new UnsupportedOperationException( "reactive persistence context" ); + throw LOG.nonReactiveMethodCall( "reactiveGetDatabaseSnapshot" ); } private static final Object[] NO_ROW = new Object[]{ StatefulPersistenceContext.NO_ROW }; 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 b43d2753c..2772cc15d 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 @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; - import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -38,6 +36,9 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; +import static org.hibernate.reactive.engine.impl.CascadingActions.PERSIST_ON_FLUSH; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + /** * Collects commons methods needed during the management of flush events. * @@ -80,14 +81,32 @@ private ReactiveActionQueue actionQueue(EventSource session) { * @throws HibernateException Error flushing caches to execution queues. */ protected CompletionStage flushEverythingToExecutions(FlushEvent event) throws HibernateException { - LOG.trace( "Flushing session" ); - final EventSource session = event.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); + return preFlush( session, persistenceContext ) + .thenRun( () -> flushEverythingToExecutions( event, persistenceContext, session ) ); + } + protected void flushEverythingToExecutions(FlushEvent event, PersistenceContext persistenceContext, EventSource session) { + persistenceContext.setFlushing( true ); + try { + int entityCount = flushEntities( event, persistenceContext ); + int collectionCount = flushCollections( session, persistenceContext ); + + event.setNumberOfEntitiesProcessed( entityCount ); + event.setNumberOfCollectionsProcessed( collectionCount ); + } + finally { + persistenceContext.setFlushing( false); + } + + //some statistics + logFlushResults( event ); + } + + protected CompletionStage preFlush(EventSource session, PersistenceContext persistenceContext) { + session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); return prepareEntityFlushes( session, persistenceContext ) .thenAccept( v -> { // we could move this inside if we wanted to @@ -97,20 +116,6 @@ protected CompletionStage flushEverythingToExecutions(FlushEvent event) th // now, any collections that are initialized // inside this block do not get updated - they // are ignored until the next flush - persistenceContext.setFlushing( true ); - try { - int entityCount = flushEntities( event, persistenceContext ); - int collectionCount = flushCollections( session, persistenceContext ); - - event.setNumberOfEntitiesProcessed(entityCount); - event.setNumberOfCollectionsProcessed(collectionCount); - } - finally { - persistenceContext.setFlushing( false ); - } - - //some statistics - logFlushResults( event ); } ); } @@ -145,7 +150,6 @@ protected void logFlushResults(FlushEvent event) { * and also apply orphan delete */ private CompletionStage prepareEntityFlushes(EventSource session, PersistenceContext persistenceContext) throws HibernateException { - LOG.debug( "Processing flush-time cascades" ); final PersistContext context = PersistContext.create(); @@ -154,7 +158,41 @@ private CompletionStage prepareEntityFlushes(EventSource session, Persiste return loop( entries, index -> flushable( entries[index].getValue() ), - index -> cascadeOnFlush( session, entries[index].getValue().getPersister(), entries[index].getKey(), context ) ); + index -> cascadeOnFlush( + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + context + ) + ) + // perform these checks after all cascade persist events have been + // processed, so that all entities which will be persisted are + // persistent when we do the check (I wonder if we could move this + // into Nullability, instead of abusing the Cascade infrastructure) + .thenCompose( v -> loop( + entries, + index -> flushable( entries[index].getValue() ), + index -> Cascade.cascade( + CascadingActions.CHECK_ON_FLUSH, + CascadePoint.BEFORE_FLUSH, + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + null + ) + ) ); + } + + private CompletionStage cascadeOnFlush( + EventSource session, + EntityPersister persister, + Object object, + PersistContext anything) + throws HibernateException { + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + persistenceContext.incrementCascadeLevel(); + return Cascade.cascade( PERSIST_ON_FLUSH, CascadePoint.BEFORE_FLUSH, session, persister, object, anything ) + .whenComplete( (unused, throwable) -> persistenceContext.decrementCascadeLevel() ); } private static boolean flushable(EntityEntry entry) { @@ -299,19 +337,6 @@ private int flushCollections(final EventSource session, final PersistenceContext return count; } - private CompletionStage cascadeOnFlush( - EventSource session, - EntityPersister persister, - Object object, - PersistContext copiedAlready) - throws HibernateException { - return new Cascade<>( - CascadingActions.PERSIST_ON_FLUSH, - CascadePoint.BEFORE_FLUSH, - persister, object, copiedAlready, session - ).cascade(); - } - /** * 1. Recreate the collection key to collection map * 2. rebuild the collection entries diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java index 54fd9b23a..e9d68b00e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java @@ -43,6 +43,7 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.engine.internal.Versioning.seedVersion; @@ -226,6 +227,7 @@ protected CompletionStage reactivePerformSave( callbackRegistry.preCreate( entity ); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + processIfManagedEntity( entity, managedEntity -> managedEntity.$$_hibernate_setUseTracker( true ) ); if ( persister.getGenerator() instanceof Assigned ) { id = persister.getIdentifier( entity, source ); @@ -277,6 +279,11 @@ private CompletionStage generateEntityKey(Object id, EntityPersister return failedFuture( new NonUniqueObjectException( id, persister.getEntityName() ) ); } } + else if ( persistenceContext.containsDeletedUnloadedEntityKey( key ) ) { + return source.unwrap( ReactiveSession.class ) + .reactiveForceFlush( persistenceContext.getEntry( old ) ) + .thenApply( v -> key ); + } else { return completedFuture( key ); } @@ -331,6 +338,10 @@ protected CompletionStage reactivePerformSaveOrReplicate( false ); + if ( original.getLoadedState() != null ) { + persistenceContext.getEntityHolder( key ).setEntityEntry( original ); + } + return cascadeBeforeSave( source, persister, entity, context ) .thenCompose( v -> addInsertAction( // We have to do this after cascadeBeforeSave completes, @@ -409,7 +420,7 @@ private CompletionStage addInsertAction( boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { - final ReactiveActionQueue actionQueue = source.unwrap(ReactiveSession.class).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = source.unwrap( ReactiveSession.class ).getReactiveActionQueue(); if ( useIdentityColumn ) { final ReactiveEntityIdentityInsertAction insert = new ReactiveEntityIdentityInsertAction( values, entity, persister, false, source, shouldDelayIdentityInserts @@ -438,11 +449,14 @@ protected CompletionStage cascadeBeforeSave( Object entity, C context) { // cascade-save to many-to-one BEFORE the parent is saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } /** @@ -459,11 +473,14 @@ protected CompletionStage cascadeAfterSave( Object entity, C context) { // cascade-save to collections AFTER the collection owner was saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } protected abstract CascadingAction getCascadeReactiveAction(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 2c7784d7b..66151f03b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; @@ -17,6 +13,7 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.spi.EntityEntry; @@ -57,6 +54,10 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactive {@link org.hibernate.event.internal.DefaultDeleteEventListener}. */ @@ -82,8 +83,8 @@ public void wasJpaBootstrap(boolean wasJpaBootstrap) { * Handle the given delete event. * * @param event The delete event to be handled. - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -92,8 +93,8 @@ public void onDelete(DeleteEvent event) { } /** - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent, DeleteContext) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -105,7 +106,6 @@ public void onDelete(DeleteEvent event, DeleteContext transientEntities) throws * Handle the given delete event. * * @param event The delete event to be handled. - * */ @Override public CompletionStage reactiveOnDelete(DeleteEvent event) throws HibernateException { @@ -115,26 +115,23 @@ public CompletionStage reactiveOnDelete(DeleteEvent event) throws Hibernat /** * Handle the given delete event. This is the cascaded form. * - * @param event The delete event. + * @param event The delete event. * @param transientEntities The cache of entities already deleted - * */ @Override - public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) throws HibernateException { - if ( optimizeUnloadedDelete( event ) ) { - return voidFuture(); - } - else { - final EventSource source = event.getSession(); - Object object = event.getObject(); - if ( object instanceof CompletionStage ) { - final CompletionStage stage = (CompletionStage) object; - return stage.thenCompose( objectEvent -> fetchAndDelete( event, transientEntities, source, objectEvent ) ); - } - else { - return fetchAndDelete( event, transientEntities, source, object); - } - } + public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) + throws HibernateException { + return !optimizeUnloadedDelete( event ) + ? fetchAndDelete( event, transientEntities ) + : voidFuture(); + } + + private CompletionStage delete(DeleteEvent event, DeleteContext transientEntities, Object entity) { + final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + return entityEntry == null + ? deleteTransientInstance( event, transientEntities, entity ) + : deletePersistentInstance( event, transientEntities, entity, entityEntry ); } private boolean optimizeUnloadedDelete(DeleteEvent event) { @@ -157,12 +154,12 @@ && canBeDeletedWithoutLoading( source, persister ) ) { if ( persister.hasOwnedCollections() ) { // we're deleting an unloaded proxy with collections - for ( Type type : persister.getPropertyTypes() ) { //TODO: when we enable this for subclasses use getSubclassPropertyTypeClosure() + for ( Type type : persister.getPropertyTypes() ) { deleteOwnedCollections( type, id, source ); } } - ((ReactiveSession) source).getReactiveActionQueue() + ( (ReactiveSession) source ).getReactiveActionQueue() .addAction( new ReactiveEntityDeleteAction( id, persister, source ) ); } return true; @@ -174,10 +171,10 @@ && canBeDeletedWithoutLoading( source, persister ) ) { private static void deleteOwnedCollections(Type type, Object key, EventSource session) { final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); - final ReactiveActionQueue actionQueue = ((ReactiveSession) session).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = ( (ReactiveSession) session ).getReactiveActionQueue(); if ( type.isCollectionType() ) { final String role = ( (CollectionType) type ).getRole(); - final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor(role); + final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor( role ); if ( !persister.isInverse() ) { actionQueue.addAction( new ReactiveCollectionRemoveAction( persister, key, session ) ); } @@ -190,11 +187,9 @@ else if ( type.isComponentType() ) { } } - private CompletionStage fetchAndDelete( - DeleteEvent event, - DeleteContext transientEntities, - EventSource source, - Object objectEvent) { + private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext transientEntities) { + final Object objectEvent = event.getObject(); + final EventSource source = event.getSession(); boolean detached = event.getEntityName() != null ? !source.contains( event.getEntityName(), objectEvent ) : !source.contains( objectEvent ); @@ -204,20 +199,21 @@ private CompletionStage fetchAndDelete( } //Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); + return ( (ReactiveSession) source ) + .reactiveFetch( objectEvent, true ) + .thenCompose( entity -> delete( event, transientEntities, entity ) ); - return ( (ReactiveSession) source ).reactiveFetch( objectEvent, true ) - .thenCompose( entity -> reactiveOnDelete( event, transientEntities, entity ) ); } - private CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities, Object entity) { - final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - if ( entityEntry == null ) { - return deleteTransientInstance( event, transientEntities, entity ); - } - else { - return deletePersistentInstance( event, transientEntities, entity, entityEntry ); + protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { + if ( persister.implementsLifecycle() ) { + LOG.debug( "Calling onDelete()" ); + if ( ( (Lifecycle) entity ).onDelete( session ) ) { + LOG.debug( "Deletion vetoed by onDelete()" ); + return true; + } } + return false; } private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { @@ -225,7 +221,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EventSource source = event.getSession(); - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity); + final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); return ForeignKeys.isTransient( persister.getEntityName(), entity, null, source.getSession() ) .thenCompose( trans -> { if ( trans ) { @@ -253,7 +249,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EntityEntry entry = persistenceContext.addEntity( entity, persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, - persister.getValues( entity ), + persister.getValues( entity ), key, version, LockMode.NONE, @@ -276,22 +272,12 @@ private CompletionStage deletePersistentInstance( LOG.trace( "Deleting a persistent instance" ); final EventSource source = event.getSession(); if ( entityEntry.getStatus().isDeletedOrGone() - || source.getPersistenceContextInternal() - .containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { + || source.getPersistenceContextInternal().containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { LOG.trace( "Object was already deleted" ); return voidFuture(); } else { - return delete( - event, - transientEntities, - source, - entity, - entityEntry.getPersister(), - entityEntry.getId(), - entityEntry.getVersion(), - entityEntry - ); + return delete( event, transientEntities, source, entity, entityEntry.getPersister(), entityEntry.getId(), entityEntry.getVersion(), entityEntry ); } } @@ -305,20 +291,23 @@ private CompletionStage delete( Object version, EntityEntry entry) { callbackRegistry.preRemove( entity ); - return deleteEntity( - source, - entity, - entry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source ); - } - } ); + if ( !invokeDeleteLifecycle( source, entity, persister ) ) { + return deleteEntity( + source, + entity, + entry, + event.isCascadeDeleteEnabled(), + event.isOrphanRemovalBeforeUpdates(), + persister, + transientEntities + ) + .thenAccept( v -> { + if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { + persister.resetIdentifier( entity, id, version, source ); + } + } ); + } + return voidFuture(); } /** @@ -326,13 +315,13 @@ private CompletionStage delete( */ private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister persister) { return source.getInterceptor() == EmptyInterceptor.INSTANCE - && !persister.implementsLifecycle() - && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc - && !persister.hasCascadeDelete() - && !persister.hasNaturalIdentifier() - && !persister.hasCollectionNotReferencingPK() - && !hasRegisteredRemoveCallbacks( persister ) - && !hasCustomEventListeners( source ); + && !persister.implementsLifecycle() + && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc + && !persister.hasCascadeDelete() + && !persister.hasNaturalIdentifier() + && !persister.hasCollectionNotReferencingPK() + && !hasRegisteredRemoveCallbacks( persister ) + && !hasCustomEventListeners( source ); } private static boolean hasCustomEventListeners(EventSource source) { @@ -340,16 +329,16 @@ private static boolean hasCustomEventListeners(EventSource source) { // Bean Validation adds a PRE_DELETE listener // and Envers adds a POST_DELETE listener return fss.eventListenerGroup_PRE_DELETE.count() > 0 - || fss.eventListenerGroup_POST_DELETE.count() > 1 - || fss.eventListenerGroup_POST_DELETE.count() == 1 - && !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() - instanceof PostDeleteEventListenerStandardImpl); + || fss.eventListenerGroup_POST_DELETE.count() > 1 + || fss.eventListenerGroup_POST_DELETE.count() == 1 + && !( fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() + instanceof PostDeleteEventListenerStandardImpl ); } private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) { final Class mappedClass = persister.getMappedClass(); return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE ) - || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); + || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); } /** @@ -385,11 +374,11 @@ private void disallowDeletionOfDetached(DeleteEvent event) { * passed to remove operation in which case cascades still need to be * performed. * - * @param session The session which is the source of the event - * @param entity The entity being delete processed - * @param persister The entity persister + * @param session The session which is the source of the event + * @param entity The entity being delete processed + * @param persister The entity persister * @param transientEntities A cache of already visited transient entities - * (to avoid infinite recursion). + * (to avoid infinite recursion). */ protected CompletionStage deleteTransientEntity( EventSource session, @@ -412,12 +401,12 @@ protected CompletionStage deleteTransientEntity( * really perform it; just schedules an action/execution with the * {@link org.hibernate.engine.spi.ActionQueue} for execution during flush. * - * @param session The originating session - * @param entity The entity to delete - * @param entityEntry The entity's entry in the {@link PersistenceContext} + * @param session The originating session + * @param entity The entity to delete + * @param entityEntry The entity's entry in the {@link PersistenceContext} * @param isCascadeDeleteEnabled Is delete cascading enabled? - * @param persister The entity persister. - * @param transientEntities A cache of already deleted entities. + * @param persister The entity persister. + * @param transientEntities A cache of already deleted entities. */ protected CompletionStage deleteEntity( final EventSource session, @@ -442,7 +431,7 @@ protected CompletionStage deleteEntity( final Object[] deletedState = createDeletedState( persister, entity, currentState, session ); entityEntry.setDeletedState( deletedState ); - session.getInterceptor().onDelete( + session.getInterceptor().onRemove( entity, entityEntry.getId(), deletedState, @@ -525,7 +514,7 @@ private Object[] createDeletedState( final String[] propertyNames = persister.getPropertyNames(); final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata(); - for ( int i = 0; i < types.length; i++) { + for ( int i = 0; i < types.length; i++ ) { if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) { final CollectionType collectionType = (CollectionType) types[i]; final CollectionPersister collectionDescriptor = persister.getFactory().getMappingMetamodel() @@ -533,7 +522,12 @@ private Object[] createDeletedState( if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) { final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() ); // This will make sure that a CollectionEntry exists - deletedState[i] = collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false ); + deletedState[i] = collectionType.getCollection( + keyOfOwner, + eventSource.getSession(), + parent, + false + ); } else { deletedState[i] = currentState[i]; @@ -556,13 +550,15 @@ protected CompletionStage cascadeBeforeDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to collections BEFORE the collection owner is deleted - return fetchLazyAssociationsBeforeCascade( CascadingActions.DELETE, persister, entity, session ) - .thenCompose( - v -> new Cascade<>( - CascadingActions.DELETE, - CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, transientEntities, session - ).cascade() + return fetchLazyAssociationsBeforeCascade( CascadingActions.REMOVE, persister, entity, session ) + .thenCompose( v -> Cascade.cascade( + CascadingActions.REMOVE, + CascadePoint.AFTER_INSERT_BEFORE_DELETE, + session, + persister, + entity, + transientEntities + ) ); } @@ -572,10 +568,13 @@ protected CompletionStage cascadeAfterDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to many-to-one AFTER the parent was deleted - return new Cascade<>( - CascadingActions.DELETE, + return Cascade.cascade( + CascadingActions.REMOVE, CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, transientEntities, session - ).cascade(); + session, + persister, + entity, + transientEntities + ); } } 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 5704f3faf..20466abd2 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 @@ -27,6 +27,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class DefaultReactiveInitializeCollectionEventListener implements InitializeCollectionEventListener { @@ -47,7 +48,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionEntry ce = source.getPersistenceContextInternal().getCollectionEntry( collection ); if ( ce == null ) { - throw LOG.collectionWasEvicted(); + return failedFuture( LOG.collectionWasEvicted() ); } if ( collection.wasInitialized() ) { @@ -58,10 +59,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionPersister loadedPersister = ce.getLoadedPersister(); final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Initializing collection {0}", - collectionInfoString( loadedPersister, collection, loadedKey, source ) - ); + LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, loadedKey, source ) ); LOG.trace( "Checking second-level cache" ); } @@ -129,8 +127,7 @@ private boolean initializeCollectionFromCache( PersistentCollection collection, SessionImplementor source) { - if ( source.getLoadQueryInfluencers().hasEnabledFilters() - && persister.isAffectedByEnabledFilters( source ) ) { + if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; } @@ -160,12 +157,12 @@ private boolean initializeCollectionFromCache( return false; } - final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) - persister.getCacheEntryStructure().destructure( ce, factory ); + final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) persister + .getCacheEntryStructure().destructure( ce, factory ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); cacheEntry.assemble( collection, persister, persistenceContext.getCollectionOwner( id, persister ) ); - persistenceContext.getCollectionEntry( collection ).postInitialize( collection ); + persistenceContext.getCollectionEntry( collection ).postInitialize( collection, source ); return true; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 7a356def1..7832298e0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -113,14 +113,14 @@ private CompletionStage lockEntry( } private CompletionStage cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { - return new Cascade<>( + return Cascade.cascade( CascadingActions.LOCK, CascadePoint.AFTER_LOCK, + event.getSession(), persister, entity, - event.getLockOptions(), - event.getSession() - ).cascade(); + event.getLockOptions() + ); } /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java index 6a23b8d57..7f258a1d4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java @@ -588,14 +588,14 @@ protected CompletionStage cascadeOnMerge( final Object entity, final MergeContext copyCache ) { - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_MERGE, + source, persister, entity, - copyCache, - source - ).cascade(); + copyCache + ); } 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 a68754536..f5e5eb1b4 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 @@ -5,9 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -23,7 +20,6 @@ import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.internal.EvictVisitor; @@ -46,6 +42,9 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.Type; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactific {@link org.hibernate.event.internal.DefaultRefreshEventListener}. */ @@ -150,11 +149,11 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte return cascadeRefresh( source, persister, entity, refreshedAlready ) .thenCompose( v -> { if ( entry != null ) { - final EntityKey key = source.generateEntityKey( id, persister ); - persistenceContext.removeEntity( key ); + persistenceContext.removeEntityHolder( entry.getEntityKey() ); if ( persister.hasCollections() ) { - new EvictVisitor( source, entity ).process( entity, persister ); + new EvictVisitor( source, object ).process( object, persister ); } + persistenceContext.removeEntry( object ); } evictEntity( entity, persister, id, source ); @@ -282,14 +281,14 @@ private CompletionStage cascadeRefresh( EntityPersister persister, Object object, RefreshContext refreshedAlready) { - return new Cascade( + return Cascade.cascade( CascadingActions.REFRESH, CascadePoint.BEFORE_REFRESH, + source, persister, object, - refreshedAlready, - source - ).cascade(); + refreshedAlready + ); } private void evictCachedCollections(EntityPersister persister, Object id, EventSource source) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java index 6501f68b6..8854f22d8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -14,7 +14,6 @@ import org.hibernate.HibernateException; import org.hibernate.Internal; -import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.EventType; @@ -172,6 +171,7 @@ private static CompletionStage readGeneratedValues( null, null, QueryOptions.NONE, + true, mappingProducer.resolve( directResultSetAccess, session.getLoadQueryInfluencers(), @@ -209,11 +209,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - LockOptions.NONE, + session.getSessionFactory(), RowTransformerArrayImpl.instance(), Object[].class, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( valuesProcessingState, executionContext, rowReader, jdbcValues ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java index d15de1779..224410853 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java @@ -10,9 +10,9 @@ import io.vertx.core.net.impl.pool.CombinerExecutor; import io.vertx.core.net.impl.pool.Executor; import io.vertx.core.net.impl.pool.Task; + import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.reactive.util.impl.CompletionStages; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -29,7 +29,6 @@ * @author Gavin King * @author Davide D'Alto * @author Sanne Grinovero - * */ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierGenerator { @@ -83,40 +82,41 @@ public CompletionStage generate(ReactiveConnectionSupplier connectionSuppl //(this does actually hit a synchronization, but it's extremely short) final long next = next(); if ( next != -1 ) { - return CompletionStages.completedFuture( next ); + return completedFuture( next ); } //Another special case we need to deal with; this is an unlikely configuration, but //if it were to happen we should be better off with direct execution rather than using //the co-operative executor: if ( getBlockSize() <= 1 ) { - return nextHiValue( connectionSupplier ) - .thenApply( i -> next( i ) ); + return nextHiValue( connectionSupplier ).thenApply( this::next ); } final CompletableFuture resultForThisEventLoop = new CompletableFuture<>(); final CompletableFuture result = new CompletableFuture<>(); - executor.submit( new GenerateIdAction( connectionSupplier, result ) ); final Context context = Vertx.currentContext(); - result.whenComplete( (id,t) -> { + executor.submit( new GenerateIdAction( connectionSupplier, result ) ); + result.whenComplete( (id, t) -> { final Context newContext = Vertx.currentContext(); //Need to be careful in resuming processing on the same context as the original //request, potentially having to switch back if we're no longer executing on the same: if ( newContext != context ) { if ( t != null ) { - context.runOnContext( ( v ) -> resultForThisEventLoop.completeExceptionally( t ) ); - } else { - context.runOnContext( ( v ) -> resultForThisEventLoop.complete( id ) ); + context.runOnContext( v -> resultForThisEventLoop.completeExceptionally( t ) ); + } + else { + context.runOnContext( v -> resultForThisEventLoop.complete( id ) ); } } else { if ( t != null ) { resultForThisEventLoop.completeExceptionally( t ); - } else { + } + else { resultForThisEventLoop.complete( id ); } } - }); + } ); return resultForThisEventLoop; } @@ -126,8 +126,8 @@ private final class GenerateIdAction implements Executor.Action private final CompletableFuture result; public GenerateIdAction(ReactiveConnectionSupplier connectionSupplier, CompletableFuture result) { - this.connectionSupplier = Objects.requireNonNull(connectionSupplier); - this.result = Objects.requireNonNull(result); + this.connectionSupplier = Objects.requireNonNull( connectionSupplier ); + this.result = Objects.requireNonNull( result ); } @Override @@ -137,21 +137,22 @@ public Task execute(GeneratorState state) { // We don't need to update or initialize the hi // value in the table, so just increment the lo // value and return the next id in the block - completedFuture( local ) - .whenComplete( this::acceptAsReturnValue ); + completedFuture( local ).whenComplete( this::acceptAsReturnValue ); return null; - } else { + } + else { nextHiValue( connectionSupplier ) .whenComplete( (newlyGeneratedHi, throwable) -> { if ( throwable != null ) { result.completeExceptionally( throwable ); - } else { + } + else { //We ignore the state argument as we actually use the field directly //for convenience, but they are the same object. executor.submit( stateIgnored -> { result.complete( next( newlyGeneratedHi ) ); return null; - }); + } ); } } ); return null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java index fd7cda2c5..6edfed3df 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java @@ -16,6 +16,8 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; +import static java.util.function.Function.identity; + /** * @author Gavin King */ @@ -46,7 +48,7 @@ public void initialize(SqlStringGenerationContext context) { @Override public CompletionStage generate(ReactiveConnectionSupplier session, Object entity) { - return reactiveGenerator.generate( session, entity ).thenApply( id -> id ); + return reactiveGenerator.generate( session, entity ).thenApply( identity() ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java index 6a8741de0..f1c45e42f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java @@ -81,18 +81,6 @@ public Generator createIdentifierGenerator(String strategy, Type type, Propertie throw new MappingException( String.format( "Not an id generator [entity-name=%s]", entityName ) ); } - //TODO: deleteme, after update to ORM - @Override - public Class getIdentifierGeneratorClass(String strategy) { - try { - return super.getIdentifierGeneratorClass( strategy ); - } - catch ( MappingException ignored ) { - // happens because the class does not implement Generator - return generatorClassForName( strategy ); - } - } - protected Class generatorClassForName(String strategy) { try { return serviceRegistry.getService( ClassLoaderService.class ).classForName( strategy ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java index cb71be1b8..3fdd5acf1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -44,7 +44,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.type.StandardBasicTypes; import org.jboss.logging.Logger; @@ -181,7 +181,13 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI // FIXME: use JdbcServices return StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, jdbcParameterBindings, new BaseExecutionContext( session ), RowTransformerDatabaseSnapshotImpl.instance(), ReactiveListResultsConsumer.UniqueSemantic.FILTER ) + .list( + jdbcSelect, + jdbcParameterBindings, + new BaseExecutionContext( session ), + RowTransformerArrayImpl.instance(), + ReactiveListResultsConsumer.UniqueSemantic.FILTER + ) .thenApply( list -> { assert list != null; final int size = list.size(); @@ -191,7 +197,7 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI return null; } - final Object[] entitySnapshot = (Object[]) list.get( 0 ); + final Object[] entitySnapshot = list.get( 0 ); // The result of this method is treated like the entity state array which doesn't include the id // So we must exclude it from the array if ( entitySnapshot.length == 1 ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java index a0f1135a7..c973d9713 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java @@ -11,13 +11,15 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.results.internal.ReactiveRowTransformerArrayImpl; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; import org.hibernate.sql.results.spi.RowTransformer; /** * A reactive load plan for loading an array of state by a single restrictive part. + * + * @see org.hibernate.loader.ast.internal.SingleIdArrayLoadPlan */ public class ReactiveSingleIdArrayLoadPlan extends ReactiveSingleIdLoadPlan { @@ -33,7 +35,6 @@ public ReactiveSingleIdArrayLoadPlan( @Override protected RowTransformer> getRowTransformer() { - return RowTransformerDatabaseSnapshotImpl.instance(); + return ReactiveRowTransformerArrayImpl.asRowTransformer(); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java index bdb9d692a..684894cf2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java @@ -30,11 +30,11 @@ public class ReactiveSingleIdEntityLoaderStandardImpl extends SingleIdEntityL public ReactiveSingleIdEntityLoaderStandardImpl( EntityMappingType entityDescriptor, - SessionFactoryImplementor sessionFactory) { + LoadQueryInfluencers loadQueryInfluencers) { super( entityDescriptor, - sessionFactory, - (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, sessionFactory ) + loadQueryInfluencers, + (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java index 2fd588727..5d5c03fb5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java @@ -25,8 +25,10 @@ public class ReactiveStandardBatchLoaderFactory implements BatchLoaderFactory { @Override public EntityBatchLoader createEntityBatchLoader( - int domainBatchSize, EntityMappingType entityDescriptor, - SessionFactoryImplementor factory) { + int domainBatchSize, + EntityMappingType entityDescriptor, + LoadQueryInfluencers loadQueryInfluencers) { + SessionFactoryImplementor factory = loadQueryInfluencers.getSessionFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); // NOTE : don't use the EntityIdentifierMapping here because it will not be known until later diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java index f7dca4f5c..26a3cbf12 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java @@ -5,16 +5,44 @@ */ package org.hibernate.reactive.metamodel.mapping.internal; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; + +import org.hibernate.cache.MutableCacheKeyBuilder; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.IndexedConsumer; +import org.hibernate.metamodel.mapping.AssociationKey; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl; +import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveCollectionDomainResult; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveEagerCollectionFetch; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; 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; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.type.descriptor.java.JavaType; public class ReactivePluralAttributeMapping extends PluralAttributeMappingImpl implements PluralAttributeMapping { @@ -58,4 +86,524 @@ protected Fetch buildEagerCollectionFetch( creationState ); } + + @Override + public ForeignKeyDescriptor getKeyDescriptor() { + if ( !super.getKeyDescriptor().isEmbedded() ) { + // Hibernate ORM has a check like this that will fail if ForeignKeyDescriptor is not an instance of + // SimpleForeignKeyDescriptor: see LoadSelectBuilder#applySubSelectRestriction line 1115 + return new ReactiveSimpleForeignKeyDescriptor( (SimpleForeignKeyDescriptor) super.getKeyDescriptor() ); + } + return new ReactiveForeignKeyDescriptor( super.getKeyDescriptor() ); + } + + private static DomainResult convert(DomainResult keyDomainResult) { + if ( keyDomainResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyDomainResult ); + } + return keyDomainResult; + } + + private static class ReactiveSimpleForeignKeyDescriptor extends SimpleForeignKeyDescriptor { + + protected ReactiveSimpleForeignKeyDescriptor(SimpleForeignKeyDescriptor original) { + super( original ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fromSide, fetchParent, creationState ) ); + } + } + + private static class ReactiveForeignKeyDescriptor implements ForeignKeyDescriptor { + + private final ForeignKeyDescriptor delegate; + + public ReactiveForeignKeyDescriptor(ForeignKeyDescriptor delegate) { + this.delegate = delegate; + } + + @Override + public String getPartName() { + return delegate.getPartName(); + } + + @Override + public String getKeyTable() { + return delegate.getKeyTable(); + } + + @Override + public String getTargetTable() { + return delegate.getTargetTable(); + } + + @Override + public ValuedModelPart getKeyPart() { + return delegate.getKeyPart(); + } + + @Override + public ValuedModelPart getTargetPart() { + return delegate.getTargetPart(); + } + + @Override + public boolean isKeyPart(ValuedModelPart modelPart) { + return delegate.isKeyPart( modelPart ); + } + + @Override + public ValuedModelPart getPart(Nature nature) { + return delegate.getPart( nature ); + } + + @Override + public Side getKeySide() { + return delegate.getKeySide(); + } + + @Override + public Side getTargetSide() { + return delegate.getTargetSide(); + } + + @Override + public Side getSide(Nature nature) { + return delegate.getSide( nature ); + } + + @Override + public String getContainingTableExpression() { + return delegate.getContainingTableExpression(); + } + + @Override + public int compare(Object key1, Object key2) { + return delegate.compare( key1, key2 ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fromSide, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createTargetDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return delegate.createTargetDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ); + } + + @Override + public DomainResult createDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + String resultVariable, + DomainResultCreationState creationState) { + return delegate.createDomainResult( navigablePath, targetTableGroup, resultVariable, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableGroup targetSideTableGroup, + TableGroup keySideTableGroup, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideTableGroup, keySideTableGroup, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableReference targetSideReference, + TableReference keySideReference, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideReference, keySideReference, creationState ); + } + + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return delegate.isSimpleJoinPredicate( predicate ); + } + + @Override + public SelectableMapping getSelectable(int columnIndex) { + return delegate.getSelectable( columnIndex ); + } + + @Override + public int forEachSelectable(int offset, SelectableConsumer consumer) { + return delegate.forEachSelectable( offset, consumer ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Nature nature, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, nature, session ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Side side, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, side, session ); + } + + @Override + public int visitKeySelectables(int offset, SelectableConsumer consumer) { + return delegate.visitKeySelectables( offset, consumer ); + } + + @Override + public int visitKeySelectables(SelectableConsumer consumer) { + return delegate.visitKeySelectables( consumer ); + } + + @Override + public int visitTargetSelectables(int offset, SelectableConsumer consumer) { + return delegate.visitTargetSelectables( offset, consumer ); + } + + @Override + public int visitTargetSelectables(SelectableConsumer consumer) { + return delegate.visitTargetSelectables( consumer ); + } + + @Override + public ForeignKeyDescriptor withKeySelectionMapping( + ManagedMappingType declaringType, + TableGroupProducer declaringTableGroupProducer, + IntFunction selectableMappingAccess, + MappingModelCreationProcess creationProcess) { + return delegate.withKeySelectionMapping( + declaringType, + declaringTableGroupProducer, + selectableMappingAccess, + creationProcess + ); + } + + @Override + public ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart) { + return delegate.withTargetPart( targetPart ); + } + + @Override + public AssociationKey getAssociationKey() { + return delegate.getAssociationKey(); + } + + @Override + public boolean hasConstraint() { + return delegate.hasConstraint(); + } + + @Override + public boolean isEmbedded() { + return delegate.isEmbedded(); + } + + @Override + public boolean isVirtual() { + return delegate.isVirtual(); + } + + @Override + public NavigableRole getNavigableRole() { + return delegate.getNavigableRole(); + } + + @Override + public MappingType getPartMappingType() { + return delegate.getPartMappingType(); + } + + @Override + public JavaType getJavaType() { + return delegate.getJavaType(); + } + + @Override + public boolean isEntityIdentifierMapping() { + return delegate.isEntityIdentifierMapping(); + } + + @Override + public boolean hasPartitionedSelectionMapping() { + return delegate.hasPartitionedSelectionMapping(); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + + @Override + public int forEachSelectable(SelectableConsumer consumer) { + return delegate.forEachSelectable( consumer ); + } + + @Override + public AttributeMapping asAttributeMapping() { + return delegate.asAttributeMapping(); + } + + @Override + public EntityMappingType asEntityMappingType() { + return delegate.asEntityMappingType(); + } + + @Override + public BasicValuedModelPart asBasicValuedModelPart() { + return delegate.asBasicValuedModelPart(); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, valueConsumer, session ); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public EntityMappingType findContainingEntityMapping() { + return delegate.findContainingEntityMapping(); + } + + @Override + public boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) { + return delegate.areEqual( one, other, session ); + } + + @Override + public int getJdbcTypeCount() { + return delegate.getJdbcTypeCount(); + } + + @Override + public int forEachJdbcType(IndexedConsumer action) { + return delegate.forEachJdbcType( action ); + } + + @Override + public Object disassemble(Object value, SharedSessionContractImplementor session) { + return delegate.disassemble( value, session ); + } + + @Override + public void addToCacheKey( + MutableCacheKeyBuilder cacheKey, + Object value, + SharedSessionContractImplementor session) { + delegate.addToCacheKey( cacheKey, value, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public JdbcMapping getJdbcMapping(int index) { + return delegate.getJdbcMapping( index ); + } + + @Override + public JdbcMapping getSingleJdbcMapping() { + return delegate.getSingleJdbcMapping(); + } + + @Override + public int forEachJdbcType(int offset, IndexedConsumer action) { + return delegate.forEachJdbcType( offset, action ); + } + + @Override + public void forEachInsertable(SelectableConsumer consumer) { + delegate.forEachInsertable( consumer ); + } + + @Override + public void forEachNonFormula(SelectableConsumer consumer) { + delegate.forEachNonFormula( consumer ); + } + + @Override + public void forEachUpdatable(SelectableConsumer consumer) { + delegate.forEachUpdatable( consumer ); + } + + @Override + public MappingType getMappedType() { + return delegate.getMappedType(); + } + + @Override + public JavaType getExpressibleJavaType() { + return delegate.getExpressibleJavaType(); + } + + @Override + public X treatAs(Class targetType) { + return delegate.treatAs( targetType ); + } + } } 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 09acc9bb9..47b4199de 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 @@ -9,6 +9,7 @@ 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.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; 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; @@ -19,6 +20,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -81,16 +83,25 @@ protected EntityFetch buildEntityDelayedFetch( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { + boolean selectByUniqueKey, + DomainResultCreationState creationState) { return new ReactiveEntityDelayedFetchImpl( fetchParent, fetchedAttribute, navigablePath, - keyResult, - selectByUniqueKey + convert( keyResult ), + selectByUniqueKey, + creationState ); } + private static DomainResult convert(DomainResult keyResult) { + if ( keyResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ); + } + return keyResult; + } + @Override public ReactiveToOneAttributeMapping copy( ManagedMappingType declaringType, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java index bfb19ea0d..4c1c67d9e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java @@ -15,6 +15,7 @@ import org.hibernate.FetchMode; import org.hibernate.LockOptions; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -27,7 +28,9 @@ import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ManagedMappingType; @@ -35,6 +38,7 @@ import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; @@ -54,11 +58,14 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.metamodel.mapping.internal.ReactivePluralAttributeMapping; import org.hibernate.reactive.metamodel.mapping.internal.ReactiveToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.internal.ReactiveEntityResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; 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; import org.hibernate.type.BasicType; import org.hibernate.type.EntityType; @@ -155,7 +162,7 @@ private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( } } - return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, factory ); + return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( factory ) ); } private static ReactiveSingleIdEntityLoader createBatchingIdEntityLoader( @@ -312,4 +319,45 @@ public AttributeMapping buildPluralAttributeMapping( ReactivePluralAttributeMapping::new ); } + + public EntityIdentifierMapping convertEntityIdentifierMapping(EntityIdentifierMapping entityIdentifierMapping) { + if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { + return new ReactiveNonAggregatedIdentifierMappingImpl( (NonAggregatedIdentifierMappingImpl) entityIdentifierMapping ); + } + return entityIdentifierMapping; + } + + private static class ReactiveNonAggregatedIdentifierMappingImpl extends NonAggregatedIdentifierMappingImpl { + + public ReactiveNonAggregatedIdentifierMappingImpl( + EntityPersister entityPersister, + RootClass bootEntityDescriptor, + String rootTableName, + String[] rootTableKeyColumnNames, + MappingModelCreationProcess creationProcess) { + super( entityPersister, bootEntityDescriptor, rootTableName, rootTableKeyColumnNames, creationProcess ); + } + + public ReactiveNonAggregatedIdentifierMappingImpl(NonAggregatedIdentifierMappingImpl entityIdentifierMapping) { + super( entityIdentifierMapping ); + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingFetch( + fetchablePath, + this, + fetchParent, + fetchTiming, + selected, + creationState + ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index b9bfdfcb2..8eca0a6f4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -388,7 +388,7 @@ public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedN } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 22454b546..e16523523 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -8,6 +8,7 @@ import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.FetchMode; import org.hibernate.HibernateException; @@ -31,6 +32,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; @@ -279,6 +281,18 @@ public void merge( throw LOG.nonReactiveMethodCall( "mergeReactive" ); } + @Override + protected EntityIdentifierMapping generateIdentifierMapping( + Supplier templateInstanceCreator, + PersistentClass bootEntityDescriptor, + MappingModelCreationProcess creationProcess) { + return reactiveDelegate.convertEntityIdentifierMapping( super.generateIdentifierMapping( + templateInstanceCreator, + bootEntityDescriptor, + creationProcess + ) ); + } + /** * Process properties generated with an insert * @@ -436,7 +450,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 7792a27e9..51eb33fad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -210,7 +210,6 @@ public Object insert(Object[] fields, Object object, SharedSessionContractImplem throw LOG.nonReactiveMethodCall( "insertReactive" ); } - /** * @see #insertReactive(Object[], Object, SharedSessionContractImplementor) */ @@ -423,7 +422,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw new UnsupportedOperationException( "use the reactive method: #getReactiveUniqueKeyLoader(String)" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 858e2d209..821f2c7f3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -40,6 +40,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -132,22 +133,35 @@ private static CompletionStage executeQueryInterpreter( .thenCompose( subSelectFetchKeyHandler -> session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - null, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareQueryStatement( sql, false, null ), - resultsConsumer + .executeQuery( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + null, + resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), + resultsConsumer ) ) ) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } + private static int resultCountEstimate( + CacheableSqmInterpretation sqmInterpretation, + JdbcParameterBindings jdbcParameterBindings) { + final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + .getFetchClauseExpression(); + return fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + } + @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { throw new UnsupportedOperationException(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 587fd95c7..f3a24808d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -38,12 +38,14 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.InitializeCollectionEvent; +import org.hibernate.event.spi.InitializeCollectionEventListener; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.LockEvent; @@ -689,7 +691,8 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection eventListenerGroupInitCollection = fastSessionServices.eventListenerGroup_INIT_COLLECTION; + return eventListenerGroupInitCollection .fireEventOnEachListener( event, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection 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 51e75108e..bcf33c942 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 @@ -5,17 +5,13 @@ */ package org.hibernate.reactive.sql.exec.internal; -import java.io.Serializable; -import java.sql.PreparedStatement; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import org.hibernate.CacheMode; -import org.hibernate.LockOptions; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.PersistenceContext; @@ -36,14 +32,16 @@ import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.exec.SqlExecLogger; import org.hibernate.sql.exec.internal.JdbcExecHelper; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowTransformer; @@ -78,19 +76,63 @@ public CompletionStage> list( RowTransformer rowTransformer, Class domainResultType, ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { - return executeQuery( + return list( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer()::prepareStatement, + uniqueSemantic, + -1 + ); + } + + /** + * @since 2.4 (and ORM 6.6) + */ + public CompletionStage> list( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class requestedJavaType, + ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic, + int resultCountEstimate) { + // Only do auto flushing for top level queries + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + requestedJavaType, + resultCountEstimate, ReactiveListResultsConsumer.instance( uniqueSemantic ) ); } + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + public CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + @Override public CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, @@ -98,7 +140,8 @@ public CompletionStage executeQuery( ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); @@ -110,7 +153,7 @@ public CompletionStage executeQuery( persistenceContext.setDefaultReadOnly( readOnly ); } - return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, statementCreator, resultsConsumer ) + return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, resultCountEstimate, statementCreator, resultsConsumer ) .thenCompose( list -> ( (ReactivePersistenceContextAdapter) persistenceContext ) // only initialize non-lazy collections after everything else has been refreshed .reactiveInitializeNonLazyCollections() @@ -129,10 +172,11 @@ private CompletionStage doExecuteQuery( ExecutionContext executionContext, RowTransformer transformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { - final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); return resolveJdbcValuesSource( executionContext.getQueryIdentifier( deferredResultSetAccess.getFinalSql() ), @@ -175,17 +219,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - // If follow-on locking is used, we must omit the lock options here, - // because these lock options are only for Initializers. - // If we wouldn't omit this, the follow-on lock requests would be no-ops, - // because the EntityEntries would already have the desired lock mode - deferredResultSetAccess.usesFollowOnLocking() - ? LockOptions.NONE - : executionContext.getQueryOptions().getLockOptions(), + executionContext.getSession().getSessionFactory(), rowTransformer, domainResultType, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( @@ -195,6 +232,8 @@ public boolean shouldReturnProxies() { jdbcValues ); + rowReader.startLoading( rowProcessingState ); + return resultsConsumer .consume( jdbcValues, @@ -237,7 +276,12 @@ private static RowTransformer rowTransformer( return rowTransformer; } - public CompletionStage resolveJdbcValuesSource(String queryIdentifier, JdbcOperationQuerySelect jdbcSelect, boolean canBeCached, ExecutionContext executionContext, ReactiveResultSetAccess resultSetAccess) { + public CompletionStage resolveJdbcValuesSource( + String queryIdentifier, + JdbcOperationQuerySelect jdbcSelect, + boolean canBeCached, + ExecutionContext executionContext, + ReactiveDeferredResultSetAccess resultSetAccess) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); @@ -252,7 +296,7 @@ public CompletionStage resolveJdbcValuesSource(String q if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.SQL_EXEC_LOGGER.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); final Set querySpaces = jdbcSelect.getAffectedTableNames(); - if ( querySpaces == null || querySpaces.size() == 0 ) { + if ( querySpaces == null || querySpaces.isEmpty() ) { SqlExecLogger.SQL_EXEC_LOGGER.tracef( "Unexpected querySpaces is empty" ); } else { @@ -313,41 +357,70 @@ public CompletionStage resolveJdbcValuesSource(String q if ( queryResultsCacheKey == null ) { return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, null, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + null, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + null, + executionContext + ) ); } else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - JdbcValuesMetadata metadataForCache = capturingMetadata.resolveMetadataForCache(); return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, metadataForCache, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } } else { + // TODO: Implements JdbcValuesCacheHit for reactive, see JdbcSelectExecutorStandardImpl#resolveJdbcValuesSource // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - final CompletionStage stage; if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { - stage = mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ); + return mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } else { - stage = mappingProducer.reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ); + return mappingProducer + .reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } - return stage.thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - jdbcValuesMapping, - capturingMetadata, - executionContext - ) ); } } /** - * see {@link org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.CapturingJdbcValuesMetadata} + * see {@link CachedJdbcValuesMetadata} */ public static class CapturingJdbcValuesMetadata implements JdbcValuesMetadata { private final ReactiveResultSetAccess resultSetAccess; @@ -423,7 +496,7 @@ public BasicType resolveType( return basicType; } - public JdbcValuesMetadata resolveMetadataForCache() { + public CachedJdbcValuesMetadata resolveMetadataForCache() { if ( columnNames == null ) { return null; } @@ -431,60 +504,6 @@ public JdbcValuesMetadata resolveMetadataForCache() { } } - private static class CachedJdbcValuesMetadata implements JdbcValuesMetadata, Serializable { - private final String[] columnNames; - private final BasicType[] types; - - public CachedJdbcValuesMetadata(String[] columnNames, BasicType[] types) { - this.columnNames = columnNames; - this.types = types; - } - - @Override - public int getColumnCount() { - return columnNames.length; - } - - @Override - public int resolveColumnPosition(String columnName) { - final int position = ArrayHelper.indexOf( columnNames, columnName ) + 1; - if ( position == 0 ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column: " + columnName ); - } - return position; - } - - @Override - public String resolveColumnName(int position) { - final String name = columnNames[position - 1]; - if ( name == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - return name; - } - - @Override - public BasicType resolveType( - int position, - JavaType explicitJavaType, - TypeConfiguration typeConfiguration) { - final BasicType type = types[position - 1]; - if ( type == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - if ( explicitJavaType == null || type.getJavaTypeDescriptor() == explicitJavaType ) { - //noinspection unchecked - return (BasicType) type; - } - else { - return typeConfiguration.getBasicTypeRegistry().resolve( - explicitJavaType, - type.getJdbcType() - ); - } - } - } - private static class Statistics { private final ExecutionContext executionContext; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java index 034a92aa6..09ecd0b10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java @@ -5,18 +5,14 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import org.hibernate.LockMode; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; @@ -29,15 +25,14 @@ */ public class ReactiveRowProcessingState extends BaseExecutionContext implements RowProcessingState { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState; - private final ReactiveInitializersList initializers; - private final ReactiveRowReader rowReader; private final ReactiveValuesResultSet jdbcValues; private final ExecutionContext executionContext; + private final boolean needsResolveState; + + private final InitializerData[] initializerData; public ReactiveRowProcessingState( JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState, @@ -48,8 +43,10 @@ public ReactiveRowProcessingState( this.resultSetProcessingState = resultSetProcessingState; this.executionContext = executionContext; this.rowReader = rowReader; - this.initializers = rowReader.getReactiveInitializersList(); this.jdbcValues = jdbcValues; + this.needsResolveState = !isQueryCacheHit() + && getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; + this.initializerData = new InitializerData[rowReader.getInitializerCount()]; } public CompletionStage next() { @@ -61,6 +58,42 @@ public JdbcValuesSourceProcessingState getJdbcValuesSourceProcessingState() { return resultSetProcessingState; } + @Override + public Object getEntityId() { + return executionContext.getEntityId(); + } + + @Override + public LockMode determineEffectiveLockMode(String alias) { + if ( jdbcValues.usesFollowOnLocking() ) { + // If follow-on locking is used, we must omit the lock options here, + // because these lock options are only for Initializers. + // If we wouldn't omit this, the follow-on lock requests would be no-ops, + // because the EntityEntrys would already have the desired lock mode + return LockMode.NONE; + } + final LockMode effectiveLockMode = resultSetProcessingState.getQueryOptions().getLockOptions() + .getEffectiveLockMode( alias ); + return effectiveLockMode == LockMode.NONE + ? jdbcValues.getValuesMapping().determineDefaultLockMode( alias, effectiveLockMode ) + : effectiveLockMode; + } + + @Override + public boolean needsResolveState() { + return needsResolveState; + } + + @Override + public T getInitializerData(int initializerId) { + return (T) initializerData[initializerId]; + } + + @Override + public void setInitializerData(int initializerId, InitializerData state) { + initializerData[initializerId] = state; + } + @Override public RowReader getRowReader() { return rowReader; @@ -82,19 +115,12 @@ public boolean isQueryCacheHit() { return false; } - public void finishRowProcessing() { - } - @Override - public Initializer resolveInitializer(NavigablePath path) { - return this.initializers.resolveInitializer( path ); + public void finishRowProcessing(boolean wasAdded) { + jdbcValues.finishRowProcessing( this, wasAdded ); } public QueryOptions getQueryOptions() { return this.executionContext.getQueryOptions(); } - - public boolean hasCollectionInitializers() { - return this.initializers.hasCollectionInitializers(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java index b41cd90ab..ab68e34be 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -22,13 +22,37 @@ */ public interface ReactiveSelectExecutor { + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + default CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer); CompletionStage> list( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java index 4323e04bd..f1dfa276b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.DataException; import org.hibernate.exception.LockTimeoutException; +import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.results.internal.ReactiveResultSetAccess; import org.hibernate.sql.ast.spi.SqlSelection; @@ -28,8 +29,8 @@ import org.hibernate.sql.results.caching.QueryCachePutManager; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerEnabledImpl; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -46,6 +47,8 @@ public class ReactiveValuesResultSet { private final ReactiveResultSetAccess resultSetAccess; private final JdbcValuesMapping valuesMapping; private final ExecutionContext executionContext; + private final boolean usesFollowOnLocking; + private final int resultCountEstimate; private final SqlSelection[] sqlSelections; private final BitSet initializedIndexes; private final Object[] currentRowJdbcValues; @@ -54,19 +57,29 @@ public class ReactiveValuesResultSet { // Contains the size of the row to cache, or if the value is negative, // represents the inverted index of the single value to cache private final int rowToCacheSize; + private int resultCount; public ReactiveValuesResultSet( ReactiveResultSetAccess resultSetAccess, QueryKey queryCacheKey, String queryIdentifier, QueryOptions queryOptions, + boolean usesFollowOnLocking, JdbcValuesMapping valuesMapping, - JdbcValuesMetadata metadataForCache, + CachedJdbcValuesMetadata metadataForCache, ExecutionContext executionContext) { - this.queryCachePutManager = resolveQueryCachePutManager( executionContext, queryOptions, queryCacheKey, queryIdentifier, metadataForCache ); + this.queryCachePutManager = resolveQueryCachePutManager( + executionContext, + queryOptions, + queryCacheKey, + queryIdentifier, + metadataForCache + ); this.resultSetAccess = resultSetAccess; this.valuesMapping = valuesMapping; this.executionContext = executionContext; + this.usesFollowOnLocking = usesFollowOnLocking; + this.resultCountEstimate = determineResultCountEstimate( resultSetAccess, queryOptions ); final int rowSize = valuesMapping.getRowSize(); this.sqlSelections = new SqlSelection[rowSize]; @@ -116,12 +129,27 @@ public ReactiveValuesResultSet( } } + private int determineResultCountEstimate( + ReactiveResultSetAccess resultSetAccess, + QueryOptions queryOptions) { + final Limit limit = queryOptions.getLimit(); + if ( limit != null && limit.getMaxRows() != null ) { + return limit.getMaxRows(); + } + + final int resultCountEstimate = resultSetAccess.getResultCountEstimate(); + if ( resultCountEstimate > 0 ) { + return resultCountEstimate; + } + return -1; + } + private static QueryCachePutManager resolveQueryCachePutManager( ExecutionContext executionContext, QueryOptions queryOptions, QueryKey queryCacheKey, String queryIdentifier, - JdbcValuesMetadata metadataForCache) { + CachedJdbcValuesMetadata metadataForCache) { if ( queryCacheKey != null ) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final QueryResultsCache queryCache = factory.getCache() @@ -182,13 +210,20 @@ public Object[] getCurrentRowValuesArray() { public void finishUp(SharedSessionContractImplementor session) { if ( queryCachePutManager != null ) { - queryCachePutManager.finishUp( session ); + queryCachePutManager.finishUp( resultCount, session ); } resultSetAccess.release(); } + public boolean usesFollowOnLocking() { + return usesFollowOnLocking; + } + public void finishRowProcessing(RowProcessingState rowProcessingState, boolean wasAdded) { if ( queryCachePutManager != null ) { + if ( wasAdded ) { + resultCount++; + } final Object objectToCache; if ( valueIndexesToCacheIndexes == null ) { objectToCache = Arrays.copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); @@ -246,4 +281,8 @@ private CompletionStage readCurrentRowValues(boolean hasResults) { return true; } ); } + + public int getResultCountEstimate() { + return resultCountEstimate; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java index 84bef8045..8580a8b68 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java @@ -21,7 +21,7 @@ CompletionStage reactiveAssemble( JdbcValuesSourceProcessingOptions options); /** - * Convenience form of {@link #assemble(RowProcessingState, JdbcValuesSourceProcessingOptions)} + * Convenience form of {@link #assemble(RowProcessingState)} */ default CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState) { return reactiveAssemble( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java index bff508832..c20b4775d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java @@ -6,18 +6,62 @@ package org.hibernate.reactive.sql.results.graph; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; import org.hibernate.Incubating; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + /** * @see org.hibernate.sql.results.graph.Initializer */ @Incubating -public interface ReactiveInitializer { +public interface ReactiveInitializer { + + /** + * The current data of this initializer. + */ + Data getData(RowProcessingState rowProcessingState); + + CompletionStage reactiveResolveInstance(Data data); + + /** + * @see org.hibernate.sql.results.graph.internal.AbstractInitializer#resolveKey(InitializerData) + */ + default CompletionStage reactiveResolveKey(Data data) { + data.setState( Initializer.State.KEY_RESOLVED ); + return forEachReactiveSubInitializer( ReactiveInitializer::reactiveResolveKey, data ); + } + + default CompletionStage reactiveResolveKey(RowProcessingState rowProcessingState) { + Data data = getData( rowProcessingState ); + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, Data data) { + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, RowProcessingState rowProcessingState) { + return reactiveResolveInstance( instance, getData( rowProcessingState ) ); + } + + default CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveResolveInstance( getData( rowProcessingState ) ); + } + + CompletionStage reactiveInitializeInstance(Data data); - CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState); + default CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveInitializeInstance( getData( rowProcessingState ) ); + } - CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState); + CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data); + Object getResolvedInstance(Data data); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java index 6a60e89db..f1bb6fa9c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java @@ -5,16 +5,22 @@ */ package org.hibernate.reactive.sql.results.graph.collection.internal; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.collection.CollectionInitializer; import org.hibernate.sql.results.graph.collection.internal.CollectionDomainResult; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; public class ReactiveCollectionDomainResult extends CollectionDomainResult { @@ -48,4 +54,92 @@ public Fetch generateFetchableFetch( } return fetch; } + + @Override + public CollectionInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + CollectionInitializer initializer = super.createInitializer( parent, creationState ); + return new ReactiveCollectionInitializerAdapter<>( initializer ); + } + + public static class ReactiveCollectionInitializerAdapter + implements CollectionInitializer { + + private final CollectionInitializer delegate; + + public ReactiveCollectionInitializerAdapter(CollectionInitializer initializer) { + this.delegate = initializer; + } + + @Override + public NavigablePath getNavigablePath() { + return delegate.getNavigablePath(); + } + + @Override + public PluralAttributeMapping getInitializedPart() { + return delegate.getInitializedPart(); + } + + @Override + public T getData(RowProcessingState rowProcessingState) { + return delegate.getData( rowProcessingState ); + } + + @Override + public void startLoading(RowProcessingState rowProcessingState) { + delegate.startLoading( rowProcessingState ); + } + + @Override + public void resolveKey(T data) { + delegate.resolveKey( data ); + } + + @Override + public void resolveInstance(T data) { + delegate.resolveInstance( data ); + } + + @Override + public void resolveState(T data) { + + } + + @Override + public void initializeInstance(T data) { + delegate.initializeInstance( data ); + } + + @Override + public void finishUpRow(T data) { + delegate.finishUpRow( data ); + } + + @Override + public boolean isPartOfKey() { + return delegate.isPartOfKey(); + } + + @Override + public boolean isEager() { + return delegate.isEager(); + } + + @Override + public boolean hasLazySubInitializers() { + return delegate.hasLazySubInitializers(); + } + + @Override + public boolean isResultInitializer() { + return delegate.isResultInitializer(); + } + + @Override + public PersistentCollection getCollectionInstance(T data) { + return delegate.getCollectionInstance( data ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java new file mode 100644 index 000000000..50034533b --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -0,0 +1,40 @@ +/* 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.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; + +public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { + + public ReactiveEmbeddableFetchImpl( + NavigablePath navigablePath, + EmbeddableValuedFetchable embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java new file mode 100644 index 000000000..a708481cf --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -0,0 +1,27 @@ +/* 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.graph.embeddable.internal; + +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; + +public class ReactiveEmbeddableForeignKeyResultImpl extends EmbeddableForeignKeyResultImpl { + + public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return getReferencedModePart() instanceof NonAggregatedIdentifierMapping + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new EmbeddableInitializerImpl( this, null, null, creationState, true ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java new file mode 100644 index 000000000..3e760d9b0 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -0,0 +1,91 @@ +/* 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.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl + implements ReactiveInitializer { + + private static class ReactiveEmbeddableInitializerData extends EmbeddableInitializerData { + + public ReactiveEmbeddableInitializerData( + EmbeddableInitializerImpl initializer, + RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() { + return super.concreteEmbeddableType; + } + } + + public ReactiveEmbeddableInitializerImpl( + EmbeddableResultGraphNode resultDescriptor, + BasicFetch discriminatorFetch, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEmbeddableInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; + final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); + if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { + return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ) ); + } + else { + Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; + return loop( 0, initializers.length, i -> { + ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; + return consumer.apply( reactiveInitializer, rowProcessingState ); + } ); + } + } + + @Override + public Object getResolvedInstance(EmbeddableInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java new file mode 100644 index 000000000..2af264526 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java @@ -0,0 +1,69 @@ +/* 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.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; + +public class ReactiveNonAggregatedIdentifierMappingFetch extends ReactiveEmbeddableFetchImpl { + public ReactiveNonAggregatedIdentifierMappingFetch( + NavigablePath navigablePath, + NonAggregatedIdentifierMapping embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveNonAggregatedIdentifierMappingFetch(NonAggregatedIdentifierMappingFetch fetch) { + super( fetch ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingInitializer( this, parent, creationState, false ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java new file mode 100644 index 000000000..0096e8d0f --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java @@ -0,0 +1,108 @@ +/* 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.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveNonAggregatedIdentifierMappingInitializer extends NonAggregatedIdentifierMappingInitializer + implements ReactiveInitializer { + + public ReactiveNonAggregatedIdentifierMappingInitializer( + EmbeddableResultGraphNode resultDescriptor, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( + resultDescriptor, + parent, + creationState, + isResultInitializer, + ReactiveNonAggregatedIdentifierMappingInitializer::convertFetch + ); + } + + private static Fetch convertFetch(Fetch fetch) { + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } + + @Override + public CompletionStage reactiveResolveKey(NonAggregatedIdentifierMappingInitializerData data) { + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + // We need to possibly wrap the processing state if the embeddable is within an aggregate + data.setInstance( null ); + data.setState( State.KEY_RESOLVED ); + if ( getInitializers().length == 0 ) { + // Resolve the component early to know if the key is missing or not + return reactiveResolveInstance( data ); + } + else { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final boolean[] dataIsMissing = {false}; + return loop( getInitializers(), initializer -> { + if ( dataIsMissing[0] ) { + return voidFuture(); + } + final InitializerData subData = ( (ReactiveInitializer) initializer ) + .getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenAccept( v -> { + if ( subData.getState() == State.MISSING ) { + data.setState( State.MISSING ); + dataIsMissing[0] = true; + } + } ); + } ); + } + } + + @Override + public CompletionStage reactiveResolveInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( getInitializers(), initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + + @Override + public Object getResolvedInstance(NonAggregatedIdentifierMappingInitializerData data) { + return super.getResolvedInstance( data ); + } +} 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 deleted file mode 100644 index 82aaa435d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java +++ /dev/null @@ -1,219 +0,0 @@ -/* 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.graph.entity; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.CompletionStage; - -import org.hibernate.LockMode; -import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.Status; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -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.jdbc.spi.RowProcessingState; -import org.hibernate.stat.spi.StatisticsImplementor; - -import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; -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.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public abstract class ReactiveAbstractEntityInitializer extends AbstractEntityInitializer implements ReactiveInitializer { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - this( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - } - - @Override - public void resolveInstance(RowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - } - - @Override - public void initializeInstance(RowProcessingState rowProcessingState) { - throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); - } - - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - return voidFuture(); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state == State.KEY_RESOLVED || state == State.RESOLVED ) { - return initializeEntity( getEntityInstanceForNotify(), rowProcessingState ) - .thenAccept( v -> state = State.INITIALIZED ); - } - return voidFuture(); - } - - protected CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { - if ( !skipInitialization( toInitialize, rowProcessingState ) ) { - assert consistentInstance( toInitialize, rowProcessingState ); - return initializeEntityInstance( toInitialize, rowProcessingState ); - } - return voidFuture(); - } - - - protected CompletionStage reactiveExtractConcreteTypeStateValues(RowProcessingState rowProcessingState) { - final Object[] values = new Object[getConcreteDescriptor().getNumberOfAttributeMappings()]; - final DomainResultAssembler[] concreteAssemblers = getAssemblers()[getConcreteDescriptor().getSubclassId()]; - return loop( 0, values.length, i -> { - final DomainResultAssembler assembler = concreteAssemblers[i]; - if ( assembler instanceof ReactiveDomainResultsAssembler) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) - .thenAccept( obj -> values[i] = obj ); - } - else { - values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); - return voidFuture(); - } - } ).thenApply( unused -> values ); - } - - private CompletionStage initializeEntityInstance(Object toInitialize, RowProcessingState rowProcessingState) { - final Object entityIdentifier = getEntityKey().getIdentifier(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#initializeInstance process for entity %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - getEntityDescriptor().setIdentifier( toInitialize, entityIdentifier, session ); - return reactiveExtractConcreteTypeStateValues( rowProcessingState ) - .thenCompose( entityState -> loop( 0, entityState.length, i -> { - if ( entityState[i] instanceof CompletionStage ) { - return ( (CompletionStage) entityState[i] ) - .thenAccept( state -> entityState[i] = state ); - } - return voidFuture(); - } ).thenAccept( v -> setResolvedEntityState( entityState ) ) - ) - .thenAccept( v -> { - if ( isPersistentAttributeInterceptable(toInitialize) ) { - PersistentAttributeInterceptor persistentAttributeInterceptor = - asPersistentAttributeInterceptable( toInitialize ).$$_hibernate_getInterceptor(); - if ( persistentAttributeInterceptor == null - || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - // if we do this after the entity has been initialized the - // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; - getConcreteDescriptor().getBytecodeEnhancementMetadata() - .injectInterceptor( toInitialize, entityIdentifier, session ); - } - } - getConcreteDescriptor().setValues( toInitialize, getResolvedEntityState() ); - persistenceContext.addEntity( getEntityKey(), toInitialize ); - - // Also register possible unique key entries - registerPossibleUniqueKeyEntries( toInitialize, session ); - - 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 = getLockMode() == LockMode.NONE ? LockMode.READ : getLockMode(); - - final EntityEntry entityEntry = persistenceContext.addEntry( - toInitialize, - Status.LOADING, - getResolvedEntityState(), - rowId, - getEntityKey().getIdentifier(), - version, - lockModeToAcquire, - true, - getConcreteDescriptor(), - false - ); - - updateCaches( toInitialize, rowProcessingState, session, persistenceContext, entityIdentifier, version ); - registerNaturalIdResolution( persistenceContext, entityIdentifier ); - takeSnapshot( rowProcessingState, session, persistenceContext, entityEntry ); - getConcreteDescriptor().afterInitialize( toInitialize, session ); - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Done materializing entityInstance : %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - if ( !rowProcessingState.isQueryCacheHit() ) { - statistics.loadEntity( getConcreteDescriptor().getEntityName() ); - } - } - } ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java index be90edbb6..eb57f92fa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java @@ -5,21 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; - import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.descriptor.java.JavaType; -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; /** * @see org.hibernate.sql.results.graph.entity.internal.EntityAssembler @@ -27,19 +25,7 @@ public class ReactiveEntityAssembler extends EntityAssembler implements ReactiveDomainResultsAssembler { public ReactiveEntityAssembler(JavaType javaType, EntityInitializer initializer) { - super(javaType, initializer); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); + super( javaType, initializer ); } @Override @@ -47,9 +33,14 @@ public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowPr // Ensure that the instance really is initialized // This is important for key-many-to-ones that are part of a collection key fk, // as the instance is needed for resolveKey before initializing the instance in RowReader - return ( (ReactiveInitializer) getInitializer() ) - .reactiveResolveInstance( rowProcessingState ) - .thenApply( v -> getInitializer().getEntityInstance() ); - + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); } } 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 e761ce183..1a1660725 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 @@ -6,7 +6,9 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.FetchNotFoundException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; @@ -19,53 +21,111 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; 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.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.Type; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.determineConcreteEntityDescriptor; -public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer implements ReactiveInitializer { +public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer + implements ReactiveInitializer { private final ToOneAttributeMapping referencedModelPart; public ReactiveEntityDelayedFetchInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, NavigablePath fetchedNavigable, ToOneAttributeMapping referencedModelPart, boolean selectByUniqueKey, - DomainResultAssembler identifierAssembler) { - super( parentAccess, fetchedNavigable, referencedModelPart, selectByUniqueKey, identifierAssembler ); + DomainResult keyResult, + BasicFetch discriminatorResult, + AssemblerCreationState creationState) { + super( + parent, + fetchedNavigable, + referencedModelPart, + selectByUniqueKey, + convert(keyResult), + discriminatorResult, + creationState + ); this.referencedModelPart = referencedModelPart; } - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( isProcessed() ) { - return voidFuture(); + private static DomainResult convert(DomainResult keyResult) { + return keyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ) + : keyResult; + } + + public static class ReactiveEntityDelayedFetchInitializerData extends EntityDelayedFetchInitializerData { + + public ReactiveEntityDelayedFetchInitializerData(RowProcessingState rowProcessingState) { + super( rowProcessingState ); } - setProcessed( true ); + public Object getEntityIdentifier() { + return entityIdentifier; + } - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { + public void setEntityIdentifier(Object entityIdentifier) { + this.entityIdentifier = entityIdentifier; + } + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityDelayedFetchInitializerData( rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityDelayedFetchInitializerData initializerData) { + if ( initializerData.getState() != State.KEY_RESOLVED ) { return voidFuture(); } - setIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); + ReactiveEntityDelayedFetchInitializerData data = (ReactiveEntityDelayedFetchInitializerData) initializerData; + data.setState( State.RESOLVED ); + + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); CompletionStage stage = voidFuture(); - if ( getIdentifier() == null ) { - setEntityInstance( null ); + if ( data.getEntityIdentifier() == null ) { + data.setInstance( null ); + data.setState( State.MISSING ); } else { final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = referencedModelPart.getEntityMappingType().getEntityPersister(); + + 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(); @@ -75,58 +135,67 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState final EntityUniqueKey euk = new EntityUniqueKey( concreteDescriptor.getEntityName(), uniqueKeyPropertyName, - getIdentifier(), + data.getEntityIdentifier(), uniqueKeyPropertyType, session.getFactory() ); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( getEntityInstance() == null ) { + 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() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, getIdentifier(), session ) ) - .thenAccept( this::setEntityInstance ) + .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 ( getEntityInstance() != null ) { - persistenceContext.addEntity( euk, getEntityInstance() ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } } stage = stage.thenAccept( v -> { - if ( getEntityInstance() != null ) { - setEntityInstance( persistenceContext.proxyFor( getEntityInstance() ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } } ); } else { - final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); if ( holder != null && holder.getEntity() != null ) { - setEntityInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); + 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() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup .extract( session ) - .reactiveInternalLoad( concreteDescriptor.getEntityName(), getIdentifier(), false, false ) - .thenAccept( this::setEntityInstance ) + .reactiveInternalLoad( + concreteDescriptor.getEntityName(), + data.getEntityIdentifier(), + false, + false + ) + .thenAccept( data::setInstance ) ); } stage = stage .thenAccept( v -> { - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( getEntityInstance() ); + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); } @@ -137,7 +206,24 @@ else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + public CompletionStage reactiveInitializeInstance(EntityDelayedFetchInitializerData data) { + // No-op by default + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveInitializer initializer = (ReactiveInitializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( initializer, data.getRowProcessingState() ); + } return voidFuture(); } + + @Override + public Object getResolvedInstance(EntityDelayedFetchInitializerData data) { + return super.getResolvedInstance( data ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java index ffc2f047c..7bf7bfb5f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java @@ -5,9 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; +import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; +import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; public class ReactiveEntityFetchJoinedImpl extends EntityFetchJoinedImpl { @@ -16,19 +26,57 @@ public ReactiveEntityFetchJoinedImpl(EntityFetchJoinedImpl entityFetch) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityJoinedFetchInitializer( - getEntityResult(), - getReferencedModePart(), - getNavigablePath(), - creationState.determineEffectiveLockMode( getSourceAlias() ), - getNotFoundAction(), + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + Fetch identifierFetch = convert( getEntityResult().getIdentifierFetch() ); + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + identifierFetch, + getEntityResult().getDiscriminatorFetch(), getKeyResult(), getEntityResult().getRowIdResult(), - getEntityResult().getIdentifierFetch(), - getEntityResult().getDiscriminatorFetch(), - parentAccess, + getNotFoundAction(), + isAffectedByFilter(), + parent, + false, + creationState + ); + } + + private static Fetch convert(Fetch fetch) { + if ( fetch instanceof ReactiveEmbeddableFetchImpl ) { + return fetch; + } + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + return fetch; + } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, creationState ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java index 7caff3460..0cae0a79d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java @@ -6,7 +6,7 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -18,14 +18,15 @@ public ReactiveEntityFetchSelectImpl(EntityFetchSelectImpl original) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, getFetchedMapping(), getReferencedMappingContainer().getEntityPersister(), getKeyResult(), getNavigablePath(), isSelectByUniqueKey(), + isAffectedByFilter(), creationState ); } 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 new file mode 100644 index 000000000..a00287eab --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -0,0 +1,853 @@ +/* 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.graph.entity.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.Hibernate; +import org.hibernate.LockMode; +import org.hibernate.annotations.NotFoundAction; +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.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.LazyInitializer; +import org.hibernate.proxy.map.MapProxy; +import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; +import org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.type.Type; + +import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.metamodel.mapping.ForeignKeyDescriptor.Nature.TARGET; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; +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.trueFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEntityInitializerImpl extends EntityInitializerImpl + implements ReactiveInitializer { + + public static class ReactiveEntityInitializerData extends EntityInitializerData { + + public ReactiveEntityInitializerData(EntityInitializerImpl initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public void setEntityInstanceForNotify(Object instance) { + super.entityInstanceForNotify = instance; + } + + public Object getEntityInstanceForNotify() { + return super.entityInstanceForNotify; + } + + public EntityPersister getConcreteDescriptor() { + return super.concreteDescriptor; + } + + public void setConcreteDescriptor(EntityPersister entityPersister) { + super.concreteDescriptor = entityPersister; + } + + public EntityHolder getEntityHolder() { + return super.entityHolder; + } + + public void setEntityHolder(EntityHolder entityHolder) { + super.entityHolder = entityHolder; + } + + public EntityKey getEntityKey() { + return super.entityKey; + } + + public void setEntityKey(EntityKey entityKey) { + super.entityKey = entityKey; + } + + public String getUniqueKeyAttributePath() { + return super.uniqueKeyAttributePath; + } + + public Type[] getUniqueKeyPropertyTypes() { + return super.uniqueKeyPropertyTypes; + } + + public boolean getShallowCached() { + return super.shallowCached; + } + + public LockMode getLockMode() { + return super.lockMode; + } + } + + public ReactiveEntityInitializerImpl( + EntityResultGraphNode resultDescriptor, + String sourceAlias, + Fetch identifierFetch, + Fetch discriminatorFetch, + DomainResult keyResult, + DomainResult rowIdResult, + NotFoundAction notFoundAction, + boolean affectedByFilter, + InitializerParent parent, + boolean isResultInitializer, + AssemblerCreationState creationState) { + super( + resultDescriptor, + sourceAlias, + identifierFetch , + discriminatorFetch, + keyResult, + rowIdResult, + notFoundAction, + affectedByFilter, + parent, + isResultInitializer, + creationState + ); + } + + @Override + protected void resolveEntityKey(EntityInitializerData original, Object id) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getConcreteDescriptor() == null ) { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + data.getRowProcessingState(), + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + } + data.setEntityKey( new EntityKey( id, data.getConcreteDescriptor() ) ); + } + + @Override + public CompletionStage reactiveResolveInstance(Object instance, EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( instance == null ) { + setMissing( data ); + return voidFuture(); + } + data.setInstance( instance ); + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + if ( lazyInitializer == null ) { + // Entity is most probably initialized + data.setEntityInstanceForNotify( data.getInstance() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getInstance() ) ); + resolveEntityKey( data, data.getConcreteDescriptor().getIdentifier( data.getInstance(), session ) ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + if ( data.getEntityHolder() == null ) { + // Entity was most probably removed in the same session without setting the reference to null + return reactiveResolveKey( data ) + .thenRun( () -> { + assert data.getState() == State.MISSING; + assert getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).getSideNature() == TARGET; + } ); + } + // If the entity initializer is null, we know the entity is fully initialized, + // otherwise it will be initialized by some other initializer + data.setState( data.getEntityHolder().getEntityInitializer() == null ? State.INITIALIZED : State.RESOLVED ); + } + else if ( lazyInitializer.isUninitialized() ) { + data.setState( State.RESOLVED ); + // Read the discriminator from the result set if necessary + EntityPersister persister = getDiscriminatorAssembler() == null + ? getEntityDescriptor() + : determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), getEntityDescriptor() ); + data.setConcreteDescriptor( persister ); + assert data.getConcreteDescriptor() != null; + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + // Resolve and potentially create the entity instance + data.setEntityInstanceForNotify( resolveEntityInstance( data ) ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + registerLoadingEntity( data, data.getEntityInstanceForNotify() ); + } + else { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( lazyInitializer.getImplementation() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getEntityInstanceForNotify() ) ); + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + } + return reactiveInitializeStage( data, rowProcessingState ) + .thenCompose( v -> { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + resolveInstanceSubInitializers( data ); + if ( rowProcessingState.needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + return voidFuture(); + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } ); + } + + private CompletionStage reactiveInitializeStage( + ReactiveEntityInitializerData data, + RowProcessingState rowProcessingState) { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + else { + initializer.resolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + } + } + return voidFuture(); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setState( State.RESOLVED ); + if ( data.getEntityKey() == null ) { + assert getIdentifierAssembler() != null; + final Object id = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); + } + final PersistenceContext persistenceContext = rowProcessingState.getSession() + .getPersistenceContextInternal(); + data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + + if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { + data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); + data.setInstance( data.getEntityInstanceForNotify() ); + } + else { + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + postResolveInstance( data ); + return voidFuture(); + } + + private void postResolveInstance(ReactiveEntityInitializerData data) { + if ( data.getInstance() != null ) { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + if ( data.getRowProcessingState().needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + } + if ( data.getShallowCached() ) { + initializeSubInstancesFromParent( data ); + } + } + } + + @Override + public CompletionStage reactiveInitializeInstance(EntityInitializerData data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + if ( !skipInitialization( data ) ) { + assert consistentInstance( data ); + return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); + } + data.setState( State.INITIALIZED ); + return voidFuture(); + } + + protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object entityIdentifier = data.getEntityKey().getIdentifier(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + + return reactiveExtractConcreteTypeStateValues( data ) + .thenAccept( resolvedEntityState -> { + + preLoad( data, resolvedEntityState ); + + if ( isPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ) ) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = + asPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ).$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor == null + || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // if we do this after the entity has been initialized the + // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; + data.getConcreteDescriptor().getBytecodeEnhancementMetadata() + .injectInterceptor( data.getEntityInstanceForNotify(), entityIdentifier, session ); + } + } + data.getConcreteDescriptor().setPropertyValues( data.getEntityInstanceForNotify(), resolvedEntityState ); + + persistenceContext.addEntity( data.getEntityKey(), data.getEntityInstanceForNotify() ); + + // Also register possible unique key entries + registerPossibleUniqueKeyEntries( data, resolvedEntityState, session ); + + 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, + resolvedEntityState, + rowId, + data.getEntityKey().getIdentifier(), + version, + lockModeToAcquire, + true, + data.getConcreteDescriptor(), + false + ); + data.getEntityHolder().setEntityEntry( entityEntry ); + + registerNaturalIdResolution( data, persistenceContext, resolvedEntityState ); + + takeSnapshot( data, session, persistenceContext, entityEntry, resolvedEntityState ); + + data.getConcreteDescriptor().afterInitialize( data.getEntityInstanceForNotify(), session ); + + assert data.getConcreteDescriptor().getIdentifier( data.getEntityInstanceForNotify(), session ) != null; + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + if ( !rowProcessingState.isQueryCacheHit() ) { + statistics.loadEntity( data.getConcreteDescriptor().getEntityName() ); + } + } + updateCaches( + data, + session, + session.getPersistenceContextInternal(), + resolvedEntityState, + version + ); + } ); + } + + protected CompletionStage reactiveExtractConcreteTypeStateValues(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] values = new Object[data.getConcreteDescriptor().getNumberOfAttributeMappings()]; + final DomainResultAssembler[] concreteAssemblers = getAssemblers()[data.getConcreteDescriptor().getSubclassId()]; + return loop( 0, values.length, i -> { + final DomainResultAssembler assembler = concreteAssemblers[i]; + if ( assembler instanceof ReactiveEntityAssembler ) { + return ( (ReactiveEntityAssembler) assembler ) + .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( assembled -> values[i] = assembled ); + } + values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( v -> values ); + } + + protected CompletionStage reactiveResolveEntityInstance1(ReactiveEntityInitializerData data) { + final Object proxy = data.getEntityHolder().getProxy(); + final boolean unwrapProxy = proxy != null && getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).isUnwrapProxy() + && getConcreteDescriptor( data ).getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + final Object entityFromExecutionContext; + if ( !unwrapProxy && isProxyInstance( proxy ) ) { + if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + // If the entity comes from the execution context, it is treated as not initialized + // so that we can refresh the data as requested + registerReloadedEntity( data ); + } + else { + data.setInstance( proxy ); + if ( Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( Hibernate.unproxy( data.getInstance() ) ); + } + else { + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + assert lazyInitializer != null; + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + ensureEntityIsInitialized( data ); + } ); + } + } + } + else { + final Object existingEntity = data.getEntityHolder().getEntity(); + if ( existingEntity != null ) { + data.setEntityInstanceForNotify( existingEntity ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( data.getEntityHolder().getEntityInitializer() == null ) { + assert data.getEntityHolder().isInitialized() == isExistingEntityInitialized( existingEntity ); + if ( data.getEntityHolder().isInitialized() ) { + data.setState( State.INITIALIZED ); + } + else if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else if ( data.getEntityHolder().getEntityInitializer() != this ) { + data.setState( State.INITIALIZED ); + } + } + else if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + // This is the entity to refresh, so don't set the state to initialized + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else { + assert data.getEntityHolder().getEntityInitializer() == this; + // look to see if another initializer from a parent load context or an earlier + // initializer is already loading the entity + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + data.setInstance( data.getEntityInstanceForNotify() ); + final Initializer idInitializer; + if ( data.getEntityHolder().getEntityInitializer() == this && data.getState() != State.INITIALIZED + && getIdentifierAssembler() != null + && ( idInitializer = getIdentifierAssembler().getInitializer() ) != null ) { + // If this is the owning initializer and the returned object is not initialized, + // this means that the entity instance was just instantiated. + // In this case, we want to call "assemble" and hence "initializeInstance" on the initializer + // for possibly non-aggregated identifier mappings, so inject the virtual id representation + idInitializer.initializeInstance( data.getRowProcessingState() ); + } + ensureEntityIsInitialized( data ); + } ); + } + } + ensureEntityIsInitialized( data ); + return voidFuture(); + } + + private void ensureEntityIsInitialized(ReactiveEntityInitializerData data) { + // todo: ensure we initialize the entity + assert !data.getShallowCached() || data.getState() == State.INITIALIZED : "Forgot to initialize the entity"; + } + + protected CompletionStage reactiveResolveEntityInstance2(ReactiveEntityInitializerData data) { + if ( data.getEntityHolder().getEntityInitializer() == this ) { + assert data.getEntityHolder().getEntity() == null; + return reactiveResolveEntityInstance( data ); + } + else { + // the entity is already being loaded elsewhere + return completedFuture( data.getEntityHolder().getEntity() ); + } + } + + protected CompletionStage reactiveResolveEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object resolved = resolveToOptionalInstance( data ); + if ( resolved != null ) { + registerLoadingEntity( data, resolved ); + return completedFuture( resolved ); + } + else { + if ( rowProcessingState.isQueryCacheHit() && getEntityDescriptor().useShallowQueryCacheLayout() ) { + // We must load the entity this way, because the query cache entry contains only the primary key + data.setState( State.INITIALIZED ); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + assert data.getEntityHolder().getEntityInitializer() == this; + // If this initializer owns the entity, we have to remove the entity holder, + // because the subsequent loading process will claim the entity + session.getPersistenceContextInternal().removeEntityHolder( data.getEntityKey() ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + data.getConcreteDescriptor().getEntityName(), + data.getEntityKey().getIdentifier(), + true, + false + ); + } + // We have to query the second level cache if reference cache entries are used + else if ( getEntityDescriptor().canUseReferenceCacheEntries() ) { + final Object cached = resolveInstanceFromCache( data ); + if ( cached != null ) { + // EARLY EXIT!!! + // because the second level cache has reference cache entries, the entity is initialized + data.setState( State.INITIALIZED ); + return completedFuture( cached ); + } + } + final Object instance = instantiateEntity( data ); + registerLoadingEntity( data, instance ); + return completedFuture( instance ); + } + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveToOptionalInstance(ReactiveEntityInitializerData data) { + if ( isResultInitializer() ) { + // this isEntityReturn bit is just for entity loaders, not hql/criteria + final JdbcValuesSourceProcessingOptions processingOptions = + data.getRowProcessingState().getJdbcValuesSourceProcessingState().getProcessingOptions(); + return matchesOptionalInstance( data, processingOptions ) ? processingOptions.getEffectiveOptionalObject() : null; + } + else { + return null; + } + } + + // FIXME: I could change the scope of this method in ORM + private boolean isProxyInstance(Object proxy) { + return proxy != null + && ( proxy instanceof MapProxy || getEntityDescriptor().getJavaType().getJavaTypeClass().isInstance( proxy ) ); + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveInstanceFromCache(ReactiveEntityInitializerData data) { + return CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + data.getRowProcessingState().getSession().asEventSource(), + null, + data.getLockMode(), + getEntityDescriptor(), + data.getEntityKey() + ); + } + + // FIXME: I could change the scope of this method in ORM + private boolean matchesOptionalInstance( + ReactiveEntityInitializerData data, + JdbcValuesSourceProcessingOptions processingOptions) { + final Object optionalEntityInstance = processingOptions.getEffectiveOptionalObject(); + final Object requestedEntityId = processingOptions.getEffectiveOptionalId(); + return requestedEntityId != null + && optionalEntityInstance != null + && requestedEntityId.equals( data.getEntityKey().getIdentifier() ); + } + + private boolean isExistingEntityInitialized(Object existingEntity) { + return Hibernate.isInitialized( existingEntity ); + } + + @Override + public CompletionStage reactiveResolveKey(EntityInitializerData data) { + return reactiveResolveKey( (ReactiveEntityInitializerData) data, false ); + } + + protected CompletionStage reactiveResolveKey(ReactiveEntityInitializerData data, boolean entityKeyOnly) { + // todo (6.0) : atm we do not handle sequential selects + // - see AbstractEntityPersister#hasSequentialSelect and + // AbstractEntityPersister#getSequentialSelect in 5.2 + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + data.setState( State.KEY_RESOLVED ); + + // reset row state + data.setConcreteDescriptor( null ); + data.setEntityKey( null ); + data.setInstance( null ); + data.setEntityInstanceForNotify( null ); + data.setEntityHolder( null ); + + final Object[] id = new Object[1]; + return initializeId( data, id, entityKeyOnly ) + .thenCompose( initialized -> { + if ( initialized ) { + resolveEntityKey( data, id[0] ); + if ( !entityKeyOnly ) { + // Resolve the entity instance early as we have no key many-to-one + return reactiveResolveInstance( data ) + .thenCompose( v -> { + if ( !data.getShallowCached() ) { + if ( data.getState() == State.INITIALIZED ) { + if ( data.getEntityHolder().getEntityInitializer() == null ) { + // The entity is already part of the persistence context, + // so let's figure out the loaded state and only run sub-initializers if necessary + return reactiveResolveInstanceSubInitializers( data ); + } + // If the entity is initialized and getEntityInitializer() == this, + // we already processed a row for this entity before, + // but we still have to call resolveKeySubInitializers to activate sub-initializers, + // because a row might contain data that sub-initializers want to consume + else { + // todo: try to diff the eagerness of the sub-initializers to avoid further processing + return reactiveResolveKeySubInitializers( data ); + } + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } + return voidFuture(); + } ); + } + } + return voidFuture(); + } ); + } + + + protected CompletionStage reactiveResolveInstanceSubInitializers(ReactiveEntityInitializerData data) { + final Initializer[] initializers = getSubInitializers()[data.getConcreteDescriptor().getSubclassId()]; + if ( initializers.length == 0 ) { + return voidFuture(); + } + final EntityEntry entityEntry = data.getEntityHolder().getEntityEntry(); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + assert entityEntry == rowProcessingState.getSession() + .getPersistenceContextInternal() + .getEntry( data.getEntityInstanceForNotify() ); + final Object[] loadedState = entityEntry.getLoadedState(); + final Object[] state; + if ( loadedState == null ) { + if ( entityEntry.getStatus() == Status.READ_ONLY ) { + state = data.getConcreteDescriptor().getValues( data.getEntityInstanceForNotify() ); + } + else { + // This branch is entered when a load happens while a cache entry is assembling. + // The EntityEntry has the LOADING state, but the loaded state is still empty. + assert entityEntry.getStatus() == Status.LOADING; + // Just skip any initialization in this case as the cache entry assembling will take care of it + return voidFuture(); + } + } + else { + state = loadedState; + } + return loop( 0, initializers.length, i -> { + final Initializer initializer = initializers[i]; + if ( initializer != null ) { + final Object subInstance = state[i]; + if ( subInstance == UNFETCHED_PROPERTY ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( rowProcessingState ); + } + else { + // Go through the normal initializer process + initializer.resolveKey( rowProcessingState ); + } + } + else { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( subInstance, rowProcessingState ); + } + else { + initializer.resolveInstance( subInstance, rowProcessingState ); + } + } + } + return voidFuture(); + } ); + } + + protected CompletionStage reactiveResolveKeySubInitializers(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( + getSubInitializers()[data.getConcreteDescriptor().getSubclassId()], + initializer -> { + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( rowProcessingState ); + } + initializer.resolveKey( rowProcessingState ); + } + return voidFuture(); + } + ); + } + + /** + * Return {@code true} if the identifier has been initialized + */ + private CompletionStage initializeId(ReactiveEntityInitializerData data, Object[] id, boolean entityKeyOnly) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + if ( getIdentifierAssembler() == null ) { + id[0] = rowProcessingState.getEntityId(); + assert id[0] != null : "Initializer requires a not null id for loading"; + return trueFuture(); + } + else { + //noinspection unchecked + final Initializer initializer = (Initializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + final InitializerData subData = initializer.getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenCompose( v -> { + if ( subData.getState() == State.MISSING ) { + setMissing( data ); + return falseFuture(); + } + else { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + rowProcessingState, + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + if ( isKeyManyToOne() ) { + if ( !data.getShallowCached() && !entityKeyOnly ) { + resolveKeySubInitializers( data ); + } + return falseFuture(); + } + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } ); + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } + } + + @Override + protected EntityInitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return voidFuture() + .thenCompose( v -> { + if ( getKeyAssembler() != null ) { + final Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + final ReactiveEntityInitializerDataAdaptor entityInitializerData = new ReactiveEntityInitializerDataAdaptor( + (EntityInitializerData) data ); + if ( entityInitializerData.getConcreteDescriptor() == null ) { + return loop( getSubInitializers(), initializers -> + loop( initializers, initializer -> { + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + return voidFuture(); + } ) + ); + } + else { + Initializer[] subInitializers = getSubInitializers()[entityInitializerData.getConcreteDescriptor() + .getSubclassId()]; + return loop( subInitializers, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + } ); + } + + private static class ReactiveEntityInitializerDataAdaptor extends EntityInitializerData { + + public ReactiveEntityInitializerDataAdaptor(EntityInitializerData delegate) { + super( delegate ); + } + + public EntityPersister getConcreteDescriptor() { + return concreteDescriptor; + } + } + + @Override + public Object getResolvedInstance(EntityInitializerData data) { + return super.getResolvedInstance( data ); + } +} 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 deleted file mode 100644 index 4cb7d5b00..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java +++ /dev/null @@ -1,133 +0,0 @@ -/* 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.graph.entity.internal; - -import org.hibernate.FetchNotFoundException; -import org.hibernate.Hibernate; -import org.hibernate.LockMode; -import org.hibernate.annotations.NotFoundAction; -import org.hibernate.internal.log.LoggingHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -/** - * Basically a copy of {@link org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer}. - * - * We could delegate but it would require override a huge amount of methods. - * - * @see org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer - */ -public class ReactiveEntityJoinedFetchInitializer extends ReactiveAbstractEntityInitializer { - - private static final String CONCRETE_NAME = ReactiveEntityJoinedFetchInitializer.class.getSimpleName(); - - private final DomainResultAssembler keyAssembler; - private final NotFoundAction notFoundAction; - - public ReactiveEntityJoinedFetchInitializer( - EntityResultGraphNode resultDescriptor, - EntityValuedFetchable referencedFetchable, - NavigablePath navigablePath, - LockMode lockMode, - NotFoundAction notFoundAction, - DomainResult keyResult, - DomainResult rowIdResult, - Fetch identifierFetch, - Fetch discriminatorFetch, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - assert getInitializedPart() == referencedFetchable; - this.notFoundAction = notFoundAction; - - this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); - } - - @Override - public void resolveKey(RowProcessingState rowProcessingState) { - if ( isParentShallowCached() ) { - state = State.MISSING; - } - else if ( state == State.UNINITIALIZED ) { - if ( shouldSkipInitializer( rowProcessingState ) ) { - state = State.MISSING; - return; - } - - super.resolveKey( rowProcessingState ); - - // super processes the foreign-key target column. here we - // need to also look at the foreign-key value column to check - // for a dangling foreign-key - - if ( keyAssembler != null ) { - final Object fkKeyValue = keyAssembler.assemble( rowProcessingState ); - if ( fkKeyValue != null ) { - if ( state == State.MISSING ) { - if ( notFoundAction != NotFoundAction.IGNORE ) { - throw new FetchNotFoundException( - getEntityDescriptor().getEntityName(), - fkKeyValue - ); - } - else { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "Ignoring dangling foreign-key due to `@NotFound(IGNORE); association will be null - %s", - getNavigablePath() - ); - } - } - } - } - } - } - - @Override - public void initializeInstanceFromParent(Object parentInstance, RowProcessingState rowProcessingState) { - final AttributeMapping attributeMapping = getInitializedPart().asAttributeMapping(); - final Object instance = attributeMapping != null - ? attributeMapping.getValue( parentInstance ) - : parentInstance; - setEntityInstance( instance ); - setEntityInstanceForNotify( Hibernate.unproxy( instance ) ); - state = State.INITIALIZED; - initializeSubInstancesFromParent( rowProcessingState ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public boolean isResultInitializer() { - return false; - } - - @Override - public String toString() { - return "ReactiveEntityJoinedFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java deleted file mode 100644 index fde931db7..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* 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.graph.entity.internal; - - -import org.hibernate.LockMode; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.basic.BasicFetch; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; - -/** - * @see org.hibernate.sql.results.graph.entity.internal.EntityResultInitializer - */ -public class ReactiveEntityResultInitializer extends ReactiveAbstractEntityInitializer { - private static final String CONCRETE_NAME = ReactiveEntityResultInitializer.class.getSimpleName(); - - public ReactiveEntityResultInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - BasicFetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public String toString() { - return CONCRETE_NAME + "(" + getNavigablePath() + ")"; - } - - @Override - public boolean isPartOfKey() { - // The entity result itself can never be part of the key - return false; - } - - @Override - public boolean isResultInitializer() { - return true; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java index 88300f7fb..61ad620db 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java @@ -10,114 +10,82 @@ import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.spi.EntityIdentifierNavigablePath; 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.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchByUniqueKeyInitializer */ -public class ReactiveEntitySelectFetchByUniqueKeyInitializer extends ReactiveEntitySelectFetchInitializer { +public class ReactiveEntitySelectFetchByUniqueKeyInitializer + extends ReactiveEntitySelectFetchInitializer { private final ToOneAttributeMapping fetchedAttribute; public ReactiveEntitySelectFetchByUniqueKeyInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.fetchedAttribute = fetchedAttribute; } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - + public CompletionStage reactiveInitialize(EntitySelectFetchInitializerData actual) { + final ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) actual; final String entityName = concreteDescriptor.getEntityName(); final String uniqueKeyPropertyName = fetchedAttribute.getReferencedPropertyName(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); + + final SharedSessionContractImplementor session = data.getRowProcessingState().getSession(); + final EntityUniqueKey euk = new EntityUniqueKey( entityName, uniqueKeyPropertyName, - entityIdentifier, + data.getEntityIdentifier(), concreteDescriptor.getPropertyType( uniqueKeyPropertyName ), session.getFactory() ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( entityInstance == null ) { + data.setInstance( persistenceContext.getEntity( euk ) ); + if ( data.getInstance() == null ) { return ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, entityIdentifier, session ) - .thenAccept( this::setEntityInstance ) + .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 ( entityInstance != null ) { - persistenceContext.addEntity( euk, entityInstance ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } - if ( entityInstance != null ) { - setEntityInstance( persistenceContext.proxyFor( entityInstance ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } return voidFuture(); } - private void setEntityInstance(Object instance) { - entityInstance = instance; + @Override + public Object getResolvedInstance(EntitySelectFetchInitializerData data) { + return super.getResolvedInstance( data ); } } 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 1a9d14f4c..b27c0cc93 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 @@ -7,7 +7,9 @@ import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.EntityFilterException; import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.annotations.NotFoundAction; @@ -15,268 +17,203 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.EntityIdentifierNavigablePath; 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.EntityLoadingLogging; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -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; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; -import static org.hibernate.sql.results.graph.entity.EntityLoadingLogging.ENTITY_LOADING_LOGGER; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer */ -public class ReactiveEntitySelectFetchInitializer extends EntitySelectFetchInitializer implements ReactiveInitializer { +public class ReactiveEntitySelectFetchInitializer + extends EntitySelectFetchInitializer implements ReactiveInitializer { - private static final String CONCRETE_NAME = ReactiveEntitySelectFetchInitializer.class.getSimpleName(); + public static class ReactiveEntitySelectFetchInitializerData + extends EntitySelectFetchInitializer.EntitySelectFetchInitializerData { + + public ReactiveEntitySelectFetchInitializerData(EntitySelectFetchInitializer initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public Object getEntityIdentifier() { + return entityIdentifier; + } + + public void setEntityIdentifier(Object entityIdentifier) { + super.entityIdentifier = entityIdentifier; + } + } private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final boolean isEnhancedForLazyLoading; public ReactiveEntitySelectFetchInitializer( - FetchParentAccess parentAccess, - ToOneAttributeMapping toOneMapping, + InitializerParent parent, + ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, toOneMapping, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntitySelectFetchInitializerData( this, rowProcessingState ); + } + @Override public void resolveInstance(RowProcessingState rowProcessingState) { super.resolveInstance( rowProcessingState ); } @Override - public void initializeInstance(RowProcessingState rowProcessingState) { + public void initializeInstance(EntitySelectFetchInitializerData data) { throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); - } - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); - - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - 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 - ) - ); - } - entityInstance = holder.getEntity(); - if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - else if ( holder.getEntityInitializer() != this ) { - // 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", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - state = State.INITIALIZED; - return voidFuture(); - } - else if ( entityInstance == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); + protected void initialize(EntitySelectFetchInitializerData data) { + throw LOG.nonReactiveMethodCall( "reactiveInitialize" ); } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - initializeState(); - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - initializeState(); - return voidFuture(); - } - - if ( ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, data.getRowProcessingState() ); } + return voidFuture(); + } + protected CompletionStage reactiveInitialize(EntitySelectFetchInitializerData ormData) { + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) ormData; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final String entityName = concreteDescriptor.getEntityName(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); 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 - ) - ); - } - entityInstance = holder.getEntity(); + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - initializeState(); + if ( data.getInstance() != null && Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); return voidFuture(); } } else if ( holder.getEntityInitializer() != this ) { // 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", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - initializeState(); + data.setState( State.INITIALIZED ); return voidFuture(); } - else if ( entityInstance == null ) { - initializeState(); + else if ( data.getInstance() == null ) { + // todo: maybe mark this as resolved instead? + assert holder.getProxy() == null : "How to handle this case?"; + data.setState( State.INITIALIZED ); return voidFuture(); } } + data.setState( State.INITIALIZED ); + final String entityName = concreteDescriptor.getEntityName(); - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Invoking session#internalLoad for entity (%s) : %s", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier - ); - } - - return ReactiveQueryExecutorLookup.extract( session ) - .reactiveInternalLoad( entityName, entityIdentifier, true, toOneMapping().isInternalLoadNullable() ) - .thenCompose( instance -> { - entityInstance = instance; - - if ( entityInstance == null ) { - if ( toOneMapping().getNotFoundAction() == NotFoundAction.EXCEPTION ) { - return failedFuture( new FetchNotFoundException( entityName, entityIdentifier ) ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + entityName, + data.getEntityIdentifier(), + true, + toOneMapping.isInternalLoadNullable() + ) + .thenAccept( instance -> { + data.setInstance( instance ); + + if ( instance == null ) { + if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) { + if ( affectedByFilter ) { + throw new EntityFilterException( + entityName, + data.getEntityIdentifier(), + toOneMapping.getNavigableRole().getFullPath() + ); + } + if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) { + throw new FetchNotFoundException( entityName, data.getEntityIdentifier() ); + } } - } - - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] : %s has being loaded by session.internalLoad.", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier + rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible( + new EntityKey( data.getEntityIdentifier(), concreteDescriptor ), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this ); } - final boolean unwrapProxy = toOneMapping().isUnwrapProxy() && isEnhancedForLazyLoading; - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entityInstance ); + final boolean unwrapProxy = toOneMapping.isUnwrapProxy() && isEnhancedForLazyLoading; + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( unwrapProxy ); } - initializeState(); - return voidFuture(); } ); } - protected void initializeState() { - state = State.INITIALIZED; + @Override + public CompletionStage reactiveResolveInstance(Data original) { + if ( original.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) original; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( keyAssembler.assemble( rowProcessingState ) ); + + if ( data.getEntityIdentifier() == null ) { + data.setState( State.MISSING ); + data.setInstance( null ); + return voidFuture(); + } + + data.setState( State.INITIALIZED ); + return reactiveInitialize( data ); } - protected ToOneAttributeMapping toOneMapping() { - return (ToOneAttributeMapping) getInitializedPart(); + @Override + public CompletionStage reactiveInitializeInstance(Data data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + data.setState( State.INITIALIZED ); + Hibernate.initialize( data.getInstance() ); + return voidFuture(); + } + + @Override + public Object getResolvedInstance(Data data) { + return super.getResolvedInstance( data ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java index 34d25c6bc..c4cb87838 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java @@ -11,11 +11,13 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntitySelectFetchInitializer; @@ -26,74 +28,94 @@ */ public class ReactiveEntitySelectFetchInitializerBuilder { - public static EntityInitializer createInitializer( - FetchParentAccess parentAccess, + public static EntityInitializer createInitializer( + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, EntityPersister entityPersister, - DomainResult keyResult, + DomainResult originalKeyResult, NavigablePath navigablePath, boolean selectByUniqueKey, + boolean affectedByFilter, AssemblerCreationState creationState) { + final DomainResult keyResult = originalKeyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) originalKeyResult ) + : originalKeyResult; if ( selectByUniqueKey ) { return new ReactiveEntitySelectFetchByUniqueKeyInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } - final BatchMode batchMode = determineBatchMode( entityPersister, parentAccess, creationState ); + final BatchMode batchMode = determineBatchMode( entityPersister, parent, creationState ); switch ( batchMode ) { case NONE: - return new ReactiveEntitySelectFetchInitializer( - parentAccess, + return new ReactiveEntitySelectFetchInitializer<>( + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); case BATCH_LOAD: - if ( parentAccess.isEmbeddableInitializer() ) { + if ( parent.isEmbeddableInitializer() ) { return new BatchEntityInsideEmbeddableSelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } else { return new BatchEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } case BATCH_INITIALIZE: return new BatchInitializeEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } throw new IllegalStateException( "Should be unreachable" ); } // FIXME: Use the one in ORM - private static BatchMode determineBatchMode( + public static BatchMode determineBatchMode( EntityPersister entityPersister, - FetchParentAccess parentAccess, + InitializerParent parent, AssemblerCreationState creationState) { - if ( !entityPersister.isBatchLoadable() || creationState.isScrollResult() ) { + if ( !entityPersister.isBatchLoadable() ) { return BatchMode.NONE; } - while ( parentAccess.isEmbeddableInitializer() ) { - final EmbeddableInitializer embeddableInitializer = parentAccess.asEmbeddableInitializer(); + if ( creationState.isDynamicInstantiation() ) { + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; + } + while ( parent.isEmbeddableInitializer() ) { + final EmbeddableInitializer embeddableInitializer = parent.asEmbeddableInitializer(); final EmbeddableValuedModelPart initializedPart = embeddableInitializer.getInitializedPart(); // For entity identifier mappings we can't batch load, // because the entity identifier needs the instance in the resolveKey phase, @@ -101,29 +123,40 @@ private static BatchMode determineBatchMode( if ( initializedPart.isEntityIdentifierMapping() // todo: check if the virtual check is necessary || initializedPart.isVirtual() - // If the parent embeddable has a custom instantiator, we can't inject entities later through setValues() - || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() instanceof StandardEmbeddableInstantiator ) ) { + || initializedPart.getMappedType().isPolymorphic() + // If the parent embeddable has a custom instantiator, + // we can't inject entities later through setValues() + || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() + instanceof StandardEmbeddableInstantiator ) ) { return entityPersister.hasSubclasses() ? BatchMode.NONE : BatchMode.BATCH_INITIALIZE; } - parentAccess = parentAccess.getFetchParentAccess(); - if ( parentAccess == null ) { + parent = parent.getParent(); + if ( parent == null ) { break; } } - if ( parentAccess != null ) { - assert parentAccess.getInitializedPart() instanceof EntityValuedModelPart; - final EntityPersister parentPersister = parentAccess.asEntityInitializer().getEntityDescriptor(); + if ( parent != null ) { + assert parent.getInitializedPart() instanceof EntityValuedModelPart; + final EntityPersister parentPersister = parent.asEntityInitializer().getEntityDescriptor(); final EntityDataAccess cacheAccess = parentPersister.getCacheAccessStrategy(); if ( cacheAccess != null ) { // Do batch initialization instead of batch loading if the parent entity is cacheable // to avoid putting entity state into the cache at a point when the association is not yet set - return BatchMode.BATCH_INITIALIZE; + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; } } return BatchMode.BATCH_LOAD; } - enum BatchMode { + private static boolean canBatchInitializeBeUsed(EntityPersister entityPersister) { + // we need to create a Proxy in order to use batch initialize + return entityPersister.getRepresentationStrategy().getProxyFactory() != null; + } + + private enum BatchMode { NONE, BATCH_LOAD, BATCH_INITIALIZE diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 09a2afaa2..da94cc329 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -6,13 +6,11 @@ package org.hibernate.reactive.sql.results.internal; import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; @@ -32,6 +30,7 @@ import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; @@ -50,7 +49,6 @@ public class ReactiveDeferredResultSetAccess extends DeferredResultSetAccess imp private CompletionStage resultSetStage; - private Integer columnCount; private ResultSet resultSet; @@ -58,8 +56,9 @@ public ReactiveDeferredResultSetAccess( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, - Function statementCreator) { - super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + JdbcSelectExecutor.StatementCreator statementCreator, + int resultCountEstimate) { + super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); this.executionContext = executionContext; this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java index 094fb28ad..2412f3941 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java @@ -53,6 +53,11 @@ public void release() { .release( resultSet, resultSetSource ); } + @Override + public int getResultCountEstimate() { + return super.getResultCountEstimate(); + } + @Override public BasicType resolveType(int position, JavaType explicitJavaType, SessionFactoryImplementor sessionFactory) { return super.resolveType( position, explicitJavaType, sessionFactory ); 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 index 5fc1a50b7..7adeab775 100644 --- 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 @@ -6,13 +6,16 @@ package org.hibernate.reactive.sql.results.internal; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; public class ReactiveEntityDelayedFetchImpl extends EntityDelayedFetchImpl { @@ -21,20 +24,26 @@ public ReactiveEntityDelayedFetchImpl( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { - super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey ); + boolean selectByUniqueKey, + DomainResultCreationState creationState) { + super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey, creationState ); } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityDelayedFetchInitializer( parentAccess, - getNavigablePath(), - getEntityValuedModelPart(), - isSelectByUniqueKey(), - getKeyResult().createResultAssembler( - parentAccess, - creationState - ) + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + getNavigablePath(), + getEntityValuedModelPart(), + isSelectByUniqueKey(), + getKeyResult(), + getDiscriminatorFetch(), + creationState ); } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java index 1d2adc4ee..819ba05c6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java @@ -5,18 +5,31 @@ */ package org.hibernate.reactive.sql.results.internal; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; -import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityResultInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityInitializerImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + public class ReactiveEntityResultImpl extends EntityResultImpl { + private static final Log LOG = make( Log.class, lookup() ); + public ReactiveEntityResultImpl( NavigablePath navigablePath, EntityValuedModelPart entityValuedModelPart, @@ -26,23 +39,51 @@ public ReactiveEntityResultImpl( } @Override - public DomainResultAssembler createResultAssembler( - FetchParentAccess parentAccess, + public DomainResultAssembler createResultAssembler( + InitializerParent parent, AssemblerCreationState creationState) { - final Initializer initializer = creationState.resolveInitializer( - getNavigablePath(), - getReferencedModePart(), - () -> new ReactiveEntityResultInitializer( - this, - getNavigablePath(), - getLockMode( creationState ), - getIdentifierFetch(), - getDiscriminatorFetch(), - getRowIdResult(), - creationState - ) + return new ReactiveEntityAssembler( + this.getResultJavaType(), + creationState.resolveInitializer( this, parent, this ).asEntityInitializer() ); + } - return new ReactiveEntityAssembler( this.getResultJavaType(), initializer.asEntityInitializer() ); + @Override + public Initializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + getIdentifierFetch(), + getDiscriminatorFetch(), + null, + getRowIdResult(), + NotFoundAction.EXCEPTION, + false, + null, + true, + creationState + ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index e752a49ca..312f510f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -8,19 +8,11 @@ import java.util.ArrayList; import java.util.Map; -import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -import static org.hibernate.reactive.util.impl.CompletionStages.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * @see org.hibernate.sql.results.internal.InitializersList @@ -28,74 +20,16 @@ public final class ReactiveInitializersList { private final Initializer[] initializers; - private final Initializer[] sortedNonCollectionsFirst; private final Initializer[] sortedForResolveInstance; private final boolean hasCollectionInitializers; - private final Map initializerMap; private ReactiveInitializersList( Initializer[] initializers, - Initializer[] sortedNonCollectionsFirst, Initializer[] sortedForResolveInstance, - boolean hasCollectionInitializers, - Map initializerMap) { + boolean hasCollectionInitializers) { this.initializers = initializers; - this.sortedNonCollectionsFirst = sortedNonCollectionsFirst; this.sortedForResolveInstance = sortedForResolveInstance; this.hasCollectionInitializers = hasCollectionInitializers; - this.initializerMap = initializerMap; - } - - public Initializer resolveInitializer(final NavigablePath path) { - return initializerMap.get( path ); - } - - public void finishUpRow(final RowProcessingState rowProcessingState) { - for ( Initializer init : initializers ) { - init.finishUpRow( rowProcessingState ); - } - } - - public void startLoading(final RowProcessingState rowProcessingState) { - for ( int i = initializers.length - 1; i >= 0; i-- ) { - initializers[i].startLoading( rowProcessingState ); - } - } - - public CompletionStage initializeInstance(final ReactiveRowProcessingState rowProcessingState) { - return loop( initializers, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( rowProcessingState ); - } - else { - initializer.initializeInstance( rowProcessingState ); - return voidFuture(); - } - } ); - } - - public void endLoading(final ExecutionContext executionContext) { - for ( Initializer initializer : initializers ) { - initializer.endLoading( executionContext ); - } - } - - public void resolveKeys(final RowProcessingState rowProcessingState) { - for ( Initializer init : sortedNonCollectionsFirst ) { - init.resolveKey( rowProcessingState ); - } - } - - public CompletionStage resolveInstances(final ReactiveRowProcessingState rowProcessingState) { - return loop( sortedNonCollectionsFirst, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( rowProcessingState ); - } - else { - initializer.resolveInstance( rowProcessingState ); - return voidFuture(); - } - } ); } public boolean hasCollectionInitializers() { @@ -103,17 +37,23 @@ public boolean hasCollectionInitializers() { } static class Builder { - private final ArrayList initializers = new ArrayList<>(); + private final ArrayList> initializers; int nonCollectionInitializersNum = 0; int resolveFirstNum = 0; - public Builder() {} + public Builder() { + initializers = new ArrayList<>(); + } + + public Builder(int size) { + initializers = new ArrayList<>( size ); + } - public void addInitializer(final Initializer initializer) { + public void addInitializer(final Initializer initializer) { initializers.add( initializer ); //in this method we perform these checks merely to learn the sizing hints, //so to not need dynamically scaling collections. - //This implies performing both checks twice but since they're cheap it's preferrable + //This implies performing both checks twice but since they're cheap it's preferable //to multiple allocations; not least this allows using arrays, which makes iteration //cheaper during the row processing - which is very hot. if ( !initializer.isCollectionInitializer() ) { @@ -124,26 +64,17 @@ public void addInitializer(final Initializer initializer) { } } - private static boolean initializeFirst(final Initializer initializer) { + private static boolean initializeFirst(final Initializer initializer) { return !( initializer instanceof EntityDelayedFetchInitializer ) && !( initializer instanceof EntitySelectFetchInitializer ); } - ReactiveInitializersList build(final Map initializerMap) { + ReactiveInitializersList build(final Map> initializerMap) { final int size = initializers.size(); - final Initializer[] sortedNonCollectionsFirst = new Initializer[size]; - final Initializer[] sortedForResolveInstance = new Initializer[size]; - int nonCollectionIdx = 0; - int collectionIdx = nonCollectionInitializersNum; + final Initializer[] sortedForResolveInstance = new Initializer[size]; int resolveFirstIdx = 0; int resolveLaterIdx = resolveFirstNum; - final Initializer[] originalSortInitializers = toArray( initializers ); - for ( Initializer initializer : originalSortInitializers ) { - if ( initializer.isCollectionInitializer() ) { - sortedNonCollectionsFirst[collectionIdx++] = initializer; - } - else { - sortedNonCollectionsFirst[nonCollectionIdx++] = initializer; - } + final Initializer[] originalSortInitializers = toArray( initializers ); + for ( Initializer initializer : originalSortInitializers ) { if ( initializeFirst( initializer ) ) { sortedForResolveInstance[resolveFirstIdx++] = initializer; } @@ -152,18 +83,11 @@ ReactiveInitializersList build(final Map initializer } } final boolean hasCollectionInitializers = ( nonCollectionInitializersNum != initializers.size() ); - return new ReactiveInitializersList( - originalSortInitializers, - sortedNonCollectionsFirst, - sortedForResolveInstance, - hasCollectionInitializers, - initializerMap - ); + return new ReactiveInitializersList( originalSortInitializers, sortedForResolveInstance, hasCollectionInitializers ); } - private Initializer[] toArray(final ArrayList initializers) { - return initializers.toArray( new Initializer[0] ); + private Initializer[] toArray(final ArrayList> initializers) { + return initializers.toArray( new Initializer[initializers.size()] ); } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java index f99a13d53..75a265ed5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java @@ -37,6 +37,15 @@ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { SessionFactoryImplementor getFactory(); void release(); + /** + * The estimate for the amount of results that can be expected for pre-sizing collections. + * May return zero or negative values if the count can not be reasonably estimated. + * @since 6.6 + */ + default int getResultCountEstimate() { + return -1; + } + default int getColumnCount() { try { return getResultSet().getMetaData().getColumnCount(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java index d3038621c..72d5df0c2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java @@ -5,27 +5,10 @@ */ package org.hibernate.reactive.sql.results.internal; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import org.hibernate.LockMode; -import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.ResultsLogger; -import org.hibernate.sql.results.graph.AssemblerCreationState; -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.InitializerProducer; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -34,126 +17,12 @@ public class ReactiveResultsHelper { public static ReactiveRowReader createRowReader( - ExecutionContext executionContext, - LockOptions lockOptions, + SessionFactoryImplementor sessionFactory, RowTransformer rowTransformer, Class transformedResultJavaType, - JdbcValuesMapping jdbcValuesMapping) { - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - - final Map initializerMap = new LinkedHashMap<>(); - final ReactiveInitializersList.Builder initializersBuilder = new ReactiveInitializersList.Builder(); - - final List> assemblers = jdbcValuesMapping.resolveAssemblers( - creationState( executionContext, lockOptions, sessionFactory, initializerMap, initializersBuilder ) - ); - - logInitializers( initializerMap ); - - final ReactiveInitializersList initializersList = initializersBuilder.build( initializerMap ); - return new ReactiveStandardRowReader<>( assemblers, initializersList, rowTransformer, transformedResultJavaType ); - } - - private static AssemblerCreationState creationState( - ExecutionContext executionContext, - LockOptions lockOptions, - SessionFactoryImplementor sessionFactory, - Map initializerMap, - ReactiveInitializersList.Builder initializersBuilder) { - return new AssemblerCreationState() { - - @Override - public boolean isScrollResult() { - return executionContext.isScrollResult(); - } - - @Override - public LockMode determineEffectiveLockMode(String identificationVariable) { - return lockOptions.getEffectiveLockMode( identificationVariable ); - } - - @Override - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - Supplier producer) { - return resolveInitializer( - navigablePath, - fetchedModelPart, - null, - null, - (resultGraphNode, parentAccess, creationState) -> producer.get() - ); - } - - @Override - public

Initializer resolveInitializer( - P resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer

producer) { - return resolveInitializer( - resultGraphNode.getNavigablePath(), - resultGraphNode.getReferencedModePart(), - resultGraphNode, - parentAccess, - producer - ); - } - - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - T resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer producer) { - - final Initializer existing = initializerMap.get( navigablePath ); - if ( existing != null ) { - if ( fetchedModelPart.getNavigableRole().equals( - existing.getInitializedPart().getNavigableRole() ) ) { - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( - "Returning previously-registered initializer : %s", - existing - ); - return existing; - } - } - - final Initializer initializer = producer.createInitializer( resultGraphNode, parentAccess, this ); - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( "Registering initializer : %s", initializer ); - - initializerMap.put( navigablePath, initializer ); - initializersBuilder.addInitializer( initializer ); - - return initializer; - } - - @Override - public SqlAstCreationContext getSqlAstCreationContext() { - return sessionFactory; - } - - @Override - public ExecutionContext getExecutionContext() { - return executionContext; - } - }; - } - - private static void logInitializers(Map initializerMap) { - if ( ! ResultsLogger.RESULTS_MESSAGE_LOGGER.isDebugEnabled() ) { - return; - } - - ResultsLogger.RESULTS_MESSAGE_LOGGER.debug( "Initializer list" ); - initializerMap.forEach( (navigablePath, initializer) -> { - ResultsLogger.RESULTS_MESSAGE_LOGGER.debugf( - " %s -> %s@%s (%s)", - navigablePath, - initializer, - initializer.hashCode(), - initializer.getInitializedPart() - ); - } ); + ReactiveValuesResultSet resultSet) { + final JdbcValuesMappingResolution jdbcValuesMappingResolution = resultSet + .getValuesMapping().resolveAssemblers( sessionFactory ); + return new ReactiveStandardRowReader<>( jdbcValuesMappingResolution, rowTransformer, transformedResultJavaType ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java new file mode 100644 index 000000000..3fedd1684 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java @@ -0,0 +1,40 @@ +/* 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 java.util.concurrent.CompletionStage; + +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; +import org.hibernate.sql.results.spi.RowTransformer; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.internal.RowTransformerArrayImpl + */ +public class ReactiveRowTransformerArrayImpl implements RowTransformer> { + + /** + * Singleton access + * + * @see #instance() + */ + private static final RowTransformerArrayImpl INSTANCE = new RowTransformerArrayImpl(); + + public static RowTransformerArrayImpl instance() { + return INSTANCE; + } + + // I'm not sure why I need this + public static RowTransformer> asRowTransformer() { + return (RowTransformer) INSTANCE; + } + + @Override + public CompletionStage transformRow(Object[] objects) { + return completedFuture( objects ); + } +} 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 7fda67775..87aa57103 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 @@ -5,21 +5,28 @@ */ package org.hibernate.reactive.sql.results.internal; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.spi.RowTransformer; import org.hibernate.type.descriptor.java.JavaType; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.results.LoadingLogger.LOGGER; @@ -30,23 +37,86 @@ */ public class ReactiveStandardRowReader implements ReactiveRowReader { - private final List> resultAssemblers; - private final ReactiveInitializersList initializers; + private static final Log LOG = make( Log.class, lookup() ); + + private final DomainResultAssembler[] resultAssemblers; + private final Initializer[] resultInitializers; + private final InitializerData[] resultInitializersData; + private final Initializer[] initializers; + private final InitializerData[] initializersData; + private final Initializer[] sortedForResolveInstance; + private final InitializerData[] sortedForResolveInstanceData; + private final boolean hasCollectionInitializers; private final RowTransformer rowTransformer; private final Class domainResultJavaType; + private final ComponentType componentType; + private final Class resultElementClass; + private final int assemblerCount; public ReactiveStandardRowReader( - List> resultAssemblers, - ReactiveInitializersList initializers, + JdbcValuesMappingResolution jdbcValuesMappingResolution, + RowTransformer rowTransformer, + Class domainResultJavaType) { + this( + jdbcValuesMappingResolution.getDomainResultAssemblers(), + jdbcValuesMappingResolution.getResultInitializers(), + jdbcValuesMappingResolution.getInitializers(), + jdbcValuesMappingResolution.getSortedForResolveInstance(), + jdbcValuesMappingResolution.hasCollectionInitializers(), + rowTransformer, + domainResultJavaType + ); + } + + public ReactiveStandardRowReader( + DomainResultAssembler[] resultAssemblers, + Initializer[] resultInitializers, + Initializer[] initializers, + Initializer[] sortedForResolveInitializers, + boolean hasCollectionInitializers, RowTransformer rowTransformer, Class domainResultJavaType) { this.resultAssemblers = resultAssemblers; - this.initializers = initializers; + this.resultInitializers = (Initializer[]) resultInitializers; + this.resultInitializersData = new InitializerData[resultInitializers.length]; + this.initializers = (Initializer[]) initializers; + this.initializersData = new InitializerData[initializers.length]; + this.sortedForResolveInstance = (Initializer[]) sortedForResolveInitializers; + this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length]; + this.hasCollectionInitializers = hasCollectionInitializers; this.rowTransformer = rowTransformer; - this.assemblerCount = resultAssemblers.size(); this.domainResultJavaType = domainResultJavaType; + this.assemblerCount = resultAssemblers.length; + if ( domainResultJavaType == null + || domainResultJavaType == Object[].class + || domainResultJavaType == Object.class + || !domainResultJavaType.isArray() + || resultAssemblers.length == 1 + && domainResultJavaType == resultAssemblers[0].getAssembledJavaType().getJavaTypeClass() ) { + this.resultElementClass = Object.class; + this.componentType = ComponentType.OBJECT; + } + else { + this.resultElementClass = domainResultJavaType.getComponentType(); + this.componentType = ComponentType.determineComponentType( domainResultJavaType ); + } + } + + @Override + public int getInitializerCount() { + return initializers.length; + } + + @Override + public boolean hasCollectionInitializers() { + return hasCollectionInitializers; + } + + @Override + public R readRow(RowProcessingState processingState) { + throw LOG.nonReactiveMethodCall( "reactiveReadRow" ); } @Override @@ -55,76 +125,356 @@ public CompletionStage reactiveReadRow(ReactiveRowProcessingState rowProcessi return coordinateInitializers( rowProcessingState ) .thenCompose( v -> { - final Object[] resultRow = new Object[assemblerCount]; - return loop( 0, assemblerCount, i -> { - final DomainResultAssembler assembler = resultAssemblers.get( i ); - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); - if ( assembler instanceof ReactiveDomainResultsAssembler ) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( rowProcessingState, options ) - .thenAccept( obj -> resultRow[i] = obj ); - } - resultRow[i] = assembler.assemble( rowProcessingState, options ); - return voidFuture(); - } ) - .thenApply( ignore -> { - afterRow( rowProcessingState ); - return rowTransformer.transformRow( resultRow ); - } ); + // Copied from Hibernate ORM: + // "The following is ugly, but unfortunately necessary to not hurt performance. + // This implementation was micro-benchmarked and discussed with Francesco Nigro, + // who hinted that using this style instead of the reflective Array.getLength(), Array.set() + // is easier for the JVM to optimize" + switch ( componentType ) { + case BOOLEAN: + return booleanComponent( resultAssemblers, rowProcessingState, options ); + case BYTE: + return byteComponent( resultAssemblers, rowProcessingState, options ); + case CHAR: + return charComponent( resultAssemblers, rowProcessingState, options ); + case SHORT: + return shortComponent( resultAssemblers, rowProcessingState, options ); + case INT: + return intComponent( resultAssemblers, rowProcessingState, options ); + case LONG: + return longComponent( resultAssemblers, rowProcessingState, options ); + case FLOAT: + return floatComponent( resultAssemblers, rowProcessingState, options ); + case DOUBLE: + return doubleComponent( resultAssemblers, rowProcessingState, options ); + default: + return objectComponent( resultAssemblers, rowProcessingState, options ); + } } ); } - @Override - public Class getDomainResultResultJavaType() { - return domainResultJavaType; + private CompletionStage booleanComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final boolean[] resultRow = new boolean[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (boolean) obj ); + } + resultRow[i] = (boolean) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage byteComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final byte[] resultRow = new byte[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (byte) obj ); + } + resultRow[i] = (byte) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage charComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final char[] resultRow = new char[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (char) obj ); + } + resultRow[i] = (char) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage shortComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final short[] resultRow = new short[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (short) obj ); + } + resultRow[i] = (short) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage intComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final int[] resultRow = new int[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (int) obj ); + } + resultRow[i] = (int) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage longComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final long[] resultRow = new long[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (long) obj ); + } + resultRow[i] = (long) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage floatComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final float[] resultRow = new float[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (float) obj ); + } + resultRow[i] = (float) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage doubleComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final double[] resultRow = new double[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (double) obj ); + } + resultRow[i] = (double) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage objectComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = obj ); + } + resultRow[i] = assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return rowTransformer.transformRow( resultRow ); + } ); } @Override - public Class getResultJavaType() { - if ( resultAssemblers.size() == 1 ) { - return resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass(); - } + public EntityKey resolveSingleResultEntityKey(RowProcessingState rowProcessingState) { + return null; + } - return Object[].class; + @Override + public Class getDomainResultResultJavaType() { + return domainResultJavaType; } @Override public List> getResultJavaTypes() { - List> javaTypes = new ArrayList<>( resultAssemblers.size() ); - for ( DomainResultAssembler resultAssembler : resultAssemblers ) { + List> javaTypes = new ArrayList<>( resultAssemblers.length ); + for ( DomainResultAssembler resultAssembler : resultAssemblers ) { javaTypes.add( resultAssembler.getAssembledJavaType() ); } return javaTypes; } - @Override - public List getInitializers() { - throw new UnsupportedOperationException(); + private void afterRow(RowProcessingState rowProcessingState) { + LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); + finishUpRow(); } - @Override - public ReactiveInitializersList getReactiveInitializersList() { - return initializers; + private void finishUpRow() { + for ( InitializerData data : initializersData ) { + data.setState( Initializer.State.UNINITIALIZED ); + } } - @Override - public R readRow(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw LOG.nonReactiveMethodCall( "reactiveRowReader" ); + private CompletionStage coordinateInitializers(RowProcessingState rowProcessingState) { + return loop( 0, resultInitializers.length, i -> resolveKey( resultInitializers[i], resultInitializersData[i] ) ) + .thenCompose( v -> loop( 0, sortedForResolveInstance.length, i -> resolveInstance( sortedForResolveInstance[i], sortedForResolveInstanceData[i] ) ) ) + .thenCompose( v -> loop( 0, initializers.length, i -> initializeInstance( initializers[i], initializersData[i] ) ) ); } - private void afterRow(RowProcessingState rowProcessingState) { - LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); - initializers.finishUpRow( rowProcessingState ); + private CompletionStage resolveKey(Initializer initializer, InitializerData initializerData) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( initializerData ); + } + initializer.resolveKey( initializerData ); + return voidFuture(); } - private CompletionStage coordinateInitializers(ReactiveRowProcessingState rowProcessingState) { - initializers.resolveKeys( rowProcessingState ); - return initializers.resolveInstances( rowProcessingState ) - .thenCompose( v -> initializers.initializeInstance( rowProcessingState ) ); + private CompletionStage resolveInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.KEY_RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( initializerData ); + } + initializer.resolveInstance( initializerData ); + } + return voidFuture(); + } + + private CompletionStage initializeInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( initializerData ); + } + initializer.initializeInstance( initializerData ); + } + return voidFuture(); } @Override - public void finishUp(JdbcValuesSourceProcessingState processingState) { - initializers.endLoading( processingState.getExecutionContext() ); + public void startLoading(RowProcessingState processingState) { + for ( int i = 0; i < resultInitializers.length; i++ ) { + final Initializer initializer = resultInitializers[i]; + initializer.startLoading( processingState ); + resultInitializersData[i] = initializer.getData( processingState ); + } + for ( int i = 0; i < sortedForResolveInstance.length; i++ ) { + sortedForResolveInstanceData[i] = sortedForResolveInstance[i].getData( processingState ); + } + for ( int i = 0; i < initializers.length; i++ ) { + initializersData[i] = initializers[i].getData( processingState ); + } + } + + @Override + public void finishUp(RowProcessingState rowProcessingState) { + for ( int i = 0; i < initializers.length; i++ ) { + initializers[i].endLoading( initializersData[i] ); + } + } + + enum ComponentType { + BOOLEAN( boolean.class ), + BYTE( byte.class ), + SHORT( short.class ), + CHAR( char.class ), + INT( int.class ), + LONG( long.class ), + FLOAT( float.class ), + DOUBLE( double.class ), + OBJECT( Object.class ); + + private final Class componentType; + + ComponentType(Class componentType) { + this.componentType = componentType; + } + + public static ComponentType determineComponentType(Class resultType) { + if ( resultType == boolean[].class ) { + return BOOLEAN; + } + if ( resultType == byte[].class ) { + return BYTE; + } + if ( resultType == short[].class ) { + return SHORT; + } + if ( resultType == char[].class ) { + return CHAR; + } + if ( resultType == int[].class ) { + return INT; + } + if ( resultType == long[].class ) { + return LONG; + } + if ( resultType == float[].class ) { + return FLOAT; + } + if ( resultType == double[].class ) { + return DOUBLE; + } + return OBJECT; + } + + public Class getComponentType() { + return componentType; + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java index d42cdff6c..1fa443ddf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java @@ -7,11 +7,13 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntitySelectFetchInitializerBuilder; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.domain.CircularFetchImpl; @@ -24,8 +26,8 @@ public ReactiveCircularFetchImpl(CircularFetchImpl original) { } @Override - protected EntityInitializer buildEntitySelectFetchInitializer( - FetchParentAccess parentAccess, + protected EntityInitializer buildEntitySelectFetchInitializer( + InitializerParent parent, ToOneAttributeMapping fetchable, EntityPersister entityPersister, DomainResult keyResult, @@ -33,12 +35,33 @@ protected EntityInitializer buildEntitySelectFetchInitializer( boolean selectByUniqueKey, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, fetchable, entityPersister, keyResult, navigablePath, selectByUniqueKey, + false, + creationState + ); + } + + @Override + protected EntityInitializer buildEntityDelayedFetchInitializer( + InitializerParent parent, + NavigablePath referencedPath, + ToOneAttributeMapping fetchable, + boolean selectByUniqueKey, + DomainResult keyResult, + BasicFetch discriminatorFetch, + AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + referencedPath, + fetchable, + selectByUniqueKey, + keyResult, + discriminatorFetch, creationState ); } 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 5a563c093..7c23bc739 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 @@ -19,9 +19,9 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.spi.LoadContexts; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; @@ -34,7 +34,6 @@ import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** - * * @see org.hibernate.sql.results.spi.ListResultsConsumer */ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer, R> { @@ -45,7 +44,7 @@ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer DE_DUP_CONSUMER = new ReactiveListResultsConsumer<>( FILTER ); private static final ReactiveListResultsConsumer ERROR_DUP_CONSUMER = new ReactiveListResultsConsumer<>( ASSERT ); - private static void validateUniqueResult(Boolean unique) { + private static boolean validateUniqueResult(Boolean unique) { if ( !unique ) { throw new HibernateException( String.format( Locale.ROOT, @@ -53,25 +52,7 @@ private static void validateUniqueResult(Boolean unique) { ASSERT ) ); } - } - - private static class RegistrationHandler { - - private final LoadContexts contexts; - private final JdbcValuesSourceProcessingStateStandardImpl state; - - private RegistrationHandler(LoadContexts contexts, JdbcValuesSourceProcessingStateStandardImpl state) { - this.contexts = contexts; - this.state = state; - } - - public void register() { - contexts.register( state ); - } - - public void deregister() { - contexts.deregister( state ); - } + return true; } @Override @@ -101,65 +82,89 @@ public CompletionStage> consume( ? new EntityResult<>( domainResultJavaType ) : new Results<>( domainResultJavaType ); - Supplier> addToResultsSupplier = addToResultsSupplier( results, rowReader, rowProcessingState, processingOptions, isEntityResultType ); - final int[] readRows = { 0 }; + 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, readRows[0] ) ) - .handle( (list, ex) -> { - end( jdbcValues, session, jdbcValuesSourceProcessingState, rowReader, persistenceContext, ex ); - return list; - } ); + .thenCompose( hasNext -> { + if ( hasNext ) { + return addToResultsSupplier.get() + .thenApply( added -> { + rowProcessingState.finishRowProcessing( added ); + readRows[0]++; + return true; + } ); + + } + return falseFuture(); + } ) ) + .thenApply( v -> finishUp( rowReader, rowProcessingState, jdbcValuesSourceProcessingState, results, readRows, queryOptions ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> { + end( jdbcValues, session, jdbcValuesSourceProcessingState, persistenceContext, handler.getThrowable() ); + return handler.getResultAsCompletionStage(); + } ); + } + + private List finishUp( + ReactiveRowReader rowReader, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + Results results, int[] readRows, QueryOptions queryOptions) { + rowReader.finishUp( rowProcessingState ); + jdbcValuesSourceProcessingState.finishUp( readRows[0] > 1 ); + + final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); + return resultListTransformer != null + ? resultListTransformer.transformList( results.getResults() ) + : results.getResults(); } - private Supplier> addToResultsSupplier( + /** + * The boolean in the CompletionStage is true if the element has been added to the results + */ + private Supplier> addToResultsSupplier( ReactiveListResultsConsumer.Results results, ReactiveRowReader rowReader, ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions processingOptions, boolean isEntityResultType) { if ( this.uniqueSemantic == FILTER - || this.uniqueSemantic == ASSERT && rowProcessingState.hasCollectionInitializers() + || this.uniqueSemantic == ASSERT && rowReader.hasCollectionInitializers() || this.uniqueSemantic == ALLOW && isEntityResultType ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::addUnique ); + .thenApply( results::addUnique ); } if ( this.uniqueSemantic == ASSERT ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( results::addUnique ) - .thenAccept( ReactiveListResultsConsumer::validateUniqueResult ); + .thenApply( ReactiveListResultsConsumer::validateUniqueResult ); } return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::add ); + .thenApply( results::add ); } + private void end( ReactiveValuesResultSet jdbcValues, SharedSessionContractImplementor session, JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, Throwable ex) { try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); + jdbcValues.finishUp( session ); persistenceContext.afterLoad(); + persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); persistenceContext.initializeNonLazyCollections(); } catch (Throwable e) { @@ -174,26 +179,6 @@ private void end( } } - private List finishUp( - Results results, - JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, - QueryOptions queryOptions, - int readRows) { - try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); - jdbcValuesSourceProcessingState.finishUp( readRows > 0 ); - } - finally { - persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); - } - - final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); - return resultListTransformer != null - ? resultListTransformer.transformList( results.getResults() ) - : results.getResults(); - } - @SuppressWarnings("unchecked") public static ReactiveListResultsConsumer instance(ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { switch ( uniqueSemantic ) { @@ -298,8 +283,9 @@ public boolean addUnique(R result) { return true; } - public void add(R result) { + public boolean add(R result) { results.add( result ); + return true; } public List getResults() { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java index 9e0df6d04..fcf8bb760 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java @@ -5,27 +5,13 @@ */ package org.hibernate.reactive.sql.results.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; -import org.hibernate.sql.results.internal.InitializersList; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowReader; public interface ReactiveRowReader extends RowReader { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - CompletionStage reactiveReadRow(ReactiveRowProcessingState processingState, JdbcValuesSourceProcessingOptions options); - - @Override - default InitializersList getInitializersList() { - throw LOG.nonReactiveMethodCall( "getReactiveInitializersList" ); - } - - ReactiveInitializersList getReactiveInitializersList(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java index ad3d17853..814db1e4b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -25,13 +25,13 @@ public CompletionStage consume( JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, ReactiveRowProcessingState rowProcessingState, ReactiveRowReader rowReader) { - rowReader.getReactiveInitializersList().startLoading( rowProcessingState ); + rowReader.startLoading( rowProcessingState ); return rowProcessingState.next() .thenCompose( hasNext -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( result -> { rowProcessingState.finishRowProcessing( true ); - rowReader.finishUp( jdbcValuesSourceProcessingState ); + rowReader.finishUp( rowProcessingState ); jdbcValuesSourceProcessingState.finishUp( false ); return result; } ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java index b8b029fbd..f0a26dccc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java @@ -33,7 +33,7 @@ public JdbcType resolveType( if ( realDialect instanceof OracleDialect ) { String typeName = columnTypeInformation == null ? null : columnTypeInformation.getTypeName(); if ( typeName == null || typeName.isBlank() ) { - typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType.getJavaTypeDescriptor(), dialect ); + typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType, dialect ); } return new ReactiveOracleArrayJdbcType( elementType.getJdbcType(), typeName ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java index 8cb2d9cd5..52a9d9206 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java @@ -15,12 +15,17 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleArrayJdbcType; import org.hibernate.reactive.adaptor.impl.ArrayAdaptor; +import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.StructJdbcType; import static java.sql.Types.ARRAY; @@ -29,8 +34,11 @@ */ public class ReactiveOracleArrayJdbcType extends OracleArrayJdbcType { + private final String upperTypeName; + public ReactiveOracleArrayJdbcType(JdbcType elementJdbcType, String typeName) { super( elementJdbcType, typeName ); + this.upperTypeName = typeName == null ? null : typeName.toUpperCase( Locale.ROOT ); } public static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType) { @@ -52,8 +60,10 @@ public ValueBinder getBinder(final JavaType javaTypeDescriptor) { final BasicPluralJavaType containerJavaType = (BasicPluralJavaType) javaTypeDescriptor; return new BasicBinder<>( javaTypeDescriptor, this ) { private String typeName(WrapperOptions options) { - String typeName = getTypeName() == null ? getTypeName( options, containerJavaType ) : getTypeName(); - return typeName.toUpperCase( Locale.ROOT ); + return ( upperTypeName == null + ? getTypeName( options, (BasicPluralJavaType) getJavaType(), (ArrayJdbcType) getJdbcType() ).toUpperCase( Locale.ROOT ) + : upperTypeName + ); } @Override @@ -93,4 +103,71 @@ private ArrayAdaptor getArray(X value, BasicPluralJavaType containerJavaType, } }; } + + /* + * FIXME: We should change the scope of these methods in ORM: see OracleArrayJdbcType#getTypeName + */ + + static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType, ArrayJdbcType arrayJdbcType) { + Dialect dialect = options.getSessionFactory().getJdbcServices().getDialect(); + return getTypeName( containerJavaType.getElementJavaType(), arrayJdbcType.getElementJdbcType(), dialect ); + } + + static String getTypeName(BasicType elementType, Dialect dialect) { + final BasicValueConverter converter = elementType.getValueConverter(); + if ( converter != null ) { + final String simpleName; + if ( converter instanceof JpaAttributeConverter ) { + simpleName = ( (JpaAttributeConverter) converter ).getConverterJavaType() + .getJavaTypeClass() + .getSimpleName(); + } + else { + simpleName = converter.getClass().getSimpleName(); + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + return getTypeName( elementType.getJavaTypeDescriptor(), elementType.getJdbcType(), dialect ); + } + + static String getTypeName(JavaType elementJavaType, JdbcType elementJdbcType, Dialect dialect) { + final String simpleName; + if ( elementJavaType.getJavaTypeClass().isArray() ) { + simpleName = dialect.getArrayTypeName( + elementJavaType.getJavaTypeClass().getComponentType().getSimpleName(), + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + else if ( elementJdbcType instanceof StructJdbcType ) { + simpleName = ( (StructJdbcType) elementJdbcType ).getStructTypeName(); + } + else { + final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( null ); + if ( preferredJavaTypeClass == elementJavaType.getJavaTypeClass() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName(); + } + else { + if ( preferredJavaTypeClass.isArray() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + dialect.getArrayTypeName( + preferredJavaTypeClass.getComponentType().getSimpleName(), + null, + null + ); + } + else { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + preferredJavaTypeClass.getSimpleName(); + } + } + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java index c45f8a87b..0b7e3b41e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java @@ -8,8 +8,10 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.TransientObjectException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -22,14 +24,32 @@ import jakarta.persistence.Table; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; @Timeout(value = 10, timeUnit = MINUTES) - public class EagerOneToOneAssociationTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Book.class, Author.class ); + return List.of( Author.class, Book.class ); + } + + @Override + public CompletionStage deleteEntities(Class... entities) { + return getSessionFactory() + .withTransaction( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> loop( books, book -> { + Author author = book.author; + book.author = null; + author.mostPopularBook = null; + return s.remove( book, author ); + } ) + ) + ); } @Test @@ -39,16 +59,48 @@ public void testPersist(VertxTestContext context) { mostPopularBook.setAuthor( author ); author.setMostPopularBook( mostPopularBook ); - test( - context, - openSession() - .thenCompose( s -> s.persist( mostPopularBook ) - .thenCompose( v -> s.persist( author ) ) - .thenCompose( v -> s.flush() ) - ) - .thenCompose( v -> openSession() ) - .thenCompose( s -> s.find( Book.class, 5 ) ) - .thenAccept( Assertions::assertNotNull) + test( context, openSession() + .thenCompose( s -> s + .persist( mostPopularBook ) + .thenCompose( v -> s.persist( author ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s.find( Book.class, 5 ) ) + .thenAccept( book -> { + assertThat( book ).isEqualTo( mostPopularBook ); + assertThat( book.getAuthor() ).isEqualTo( author ); + } ) + ); + } + + @Test + public void testTransientException(VertxTestContext context) { + final Book yellowface = new Book( 7, "Yellowface" ); + final Author kuang = new Author( 19, "R.F. Kuang" ); + yellowface.setAuthor( kuang ); + kuang.setMostPopularBook( yellowface ); + + test( context, assertThrown( TransientObjectException.class, openSession() + .thenCompose( s -> s + .persist( yellowface ) + .thenCompose( v -> s.persist( kuang ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> s.remove( books.toArray() ) ) + // This query should force an auto-flush. Because we have deleted the book but not the associated author + // it should cause a TransientObjectException. Note that this validation has been added in 6.6, and the same test + // wasn't throwing any exception with ORM 6.5 + .thenCompose( v -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultList() + ) + ) + ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java index 097db58ff..48b73829f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java @@ -19,6 +19,7 @@ import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Table; import jakarta.persistence.metamodel.SingularAttribute; import static java.util.concurrent.TimeUnit.MINUTES; @@ -302,6 +303,7 @@ private void assertOrderByBookArray(List resultList, Book... expectedB } @Entity(name = "Book") + @Table(name = "OrderTest_Book" ) static class Book { @Id String isbn; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java index c25b25b1e..4ba2e844e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java @@ -30,6 +30,7 @@ import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.Root; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @@ -103,16 +104,10 @@ public void testDeletedStrategyWithYesNoConverter(VertxTestContext context) { @Test public void testDefaults(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .remove( s.getReference( ImplicitEntity.class, implicitEntities[0].getId() ) ) @@ -122,16 +117,10 @@ public void testDefaults(VertxTestContext context) { @Test public void testDeletionWithHQLQuery(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> requireNonNull( dbType() ) == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .createMutationQuery( "delete from ImplicitEntity where name = :name" ) @@ -143,16 +132,10 @@ public void testDeletionWithHQLQuery(VertxTestContext context) { @Test public void testDeletionWithCriteria(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> { CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java index 874517f34..788adccea 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java @@ -5,20 +5,11 @@ */ package org.hibernate.reactive; -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import java.net.URI; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.MariaDBDialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.dialect.SQLServerDialect; import org.hibernate.reactive.containers.DatabaseConfiguration; +import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.Assertions; @@ -26,6 +17,11 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.SqlConnectOptions; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @Timeout(value = 10, timeUnit = MINUTES) @@ -33,20 +29,7 @@ public class UriConfigTest extends BaseReactiveTest { @Override protected Configuration constructConfiguration() { - Class dialect; - switch ( dbType() ) { - case POSTGRESQL: dialect = PostgreSQLDialect.class; break; - case COCKROACHDB: dialect = CockroachDialect.class; break; - case MYSQL: dialect = MySQLDialect.class; break; - case MARIA: dialect = MariaDBDialect.class; break; - case SQLSERVER: dialect = SQLServerDialect.class; break; - case DB2: dialect = DB2Dialect.class; break; - case ORACLE: dialect = OracleDialect.class; break; - default: throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); - } - Configuration configuration = super.constructConfiguration(); - configuration.setProperty( Environment.DIALECT, dialect.getName() ); configuration.setProperty( Settings.URL, DatabaseConfiguration.getUri() ); configuration.setProperty( Settings.SQL_CLIENT_POOL_CONFIG, UriPoolConfiguration.class.getName() ); return configuration; @@ -77,4 +60,30 @@ private String selectQuery() { throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); } } + + /** + * This class is used by {@link UriConfigTest} to test that one can use a different implementation of + * {@link SqlClientPoolConfiguration}. + *

+ * But, it also test {@link SqlConnectOptions#fromUri(String)} for each database. + *

+ */ + public static class UriPoolConfiguration implements SqlClientPoolConfiguration { + @Override + public PoolOptions poolOptions() { + return new PoolOptions(); + } + + @Override + public SqlConnectOptions connectOptions(URI uri) { + // For CockroachDB we use the PostgreSQL Vert.x client + String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); + if ( uriString.startsWith( "sqlserver" ) ) { + // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's + // different from the supported one by the Vert.x driver. + uriString = uriString.replaceAll( ";[e|E]ncrypt=false", "?encrypt=false" ); + } + return SqlConnectOptions.fromUri( uriString ); + } + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java deleted file mode 100644 index 5ac7c404e..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive; - -import java.net.URI; - -import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; - -import io.vertx.sqlclient.PoolOptions; -import io.vertx.sqlclient.SqlConnectOptions; - -/** - * This class is used by {@link UriConfigTest} to test that one can use a different implementation of - * {@link SqlClientPoolConfiguration}. - *

- * But, in reality, it also checks {@link SqlConnectOptions#fromUri(String)} works for each database. - *

- */ -public class UriPoolConfiguration implements SqlClientPoolConfiguration { - @Override - public PoolOptions poolOptions() { - return new PoolOptions(); - } - - @Override - public SqlConnectOptions connectOptions(URI uri) { - // For CockroachDB we use the PostgreSQL Vert.x client - String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); - if ( uriString.startsWith( "sqlserver" ) ) { - // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's - // different from the supported one by the Vert.x driver. - uriString = uriString.replaceAll( ";encrypt=false", "?encrypt=false" ); - } - return SqlConnectOptions.fromUri( uriString ); - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java index babae4bd4..df28ce62a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java @@ -14,13 +14,13 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.pool.ReactiveConnectionPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPoolConfiguration; import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.testing.TestingRegistryExtension; -import org.hibernate.reactive.annotations.EnabledFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index 6f157192e..9ad2f8136 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -44,10 +44,9 @@ class OracleDatabase implements TestableDatabase { static { { - expectedDBTypeForClass.put( boolean.class, "NUMBER" ); - expectedDBTypeForClass.put( Boolean.class, "NUMBER" ); + expectedDBTypeForClass.put( boolean.class, "BOOLEAN" ); + expectedDBTypeForClass.put( Boolean.class, "BOOLEAN" ); - // FIXME: [ORM-6] Check if we need alternatives expectedDBTypeForClass.put( NumericBooleanConverter.class, "NUMBER" ); expectedDBTypeForClass.put( YesNoConverter.class, "CHAR" ); expectedDBTypeForClass.put( TrueFalseConverter.class, "CHAR" ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java index 2e26d2751..4f46641a1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java @@ -10,8 +10,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach;