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 f20bab9c6f..eb42fe9f23 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -858,7 +858,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); } else if ( resultClass != Object.class && resultClass != Object[].class ) { - query.addScalar( 1, resultClass ); + query.addResultTypeClass( resultClass ); } return query; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java index 899cec4663..211f659e4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java @@ -246,8 +246,13 @@ public class Builders { public static ResultBuilder resultClassBuilder( Class resultMappingClass, ResultSetMappingResolutionContext resolutionContext) { - final MappingMetamodelImplementor mappingMetamodel = resolutionContext - .getSessionFactory() + return resultClassBuilder( resultMappingClass, resolutionContext.getSessionFactory() ); + } + + public static ResultBuilder resultClassBuilder( + Class resultMappingClass, + SessionFactoryImplementor sessionFactory) { + final MappingMetamodelImplementor mappingMetamodel = sessionFactory .getRuntimeMetamodels() .getMappingMetamodel(); final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java index a852bcdf19..89bebac443 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java @@ -91,6 +91,9 @@ public class ResultSetMappingImpl implements ResultSetMapping { } public List getResultBuilders() { + if ( resultBuilders == null ) { + return Collections.emptyList(); + } return Collections.unmodifiableList( resultBuilders ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java index 8ba29214b4..fa34114f44 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NamedNativeQueryMementoImpl.java @@ -12,6 +12,7 @@ import java.util.Set; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.named.AbstractNamedQueryMemento; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sql.spi.NamedNativeQueryMemento; @@ -127,7 +128,7 @@ public class NamedNativeQueryMementoImpl extends AbstractNamedQueryMemento imple originalSqlString, resultSetMappingName, resultSetMappingClass, - querySpaces, + CollectionHelper.makeCopy( querySpaces ), getCacheable(), getCacheRegion(), getCacheMode(), 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 6aef48fa63..ffcb8befc9 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 @@ -61,6 +61,8 @@ import org.hibernate.query.results.ResultSetMappingImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard; import org.hibernate.query.results.dynamic.DynamicResultBuilderInstantiation; +import org.hibernate.query.results.implicit.ImplicitModelPartResultBuilderEntity; +import org.hibernate.query.results.implicit.ImplicitResultClassBuilder; import org.hibernate.query.spi.AbstractQuery; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.MutableQueryOptions; @@ -246,12 +248,6 @@ public class NativeQueryImpl return true; } - if ( resultJavaType != null && resultJavaType != Tuple.class && !resultJavaType.isArray() ) { - // todo : allow the expected Java type imply a builder to use -// resultSetMapping.addResultBuilder( resultClassBuilder( resultJavaType, context ) ); -// return true; - } - return false; }, session @@ -260,19 +256,21 @@ public class NativeQueryImpl if ( resultJavaType == Tuple.class ) { setTupleTransformer( new NativeQueryTupleTransformer() ); } - else if ( resultJavaType != null && resultJavaType != Object[].class ) { + else if ( resultJavaType != null && !resultJavaType.isArray() ) { switch ( resultSetMapping.getNumberOfResultBuilders() ) { - case 0: + case 0: { throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); - case 1: - // would be nice to support types that are "wrappable", as in `JavaType#wrap` + } + case 1: { final Class actualResultJavaType = resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); if ( actualResultJavaType != null && !resultJavaType.isAssignableFrom( actualResultJavaType ) ) { throw buildIncompatibleException( resultJavaType, actualResultJavaType ); } break; - default: + } + default: { throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" ); + } } } } @@ -449,7 +447,7 @@ public class NativeQueryImpl sqlString, originalSqlString, resultSetMapping.getMappingIdentifier(), - null, + extractResultClass( resultSetMapping ), querySpaces, isCacheable(), getCacheRegion(), @@ -465,6 +463,22 @@ public class NativeQueryImpl ); } + private Class extractResultClass(ResultSetMappingImpl resultSetMapping) { + final List resultBuilders = resultSetMapping.getResultBuilders(); + if ( resultBuilders.size() == 1 ) { + final ResultBuilder resultBuilder = resultBuilders.get( 0 ); + if ( resultBuilder instanceof ImplicitResultClassBuilder ) { + final ImplicitResultClassBuilder resultTypeBuilder = (ImplicitResultClassBuilder) resultBuilder; + return resultTypeBuilder.getJavaType(); + } + else if ( resultBuilder instanceof ImplicitModelPartResultBuilderEntity ) { + final ImplicitModelPartResultBuilderEntity resultTypeBuilder = (ImplicitModelPartResultBuilderEntity) resultBuilder; + return resultTypeBuilder.getJavaType(); + } + } + return null; + } + @Override public LockModeType getLockMode() { // the JPA spec requires IllegalStateException here, even @@ -841,6 +855,11 @@ public class NativeQueryImpl : null; } + public void addResultTypeClass(Class resultClass) { + assert CollectionHelper.isEmpty( resultSetMapping.getResultBuilders() ); + registerBuilder( Builders.resultClassBuilder( resultClass, getSessionFactory() ) ); + } + @Override public NativeQueryImplementor addScalar(String columnAlias) { return registerBuilder( Builders.scalar( columnAlias ) ); 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 d8fa6c93e7..a7216be85a 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 @@ -26,6 +26,7 @@ import jakarta.persistence.NamedNativeQuery; import jakarta.persistence.NamedQueries; import jakarta.persistence.NamedQuery; import jakarta.persistence.Query; +import jakarta.persistence.TypedQuery; import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; @@ -195,6 +196,19 @@ public class NamedQueryTest extends BaseEntityManagerFunctionalTestCase { } ); } + @Test + @TestForIssue(jiraKey = "HHH-11413") + public void testNamedQueryAddedFromTypedNativeQuery() { + doInJPA( this::entityManagerFactory, entityManager -> { + final Query query = entityManager.createNativeQuery( "select g.title from Game g where title = ?", String.class ); + entityManagerFactory().addNamedQuery( "the-query", query ); + + final TypedQuery namedQuery = entityManager.createNamedQuery( "the-query", String.class ); + namedQuery.setParameter( 1, "abc" ); + namedQuery.getResultList(); + } ); + } + @Test @TestForIssue(jiraKey = "HHH-14816") public void testQueryHintLockMode() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryAsNamedTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryAsNamedTests.java index 3d8e5392d9..0a1df96cb0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryAsNamedTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryAsNamedTests.java @@ -13,7 +13,6 @@ import org.hibernate.query.Query; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.SessionFactory; @@ -67,7 +66,6 @@ public class NativeQueryAsNamedTests { * Seems like this should work, but currently does not */ @Test - @FailureExpected( reason = "Session#createNamedQuery for a native-query does not like passing the result-class" ) public void testResultClass(SessionFactoryScope scope) { scope.inTransaction( (session) -> { final NativeQuery nativeQuery = session.createNativeQuery( THE_SELECT, String.class );