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 2c2f720ad..5c7e01ff0 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 @@ -50,7 +50,7 @@ * Emulate {@link org.hibernate.query.spi.AbstractSelectionQuery}. *

* Hibernate Reactive implementations already extend another class, - * they cannot extends {@link org.hibernate.query.spi.AbstractSelectionQuery too}. + * they cannot extend {@link org.hibernate.query.spi.AbstractSelectionQuery too}. * This approach allows us to avoid duplicating code. *

* @param @@ -74,7 +74,7 @@ public class ReactiveAbstractSelectionQuery { private Set fetchProfiles; - private final Runnable beforeQuery; + private final Supplier> beforeQuery; private final Consumer afterQuery; private final Function, R> uniqueElement; @@ -93,7 +93,7 @@ public ReactiveAbstractSelectionQuery( Supplier getDomainParameterXref, Supplier> getResultType, Supplier getQueryString, - Runnable beforeQuery, + Supplier> beforeQuery, Consumer afterQuery, Function, R> uniqueElement) { this( @@ -121,7 +121,7 @@ public ReactiveAbstractSelectionQuery( Supplier getDomainParameterXref, Supplier> getResultType, Supplier getQueryString, - Runnable beforeQuery, + Supplier> beforeQuery, Consumer afterQuery, Function, R> uniqueElement, InterpretationsKeySource interpretationsKeySource) { @@ -200,8 +200,8 @@ private LockOptions getLockOptions() { public CompletionStage> reactiveList() { final Set profiles = applyProfiles(); - beforeQuery.run(); - return doReactiveList() + return beforeQuery.get() + .thenCompose( v -> doReactiveList() ) .handle( (list, error) -> { handleException( error ); return list; 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 a0ed5e638..ebeef3d22 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 @@ -42,6 +42,7 @@ import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.type.BasicTypeReference; @@ -52,8 +53,11 @@ import jakarta.persistence.LockModeType; import jakarta.persistence.Parameter; import jakarta.persistence.TemporalType; -import jakarta.persistence.metamodel.Type; import jakarta.persistence.metamodel.SingularAttribute; +import jakarta.persistence.metamodel.Type; + +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + public class ReactiveNativeQueryImpl extends NativeQueryImpl implements ReactiveNativeQueryImplementor { @@ -123,13 +127,46 @@ private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSes this::getNull, this::getNull, this::getQueryString, - this::beforeQuery, + this::reactiveBeforeQuery, this::afterQuery, AbstractSelectionQuery::uniqueElement, null ); } + protected CompletionStage reactiveBeforeQuery() { + getQueryParameterBindings().validate(); + + final var session = getSession(); + session.prepareForQueryExecution( requiresTxn( getQueryOptions().getLockOptions().getLockMode() ) ); + return reactivePrepareForExecution() + .thenAccept( v -> { + prepareSessionFlushMode( session ); + prepareSessionCacheMode( session ); + } ); + } + + protected CompletionStage reactivePrepareForExecution() { + final var spaces = getSynchronizedQuerySpaces(); + if ( spaces == null || spaces.isEmpty() ) { + // We need to flush. The query itself is not required to execute in a + // transaction; if there is no transaction, the flush would throw a + // TransactionRequiredException which would potentially break existing + // apps, so we only do the flush if a transaction is in progress. + if ( shouldFlush() ) { + return ( (ReactiveSession) getSession() ) + .reactiveFlush() + .thenAccept( v -> resetCallback() ); + } + // Reset the callback before every execution + resetCallback(); + } + // Otherwise, the application specified query spaces via the Hibernate + // SynchronizeableQuery and so the query will already perform a partial + // flush according to the defined query spaces - no need for a full flush. + return voidFuture(); + } + private CompletionStage> doReactiveList() { return reactiveSelectPlan().reactivePerformList( this ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java index a786ac2cd..2592ad40b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java @@ -72,6 +72,9 @@ import jakarta.persistence.TemporalType; import jakarta.persistence.metamodel.Type; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactive {@link SqmQueryImpl} */ @@ -124,12 +127,22 @@ private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSes this::getDomainParameterXref, this::getResultType, this::getQueryString, - this::beforeQuery, + this::reactiveBeforeQuery, this::afterQuery, AbstractSelectionQuery::uniqueElement ); } + private CompletionStage reactiveBeforeQuery() { + try { + beforeQuery(); + return voidFuture(); + } + catch (Throwable e) { + return failedFuture( e ); + } + } + @Override public CompletionStage reactiveUnique() { return selectionQueryDelegate.reactiveUnique(); 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 a71607dcc..91393ace4 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 @@ -47,6 +47,8 @@ import java.util.stream.Stream; import static org.hibernate.query.spi.SqlOmittingQueryOptions.omitSqlQueryOptions; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * A reactive {@link SqmSelectionQueryImpl} @@ -83,15 +85,24 @@ private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSes this::getDomainParameterXref, this::getResultType, this::getQueryString, - this::beforeQuery, + this::reactiveBeforeQuery, this::afterQuery, AbstractSelectionQuery::uniqueElement ); } + private CompletionStage reactiveBeforeQuery() { + try { + beforeQuery(); + return voidFuture(); + } + catch (Throwable e) { + return failedFuture( e ); + } + } + private CompletionStage> doReactiveList() { - getSession().prepareForQueryExecution( requiresTxn( getQueryOptions().getLockOptions() - .findGreatestLockMode() ) ); + getSession().prepareForQueryExecution( requiresTxn( getQueryOptions().getLockOptions().findGreatestLockMode() ) ); final SqmSelectStatement sqmStatement = getSqmStatement(); final boolean containsCollectionFetches = sqmStatement.containsCollectionFetches(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 9196e9b16..37b5268b0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -132,9 +132,11 @@ public class ReactiveStatelessSessionImpl extends StatelessSessionImpl implement private final ReactiveConnection reactiveConnection; private final ReactiveStatelessSessionImpl batchingHelperSession; private final PersistenceContext persistenceContext; + private final boolean connectionProvided; public ReactiveStatelessSessionImpl(SessionFactoryImpl factory, SessionCreationOptions options, ReactiveConnection connection) { super( factory, options ); + connectionProvided = options.getConnection() != null; reactiveConnection = connection; persistenceContext = new ReactivePersistenceContextAdapter( super.getPersistenceContext() ); batchingHelperSession = new ReactiveStatelessSessionImpl( factory, options, reactiveConnection, persistenceContext ); @@ -150,6 +152,7 @@ private ReactiveStatelessSessionImpl( ReactiveConnection connection, PersistenceContext persistenceContext) { super( factory, options ); + connectionProvided = options.getConnection() != null; this.persistenceContext = persistenceContext; // StatelessSession should not allow JDBC batching, because that would change // its "immediate synchronous execution" model into something more like transactional @@ -1019,6 +1022,12 @@ public void prepareForQueryExecution(boolean requiresTxn) { // } } + @Override + public boolean isTransactionInProgress() { + return connectionProvided || ( isOpenOrWaitingForAutoClose() + && reactiveConnection.isTransactionInProgress() ); + } + @Override public ReactiveSqmQueryImplementor createReactiveQuery(String queryString, Class expectedResultType) { checkOpen(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NoEntitiesTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NoEntitiesTest.java new file mode 100644 index 000000000..accefa16f --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NoEntitiesTest.java @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.hibernate.FlushMode; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NoEntitiesTest extends BaseReactiveTest { + + @Test + public void emptyMetamodelWithMutiny() { + assertThat( getMutinySessionFactory().getMetamodel().getEntities() ).isEmpty(); + } + + @Test + public void shouldBeAbleToRunQueryWithMutinyTransaction(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( s -> s + .createNativeQuery( "select 42", Long.class ).getSingleResult() + ).invoke( result -> assertThat( result ).isEqualTo( 42L ) ) + ); + } + + @Test + public void runNativeQueryWithMutinyTransactionAndFlush(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( s -> { + s.setFlushMode( FlushMode.ALWAYS ); + return s + .createNativeQuery( "select 42", Long.class ).getSingleResult() + .call( s::flush ); + } ).invoke( result -> assertThat( result ).isEqualTo( 42L ) ) + ); + } + + @Test + public void runStatelessNativeQueryWithMutinyTransactionAndFlush(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withStatelessTransaction( s -> s + .createNativeQuery( "select 42", Long.class ) + .getSingleResult() + ) + .invoke( result -> assertThat( result ).isEqualTo( 42L ) ) + ); + } + + @Test + public void shouldBeAbleToRunQueryWithMutinyWithoutTransaction(VertxTestContext context) { + test( context, getMutinySessionFactory() + .openSession().chain( s -> s + .createNativeQuery( "select 666", Long.class ).getSingleResult() + ).invoke( result -> assertThat( result ).isEqualTo( 666L ) ) + ); + } +}