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 51ebd4aff58f..8bce0c82c3ac 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 @@ -328,24 +328,35 @@ private void handleExplicitResultSetMapping() { setTupleTransformerForResultType( resultType ); } else { - checkResultType( resultType ); + checkResultType( resultType, resultSetMapping ); } } } - private void checkResultType(Class resultType) { - switch ( resultSetMapping.getNumberOfResultBuilders() ) { - case 0: - throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); - case 1: - final Class actualResultJavaType = - resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); - if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { - throw buildIncompatibleException( resultType, actualResultJavaType ); - } - break; - default: - throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" ); + private void checkResultType(Class resultType, ResultSetMapping resultSetMapping) { + // resultType can be null if any of the deprecated methods were used to create the query + if ( resultType != null && !isResultTypeAlwaysAllowed( resultType )) { + switch ( resultSetMapping.getNumberOfResultBuilders() ) { + case 0: + if ( !resultSetMapping.isDynamic() ) { + throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); + } + break; + case 1: + final Class actualResultJavaType = + resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); + if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { + throw buildIncompatibleException( resultType, actualResultJavaType ); + } + break; + default: + for ( ResultBuilder resultBuilder : resultSetMapping.getResultBuilders() ) { + final Class rbJavaType = resultBuilder.getJavaType(); + if ( !resultType.isAssignableFrom( rbJavaType ) ) { + throw new IllegalArgumentException( "The result set mapping of the typed query doesn't match the declared return type" ); + } + } + } } } @@ -716,6 +727,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType ) else { mapping = resultSetMapping; } + checkResultType( resultType, mapping ); return isCacheableQuery() ? getInterpretationCache() .resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java index 0573b5f82f1e..c6c7bcf58c9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/hand/query/NativeSQLQueriesTest.java @@ -38,6 +38,8 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.Query; import org.hibernate.query.ResultListTransformer; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.JiraGroup; import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.Transformers; import org.hibernate.type.StandardBasicTypes; @@ -59,6 +61,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -938,6 +941,32 @@ public void testAliasToBeanMap(SessionFactoryScope scope) { ); } + @Test + @JiraGroup( + { + @Jira("https://hibernate.atlassian.net/browse/HHH-19376"), + @Jira("https://hibernate.atlassian.net/browse/HHH-19383") + } + ) + public void testMutateResultSetMapping(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + String sql = "SELECT p.*, COUNT(*) OVER() AS total_count " + + "FROM Person p " + + "WHERE p.name ILIKE :name " + + "ORDER BY p.id"; + // Declare Person as result type + NativeQuery query = session.createNativeQuery(sql, Person.class); + query.setParameter("name", "Ja%"); + query.setMaxResults(2); + query.setFirstResult(0); + // Now mutate the result set mapping and verify an Exception is thrown + assertThrows( IllegalArgumentException.class, + () -> query.addScalar( "total_count", StandardBasicTypes.LONG).list() ); + } + ); + } + private String buildLongString(int size, char baseChar) { StringBuilder buff = new StringBuilder(); for( int i = 0; i < size; i++ ) {