17
17
import java .util .function .Consumer ;
18
18
import java .util .function .Supplier ;
19
19
20
+ import org .checkerframework .checker .nullness .qual .Nullable ;
20
21
import org .hibernate .CacheMode ;
21
22
import org .hibernate .FlushMode ;
23
+ import org .hibernate .jpa .spi .NativeQueryConstructorTransformer ;
24
+ import org .hibernate .jpa .spi .NativeQueryListTransformer ;
25
+ import org .hibernate .jpa .spi .NativeQueryMapTransformer ;
22
26
import org .hibernate .metamodel .spi .MappingMetamodelImplementor ;
23
27
import org .hibernate .query .QueryFlushMode ;
24
28
import org .hibernate .HibernateException ;
105
109
import jakarta .persistence .TypedQuery ;
106
110
import jakarta .persistence .metamodel .SingularAttribute ;
107
111
import org .hibernate .type .BasicTypeRegistry ;
112
+ import org .hibernate .type .descriptor .java .JavaType ;
113
+ import org .hibernate .type .descriptor .java .spi .UnknownBasicJavaType ;
108
114
import org .hibernate .type .spi .TypeConfiguration ;
109
115
110
116
import static java .lang .Character .isWhitespace ;
111
117
import static java .util .Collections .addAll ;
118
+ import static org .hibernate .internal .util .ReflectHelper .isClass ;
112
119
import static org .hibernate .internal .util .StringHelper .unqualify ;
113
120
import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
114
121
import static org .hibernate .internal .util .collections .CollectionHelper .isNotEmpty ;
115
122
import static org .hibernate .internal .util .collections .CollectionHelper .makeCopy ;
116
123
import static org .hibernate .jpa .HibernateHints .HINT_NATIVE_LOCK_MODE ;
117
124
import static org .hibernate .query .results .internal .Builders .resultClassBuilder ;
118
125
import static org .hibernate .query .results .ResultSetMapping .resolveResultSetMapping ;
126
+ import static org .hibernate .query .sqm .internal .SqmUtil .isResultTypeAlwaysAllowed ;
119
127
120
128
/**
121
129
* @author Steve Ebersole
@@ -129,6 +137,7 @@ public class NativeQueryImpl<R>
129
137
private final List <ParameterOccurrence > parameterOccurrences ;
130
138
private final QueryParameterBindings parameterBindings ;
131
139
140
+ private final Class <R > resultType ;
132
141
private final ResultSetMapping resultSetMapping ;
133
142
private final boolean resultMappingSuppliedToCtor ;
134
143
@@ -166,6 +175,7 @@ public NativeQueryImpl(
166
175
return false ;
167
176
}
168
177
},
178
+ null ,
169
179
session
170
180
);
171
181
}
@@ -218,26 +228,9 @@ public NativeQueryImpl(
218
228
return false ;
219
229
}
220
230
},
231
+ resultJavaType ,
221
232
session
222
233
);
223
-
224
- if ( resultJavaType == Tuple .class ) {
225
- setTupleTransformer ( new NativeQueryTupleTransformer () );
226
- }
227
- else if ( resultJavaType != null && !resultJavaType .isArray () ) {
228
- switch ( resultSetMapping .getNumberOfResultBuilders () ) {
229
- case 0 :
230
- throw new IllegalArgumentException ( "Named query exists, but did not specify a resultClass" );
231
- case 1 :
232
- final Class <?> actualResultJavaType = resultSetMapping .getResultBuilders ().get ( 0 ).getJavaType ();
233
- if ( actualResultJavaType != null && !resultJavaType .isAssignableFrom ( actualResultJavaType ) ) {
234
- throw buildIncompatibleException ( resultJavaType , actualResultJavaType );
235
- }
236
- break ;
237
- default :
238
- throw new IllegalArgumentException ( "Cannot create TypedQuery for query with more than one return" );
239
- }
240
- }
241
234
}
242
235
243
236
/**
@@ -258,6 +251,7 @@ public NativeQueryImpl(
258
251
mappingMemento .resolve ( resultSetMapping , querySpaceConsumer , context );
259
252
return true ;
260
253
},
254
+ null ,
261
255
session
262
256
);
263
257
@@ -268,6 +262,15 @@ public NativeQueryImpl(
268
262
Supplier <ResultSetMapping > resultSetMappingCreator ,
269
263
ResultSetMappingHandler resultSetMappingHandler ,
270
264
SharedSessionContractImplementor session ) {
265
+ this ( memento , resultSetMappingCreator , resultSetMappingHandler , null , session );
266
+ }
267
+
268
+ public NativeQueryImpl (
269
+ NamedNativeQueryMemento <?> memento ,
270
+ Supplier <ResultSetMapping > resultSetMappingCreator ,
271
+ ResultSetMappingHandler resultSetMappingHandler ,
272
+ @ Nullable Class <R > resultType ,
273
+ SharedSessionContractImplementor session ) {
271
274
super ( session );
272
275
273
276
this .originalSqlString = memento .getOriginalSqlString ();
@@ -279,13 +282,35 @@ public NativeQueryImpl(
279
282
this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
280
283
this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
281
284
this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
285
+ this .resultType = resultType ;
282
286
this .querySpaces = new HashSet <>();
283
287
284
288
this .resultSetMapping = resultSetMappingCreator .get ();
285
289
286
290
this .resultMappingSuppliedToCtor =
287
291
resultSetMappingHandler .resolveResultSetMapping ( resultSetMapping , querySpaces ::add , this );
288
292
293
+ if ( resultType != null ) {
294
+ if ( !isResultTypeAlwaysAllowed ( resultType ) ) {
295
+ switch ( resultSetMapping .getNumberOfResultBuilders () ) {
296
+ case 0 :
297
+ throw new IllegalArgumentException ( "Named query exists, but did not specify a resultClass" );
298
+ case 1 :
299
+ final Class <?> actualResultJavaType = resultSetMapping .getResultBuilders ().get ( 0 )
300
+ .getJavaType ();
301
+ if ( actualResultJavaType != null && !resultType .isAssignableFrom ( actualResultJavaType ) ) {
302
+ throw buildIncompatibleException ( resultType , actualResultJavaType );
303
+ }
304
+ break ;
305
+ default :
306
+ throw new IllegalArgumentException (
307
+ "Cannot create TypedQuery for query with more than one return" );
308
+ }
309
+ }
310
+ else {
311
+ setTupleTransformerForResultType ( resultType );
312
+ }
313
+ }
289
314
applyOptions ( memento );
290
315
}
291
316
@@ -301,6 +326,7 @@ public NativeQueryImpl(
301
326
this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
302
327
this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
303
328
this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
329
+ this .resultType = null ;
304
330
this .querySpaces = new HashSet <>();
305
331
306
332
this .resultSetMapping = buildResultSetMapping ( resultSetMappingMemento .getName (), false , session );
@@ -310,6 +336,10 @@ public NativeQueryImpl(
310
336
}
311
337
312
338
public NativeQueryImpl (String sqlString , SharedSessionContractImplementor session ) {
339
+ this ( sqlString , null , session );
340
+ }
341
+
342
+ public NativeQueryImpl (String sqlString , @ Nullable Class <R > resultType , SharedSessionContractImplementor session ) {
313
343
super ( session );
314
344
315
345
this .querySpaces = new HashSet <>();
@@ -320,11 +350,46 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio
320
350
this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
321
351
this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
322
352
this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
353
+ this .resultType = resultType ;
354
+ if ( resultType != null ) {
355
+ setTupleTransformerForResultType ( resultType );
356
+ }
323
357
324
358
this .resultSetMapping = resolveResultSetMapping ( sqlString , true , session .getFactory () );
325
359
this .resultMappingSuppliedToCtor = false ;
326
360
}
327
361
362
+ protected <T > void setTupleTransformerForResultType (Class <T > resultClass ) {
363
+ final TupleTransformer <?> tupleTransformer = determineTupleTransformerForResultType ( resultClass );
364
+ if ( tupleTransformer != null ) {
365
+ setTupleTransformer ( tupleTransformer );
366
+ }
367
+ }
368
+
369
+ protected @ Nullable TupleTransformer <?> determineTupleTransformerForResultType (Class <?> resultClass ) {
370
+ if ( Tuple .class .equals ( resultClass ) ) {
371
+ return NativeQueryTupleTransformer .INSTANCE ;
372
+ }
373
+ else if ( Map .class .equals ( resultClass ) ) {
374
+ return NativeQueryMapTransformer .INSTANCE ;
375
+ }
376
+ else if ( List .class .equals ( resultClass ) ) {
377
+ return NativeQueryListTransformer .INSTANCE ;
378
+ }
379
+ else if ( resultClass != Object .class && resultClass != Object [].class ) {
380
+ if ( isClass ( resultClass ) && !hasJavaTypeDescriptor ( resultClass ) ) {
381
+ // not a basic type
382
+ return new NativeQueryConstructorTransformer <>( resultClass );
383
+ }
384
+ }
385
+ return null ;
386
+ }
387
+
388
+ private <T > boolean hasJavaTypeDescriptor (Class <T > resultClass ) {
389
+ final JavaType <Object > descriptor = getTypeConfiguration ().getJavaTypeRegistry ().findDescriptor ( resultClass );
390
+ return descriptor != null && descriptor .getClass () != UnknownBasicJavaType .class ;
391
+ }
392
+
328
393
@ FunctionalInterface
329
394
private interface ResultSetMappingHandler {
330
395
boolean resolveResultSetMapping (
@@ -436,11 +501,16 @@ public QueryParameterBindings getParameterBindings() {
436
501
return getQueryParameterBindings ();
437
502
}
438
503
504
+ @ Override
505
+ public Class <R > getResultType () {
506
+ return resultType ;
507
+ }
508
+
439
509
@ Override
440
510
public NamedNativeQueryMemento <?> toMemento (String name ) {
441
511
return new NamedNativeQueryMementoImpl <>(
442
512
name ,
443
- extractResultClass ( resultSetMapping ),
513
+ resultType != null ? resultType : extractResultClass ( resultSetMapping ),
444
514
sqlString ,
445
515
originalSqlString ,
446
516
resultSetMapping .getMappingIdentifier (),
@@ -459,14 +529,14 @@ public NamedNativeQueryMemento<?> toMemento(String name) {
459
529
);
460
530
}
461
531
462
- private Class <? > extractResultClass (ResultSetMapping resultSetMapping ) {
532
+ private Class <R > extractResultClass (ResultSetMapping resultSetMapping ) {
463
533
final List <ResultBuilder > resultBuilders = resultSetMapping .getResultBuilders ();
464
534
if ( resultBuilders .size () == 1 ) {
465
535
final ResultBuilder resultBuilder = resultBuilders .get ( 0 );
466
536
if ( resultBuilder instanceof ImplicitResultClassBuilder
467
537
|| resultBuilder instanceof ImplicitModelPartResultBuilderEntity
468
538
|| resultBuilder instanceof DynamicResultBuilderEntityCalculated ) {
469
- return resultBuilder .getJavaType ();
539
+ return ( Class < R >) resultBuilder .getJavaType ();
470
540
}
471
541
}
472
542
return null ;
@@ -618,13 +688,29 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
618
688
}
619
689
620
690
protected SelectQueryPlan <R > resolveSelectQueryPlan () {
691
+ final ResultSetMapping mapping ;
692
+ if ( resultType != null && resultSetMapping .isDynamic () && resultSetMapping .getNumberOfResultBuilders () == 0 ) {
693
+ mapping = ResultSetMapping .resolveResultSetMapping ( originalSqlString , true , getSessionFactory () );
694
+
695
+ if ( getSessionFactory ().getMappingMetamodel ().isEntityClass ( resultType ) ) {
696
+ mapping .addResultBuilder (
697
+ Builders .entityCalculated ( unqualify ( resultType .getName () ), resultType .getName (),
698
+ LockMode .READ , getSessionFactory () ) );
699
+ }
700
+ else if ( !isResultTypeAlwaysAllowed ( resultType )
701
+ && (!isClass ( resultType ) || hasJavaTypeDescriptor ( resultType )) ) {
702
+ mapping .addResultBuilder ( Builders .resultClassBuilder ( resultType , getSessionFactory () ) );
703
+ }
704
+ }
705
+ else {
706
+ mapping = resultSetMapping ;
707
+ }
621
708
return isCacheableQuery ()
622
- ? getInterpretationCache ()
623
- .resolveSelectQueryPlan ( selectInterpretationsKey (), this ::createQueryPlan )
624
- : createQueryPlan ();
709
+ ? getInterpretationCache ().resolveSelectQueryPlan ( selectInterpretationsKey ( mapping ), () -> createQueryPlan ( mapping ) )
710
+ : createQueryPlan ( mapping );
625
711
}
626
712
627
- private NativeSelectQueryPlan <R > createQueryPlan () {
713
+ private NativeSelectQueryPlan <R > createQueryPlan (ResultSetMapping resultSetMapping ) {
628
714
final NativeSelectQueryDefinition <R > queryDefinition = new NativeSelectQueryDefinition <>() {
629
715
final String sqlString = expandParameterLists ();
630
716
@@ -834,7 +920,7 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL
834
920
return bindValueMaxCount ;
835
921
}
836
922
837
- private SelectInterpretationsKey selectInterpretationsKey () {
923
+ private SelectInterpretationsKey selectInterpretationsKey (ResultSetMapping resultSetMapping ) {
838
924
return new SelectInterpretationsKey (
839
925
getQueryString (),
840
926
resultSetMapping ,
0 commit comments