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..6526bde45 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SQLSelectTest.java @@ -0,0 +1,125 @@ +/* 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + + +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.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; +import static org.hibernate.reactive.testing.DBSelectionExtension.runOnlyFor; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +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; + public static String SQL = "xxxxx"; + + @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) { + List phones = Arrays.asList( "999-999-9999", "111-111-1111", "123-456-7890" ); + thePerson = new Person(); + thePerson.id = 724200; + thePerson.name = "Claude"; + thePerson.phones = phones; + + test( context, getMutinySessionFactory().withTransaction( (s, t) -> s.persist( thePerson ) ) ); + } + + @Test + public void findEntity(VertxTestContext context) { + test( context, openSession() + .thenCompose( session -> session.find( Person.class, thePerson.getId() ) ) + .thenAccept( found -> { + assertPhones( found, "999-999-9999", "111-111-1111", "123-456-7890" ); + assertThat( sqlTracker.getLoggedQueries() ).contains( Person.SELECT_QUERY.replace( "?", "$1" ) ); + } ) + ); + } + + private static void assertPhones(Person person, String... expectedPhones) { + assertNotNull( person ); + assertThat( person.getPhones() ).containsExactlyInAnyOrder( expectedPhones ); + } + + @Entity(name = "Person") + @SQLSelect(sql = Person.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; + + @ElementCollection(fetch = FetchType.EAGER) + private List phones = new ArrayList<>(); + + 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; + } + + public List getPhones() { + return phones; + } + } +}