|
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