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