|
5 | 5 | package org.hibernate.query.sql.internal; |
6 | 6 |
|
7 | 7 | import java.io.Serializable; |
| 8 | +import java.lang.reflect.Constructor; |
8 | 9 | import java.time.Instant; |
9 | 10 | import java.util.Calendar; |
10 | 11 | import java.util.Collection; |
|
126 | 127 | import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; |
127 | 128 | import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty; |
128 | 129 | import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy; |
| 130 | +import static org.hibernate.internal.util.type.PrimitiveWrapperHelper.getDescriptorByPrimitiveType; |
129 | 131 | import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE; |
130 | 132 | import static org.hibernate.query.results.internal.Builders.resultClassBuilder; |
131 | 133 | import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping; |
@@ -331,25 +333,54 @@ private void handleExplicitResultSetMapping() { |
331 | 333 | setTupleTransformerForResultType( resultType ); |
332 | 334 | } |
333 | 335 | else { |
334 | | - checkResultType( resultType ); |
| 336 | + checkResultType( resultType, resultSetMapping ); |
335 | 337 | } |
336 | 338 | } |
337 | 339 | } |
338 | 340 |
|
339 | | - private void checkResultType(Class<R> resultType) { |
340 | | - switch ( resultSetMapping.getNumberOfResultBuilders() ) { |
341 | | - case 0: |
342 | | - throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); |
343 | | - case 1: |
344 | | - final Class<?> actualResultJavaType = |
345 | | - resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); |
346 | | - if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { |
347 | | - throw buildIncompatibleException( resultType, actualResultJavaType ); |
348 | | - } |
349 | | - break; |
350 | | - default: |
351 | | - throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" ); |
| 341 | + private void checkResultType(Class<R> resultType, ResultSetMapping resultSetMapping) { |
| 342 | + // resultType can be null if any of the deprecated methods were used to create the query |
| 343 | + if ( resultType != null && !isResultTypeAlwaysAllowed( resultType )) { |
| 344 | + switch ( resultSetMapping.getNumberOfResultBuilders() ) { |
| 345 | + case 0: |
| 346 | + if ( !resultSetMapping.isDynamic() ) { |
| 347 | + throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" ); |
| 348 | + } |
| 349 | + break; |
| 350 | + case 1: |
| 351 | + final Class<?> actualResultJavaType = |
| 352 | + resultSetMapping.getResultBuilders().get( 0 ).getJavaType(); |
| 353 | + if ( actualResultJavaType != null && !resultType.isAssignableFrom( actualResultJavaType ) ) { |
| 354 | + throw buildIncompatibleException( resultType, actualResultJavaType ); |
| 355 | + } |
| 356 | + break; |
| 357 | + default: |
| 358 | + // The return type has to be a class with an appropriate constructor, i.e. one whose parameter types match |
| 359 | + // the types of the result builders. If none such constructor is found, throw an IAE |
| 360 | + if ( !validConstructorFoundForResultType( resultType, resultSetMapping ) ) { |
| 361 | + throw new IllegalArgumentException( "The declared return type for a multi-valued result set mapping should be Object[], Map, List, or Tuple" ); |
| 362 | + } |
| 363 | + } |
| 364 | + } |
| 365 | + } |
| 366 | + |
| 367 | + private boolean validConstructorFoundForResultType(Class<R> resultType, ResultSetMapping resultSetMapping) { |
| 368 | + // Only 1 constructor with the right number of parameters is allowed (see NativeQueryConstructorTransformer) |
| 369 | + Constructor<?> constructor = resultType.getConstructors()[0]; |
| 370 | + if ( constructor.getParameterCount() != resultSetMapping.getNumberOfResultBuilders() ) { |
| 371 | + return false; |
| 372 | + } |
| 373 | + final List<ResultBuilder> resultBuilders = resultSetMapping.getResultBuilders(); |
| 374 | + Class<?>[] paramTypes = constructor.getParameterTypes(); |
| 375 | + for ( int i = 0; i < resultBuilders.size(); i++ ) { |
| 376 | + if ( |
| 377 | + resultBuilders.get( i ).getJavaType() != ( paramTypes[i].isPrimitive() ? |
| 378 | + getDescriptorByPrimitiveType(paramTypes[i] ).getWrapperClass() : |
| 379 | + paramTypes[i]) ) { |
| 380 | + return false; |
| 381 | + } |
352 | 382 | } |
| 383 | + return true; |
353 | 384 | } |
354 | 385 |
|
355 | 386 | protected <T> void setTupleTransformerForResultType(Class<T> resultClass) { |
@@ -747,6 +778,7 @@ else if ( !isResultTypeAlwaysAllowed( resultType ) |
747 | 778 | else { |
748 | 779 | mapping = resultSetMapping; |
749 | 780 | } |
| 781 | + checkResultType( resultType, mapping ); |
750 | 782 | return isCacheableQuery() |
751 | 783 | ? getInterpretationCache() |
752 | 784 | .resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) ) |
|
0 commit comments