diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java index f509eb9a2..6b849d346 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderProvidedQueryImpl.java @@ -15,6 +15,7 @@ import org.hibernate.query.named.NamedQueryMemento; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; +import org.hibernate.reactive.query.ReactiveSelectionQuery; import jakarta.persistence.Parameter; @@ -51,7 +52,7 @@ public CompletionStage load(Object pkValue, LockOptions lockOptions, Boolean query.setParameter( (Parameter) query.getParameters().iterator().next(), pkValue ); query.setHibernateFlushMode( FlushMode.MANUAL ); - return completedFuture( query.uniqueResult() ); + return ( (ReactiveSelectionQuery) query ).reactiveUnique(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java index f4d8da437..52d778e1a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/internal/ReactiveNamedObjectRepositoryImpl.java @@ -32,7 +32,7 @@ public ReactiveNamedObjectRepositoryImpl(NamedObjectRepository delegate) { @Override public NamedSqmQueryMemento getSqmQueryMemento(String queryName) { - return wrap( delegate.getSqmQueryMemento( queryName ) ); + return wrapSqmQueryMemento( delegate.getSqmQueryMemento( queryName ) ); } @Override @@ -47,7 +47,7 @@ public void registerSqmQueryMemento(String name, NamedSqmQueryMemento descriptor @Override public NamedNativeQueryMemento getNativeQueryMemento(String queryName) { - return wrap( delegate.getNativeQueryMemento( queryName ) ); + return wrapNativeQueryMemento( delegate.getNativeQueryMemento( queryName ) ); } @Override @@ -105,7 +105,7 @@ public NamedQueryMemento resolve( SessionFactoryImplementor sessionFactory, MetadataImplementor bootMetamodel, String registrationName) { - return delegate.resolve( sessionFactory, bootMetamodel, registrationName ); + return wrap(delegate.resolve( sessionFactory, bootMetamodel, registrationName )); } @Override @@ -118,7 +118,17 @@ public void close() { delegate.close(); } - private static NamedSqmQueryMemento wrap(final NamedSqmQueryMemento sqmQueryMemento) { + private static NamedQueryMemento wrap(final NamedQueryMemento namedQueryMemento) { + if ( namedQueryMemento == null ) { + return null; + } else if( namedQueryMemento instanceof NamedSqmQueryMemento ) { + return wrapSqmQueryMemento( (NamedSqmQueryMemento) namedQueryMemento ); + } else { + return wrapNativeQueryMemento( (NamedNativeQueryMemento) namedQueryMemento ); + } + } + + private static NamedSqmQueryMemento wrapSqmQueryMemento(final NamedSqmQueryMemento sqmQueryMemento) { if ( sqmQueryMemento == null ) { return null; } @@ -131,7 +141,7 @@ else if ( sqmQueryMemento instanceof ReactiveNamedSqmQueryMemento ) { } } - private static NamedNativeQueryMemento wrap(final NamedNativeQueryMemento nativeQueryMemento) { + private static NamedNativeQueryMemento wrapNativeQueryMemento(final NamedNativeQueryMemento nativeQueryMemento) { if ( nativeQueryMemento == null ) { return null; } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SQLSelectTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SQLSelectTest.java new file mode 100644 index 000000000..e7990e486 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SQLSelectTest.java @@ -0,0 +1,129 @@ +/* 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.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.hibernate.annotations.SQLSelect; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.testing.DBSelectionExtension; +import org.hibernate.reactive.testing.SqlStatementTracker; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.SQLSelectTest.Person.SELECT_QUERY; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; +import static org.hibernate.reactive.testing.DBSelectionExtension.runOnlyFor; + +public class SQLSelectTest extends BaseReactiveTest { + @RegisterExtension // We use native queries, which may be different for other DBs + public DBSelectionExtension dbSelection = runOnlyFor( POSTGRESQL ); + + private SqlStatementTracker sqlTracker; + + private Person thePerson; + + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class ); + } + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + sqlTracker = new SqlStatementTracker( SQLSelectTest::doCheckQuery, configuration.getProperties() ); + return configuration; + } + + private static boolean doCheckQuery(String s) { + return s.toLowerCase().startsWith( "select" ); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + thePerson = new Person(); + thePerson.id = 724200; + thePerson.name = "Claude"; + + test( context, getMutinySessionFactory().withTransaction( s -> s.persist( thePerson ) ) ); + } + + @Test + public void findEntity(VertxTestContext context) { + test( context, openSession() + .thenCompose( session -> session.find( Person.class, thePerson.getId() ) ) + .thenAccept( found -> { + assertThat( found ).isEqualTo( thePerson ); + assertThat( sqlTracker.getLoggedQueries() ) + .containsExactly( + "select version()", + SELECT_QUERY.replace( "?", "$1" ) + ); + } ) + ); + } + + + @Entity(name = "Person") + @SQLSelect(sql = SELECT_QUERY) + static class Person { + // Query containing simple where check to distinguish from a possible generated query + public static final String SELECT_QUERY = "SELECT id, name FROM person WHERE id = ? and 'hreact' = 'hreact'"; + + @Id + private int id; + + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Person person = (Person) o; + return Objects.equals( name, person.name ); + } + + @Override + public int hashCode() { + return Objects.hash( name ); + } + } +}