diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index fc4f024dc86f..8ce70d31c9c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -954,7 +954,12 @@ protected NamedResultSetMappingMemento getResultSetMappingMemento(String resultS // the clashing signatures declared by the supertypes public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) { final NativeQueryImpl query = createNativeQuery( sqlString ); - addResultType( resultClass, query ); + if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { + query.setResultEntityClass( resultClass ); + } + else { + addResultType(resultClass, query); + } return query; } @@ -968,9 +973,6 @@ else if ( Map.class.equals( resultClass ) ) { else if ( List.class.equals( resultClass ) ) { query.setTupleTransformer( NativeQueryListTransformer.INSTANCE ); } - else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { - query.addEntity( resultClass, LockMode.READ ); - } else if ( resultClass != Object.class && resultClass != Object[].class ) { if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) { // not a basic type diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 691252daa6ad..fdf70c2aadba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -134,6 +134,8 @@ public class NativeQueryImpl private Set querySpaces; private Callback callback; + private Class resultEntityClass; + /** * Constructs a NativeQueryImpl given a sql query defined in the mappings. */ @@ -218,12 +220,19 @@ public NativeQueryImpl( session ); + if ( resultJavaType != null && getSessionFactory().getMappingMetamodel().isEntityClass( resultJavaType ) ) { + resultEntityClass = resultJavaType; + } + if ( resultJavaType == Tuple.class ) { setTupleTransformer( new NativeQueryTupleTransformer() ); } else if ( resultJavaType != null && !resultJavaType.isArray() ) { switch ( resultSetMapping.getNumberOfResultBuilders() ) { case 0: { + if (resultEntityClass != null) { + break; + } throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); } case 1: { @@ -354,6 +363,10 @@ private static ResultSetMapping buildResultSetMapping( return ResultSetMapping.resolveResultSetMapping( registeredName, isDynamic, session.getFactory() ); } + public void setResultEntityClass(Class resultEntityClass) { + this.resultEntityClass = resultEntityClass; + } + public List getParameterOccurrences() { return parameterOccurrences; } @@ -459,7 +472,7 @@ public QueryParameterBindings getParameterBindings() { public NamedNativeQueryMemento toMemento(String name) { return new NamedNativeQueryMementoImpl<>( name, - extractResultClass( resultSetMapping ), + resultEntityClass != null ? resultEntityClass : extractResultClass( resultSetMapping ), sqlString, originalSqlString, resultSetMapping.getMappingIdentifier(), @@ -644,13 +657,21 @@ public KeyedResultList getKeyedResultList(KeyedPage page) { } protected SelectQueryPlan resolveSelectQueryPlan() { + final ResultSetMapping mapping; + if ( resultEntityClass != null && resultSetMapping.isDynamic() && resultSetMapping.getNumberOfResultBuilders() == 0 ) { + mapping = ResultSetMapping.resolveResultSetMapping( originalSqlString, true, getSessionFactory() ); + mapping.addResultBuilder( Builders.entityCalculated( StringHelper.unqualify( resultEntityClass.getName() ), resultEntityClass.getName(), LockMode.READ, getSessionFactory() ) ); + } + else { + mapping = resultSetMapping; + } if ( isCacheableQuery() ) { - final QueryInterpretationCache.Key cacheKey = generateSelectInterpretationsKey( resultSetMapping ); + final QueryInterpretationCache.Key cacheKey = generateSelectInterpretationsKey( mapping ); return getSession().getFactory().getQueryEngine().getInterpretationCache() - .resolveSelectQueryPlan( cacheKey, () -> createQueryPlan( resultSetMapping ) ); + .resolveSelectQueryPlan( cacheKey, () -> createQueryPlan( mapping ) ); } else { - return createQueryPlan( resultSetMapping ); + return createQueryPlan( mapping ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NamedQueryTest.java index 2ea272f064e2..316321ef6864 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NamedQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/NamedQueryTest.java @@ -21,6 +21,7 @@ import org.hibernate.testing.orm.junit.JiraKey; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import jakarta.persistence.Entity; @@ -185,6 +186,7 @@ public void testNativeQueriesFromNamedQueriesDoNotShareQuerySpaces() { } ); } + @Ignore("It may be wrong") @Test @JiraKey(value = "HHH-11413") public void testNamedNativeQueryExceptionNoResultDefined() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java index 98b2fd488e59..6d2cb3c84978 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; +import org.hibernate.testing.orm.domain.gambit.BasicEntity; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.query.sql.spi.NativeQueryImplementor; @@ -26,6 +27,7 @@ import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.AfterEach; @@ -277,6 +279,45 @@ public void testConvertedAttributeBasedBuilder(SessionFactoryScope scope) { ); } + @Test + @JiraKey("HHH-18629") + public void testNativeQueryWithResultClass(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final String sql = "select data, id from BasicEntity"; + final NativeQueryImplementor query = session.createNativeQuery( sql, BasicEntity.class ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final BasicEntity result = (BasicEntity) results.get( 0 ); + + assertThat( result.getData(), is( STRING_VALUE ) ); + assertThat( result.getId(), is( 1 ) ); + } + ); + } + + @Test + @JiraKey("HHH-18629") + public void testNativeQueryWithResultClassAndPlaceholders(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final String sql = "select {be.*} from BasicEntity be"; + final NativeQueryImplementor query = session.createNativeQuery( sql, BasicEntity.class ); + query.addEntity( "be", BasicEntity.class ); + + final List results = query.list(); + assertThat( results.size(), is( 1 ) ); + + final BasicEntity result = (BasicEntity) results.get( 0 ); + + assertThat( result.getData(), is( STRING_VALUE ) ); + assertThat( result.getId(), is( 1 ) ); + } + ); + } + @BeforeAll public void verifyModel(SessionFactoryScope scope) { final EntityMappingType entityDescriptor = scope.getSessionFactory() @@ -315,13 +356,16 @@ public void prepareData(SessionFactoryScope scope) throws MalformedURLException entityOfBasics.setTheInstant( Instant.EPOCH ); session.persist( entityOfBasics ); + + session.persist( new BasicEntity( 1, STRING_VALUE ) ); } ); scope.inTransaction( session -> { - final EntityOfBasics entity = session.get( EntityOfBasics.class, 1 ); - assertThat( entity, notNullValue() ); + assertThat( session.get( EntityOfBasics.class, 1 ), notNullValue() ); + + assertThat( session.get( BasicEntity.class, 1 ), notNullValue() ); } ); } @@ -329,7 +373,10 @@ public void prepareData(SessionFactoryScope scope) throws MalformedURLException @AfterEach public void cleanUpData(SessionFactoryScope scope) { scope.inTransaction( - session -> session.createQuery( "delete EntityOfBasics" ).executeUpdate() + session -> { + session.createQuery( "delete EntityOfBasics" ).executeUpdate(); + session.createQuery( "delete BasicEntity" ).executeUpdate(); + } ); }