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 ) )
+ );
+ }
+}