1717import java .util .function .Consumer ;
1818import java .util .function .Supplier ;
1919
20+ import org .checkerframework .checker .nullness .qual .Nullable ;
2021import org .hibernate .CacheMode ;
2122import org .hibernate .FlushMode ;
23+ import org .hibernate .jpa .spi .NativeQueryConstructorTransformer ;
24+ import org .hibernate .jpa .spi .NativeQueryListTransformer ;
25+ import org .hibernate .jpa .spi .NativeQueryMapTransformer ;
2226import org .hibernate .metamodel .spi .MappingMetamodelImplementor ;
2327import org .hibernate .query .QueryFlushMode ;
2428import org .hibernate .HibernateException ;
105109import jakarta .persistence .TypedQuery ;
106110import jakarta .persistence .metamodel .SingularAttribute ;
107111import org .hibernate .type .BasicTypeRegistry ;
112+ import org .hibernate .type .descriptor .java .JavaType ;
113+ import org .hibernate .type .descriptor .java .spi .UnknownBasicJavaType ;
108114import org .hibernate .type .spi .TypeConfiguration ;
109115
110116import static java .lang .Character .isWhitespace ;
111117import static java .util .Collections .addAll ;
118+ import static org .hibernate .internal .util .ReflectHelper .isClass ;
112119import static org .hibernate .internal .util .StringHelper .unqualify ;
113120import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
114121import static org .hibernate .internal .util .collections .CollectionHelper .isNotEmpty ;
@@ -129,6 +136,7 @@ public class NativeQueryImpl<R>
129136 private final List <ParameterOccurrence > parameterOccurrences ;
130137 private final QueryParameterBindings parameterBindings ;
131138
139+ private final Class <R > resultType ;
132140 private final ResultSetMapping resultSetMapping ;
133141 private final boolean resultMappingSuppliedToCtor ;
134142
@@ -166,6 +174,7 @@ public NativeQueryImpl(
166174 return false ;
167175 }
168176 },
177+ null ,
169178 session
170179 );
171180 }
@@ -218,26 +227,9 @@ public NativeQueryImpl(
218227 return false ;
219228 }
220229 },
230+ resultJavaType ,
221231 session
222232 );
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- }
241233 }
242234
243235 /**
@@ -258,6 +250,7 @@ public NativeQueryImpl(
258250 mappingMemento .resolve ( resultSetMapping , querySpaceConsumer , context );
259251 return true ;
260252 },
253+ null ,
261254 session
262255 );
263256
@@ -268,6 +261,15 @@ public NativeQueryImpl(
268261 Supplier <ResultSetMapping > resultSetMappingCreator ,
269262 ResultSetMappingHandler resultSetMappingHandler ,
270263 SharedSessionContractImplementor session ) {
264+ this ( memento , resultSetMappingCreator , resultSetMappingHandler , null , session );
265+ }
266+
267+ public NativeQueryImpl (
268+ NamedNativeQueryMemento <?> memento ,
269+ Supplier <ResultSetMapping > resultSetMappingCreator ,
270+ ResultSetMappingHandler resultSetMappingHandler ,
271+ @ Nullable Class <R > resultType ,
272+ SharedSessionContractImplementor session ) {
271273 super ( session );
272274
273275 this .originalSqlString = memento .getOriginalSqlString ();
@@ -279,13 +281,33 @@ public NativeQueryImpl(
279281 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
280282 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
281283 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
284+ this .resultType = resultType ;
282285 this .querySpaces = new HashSet <>();
283286
284287 this .resultSetMapping = resultSetMappingCreator .get ();
285288
286289 this .resultMappingSuppliedToCtor =
287290 resultSetMappingHandler .resolveResultSetMapping ( resultSetMapping , querySpaces ::add , this );
288291
292+ if ( resultType != null ) {
293+ if ( !resultType .isArray () ) {
294+ switch ( resultSetMapping .getNumberOfResultBuilders () ) {
295+ case 0 :
296+ throw new IllegalArgumentException ( "Named query exists, but did not specify a resultClass" );
297+ case 1 :
298+ final Class <?> actualResultJavaType = resultSetMapping .getResultBuilders ().get ( 0 )
299+ .getJavaType ();
300+ if ( actualResultJavaType != null && !resultType .isAssignableFrom ( actualResultJavaType ) ) {
301+ throw buildIncompatibleException ( resultType , actualResultJavaType );
302+ }
303+ break ;
304+ default :
305+ throw new IllegalArgumentException (
306+ "Cannot create TypedQuery for query with more than one return" );
307+ }
308+ }
309+ setTupleTransformerForResultType ( resultType );
310+ }
289311 applyOptions ( memento );
290312 }
291313
@@ -301,6 +323,7 @@ public NativeQueryImpl(
301323 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
302324 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
303325 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
326+ this .resultType = null ;
304327 this .querySpaces = new HashSet <>();
305328
306329 this .resultSetMapping = buildResultSetMapping ( resultSetMappingMemento .getName (), false , session );
@@ -310,6 +333,10 @@ public NativeQueryImpl(
310333 }
311334
312335 public NativeQueryImpl (String sqlString , SharedSessionContractImplementor session ) {
336+ this ( sqlString , null , session );
337+ }
338+
339+ public NativeQueryImpl (String sqlString , @ Nullable Class <R > resultType , SharedSessionContractImplementor session ) {
313340 super ( session );
314341
315342 this .querySpaces = new HashSet <>();
@@ -320,11 +347,38 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio
320347 this .parameterMetadata = parameterInterpretation .toParameterMetadata ( session );
321348 this .parameterOccurrences = parameterInterpretation .getOrderedParameterOccurrences ();
322349 this .parameterBindings = parameterMetadata .createBindings ( session .getFactory () );
350+ this .resultType = resultType ;
351+ if ( resultType != null ) {
352+ setTupleTransformerForResultType ( resultType );
353+ }
323354
324355 this .resultSetMapping = resolveResultSetMapping ( sqlString , true , session .getFactory () );
325356 this .resultMappingSuppliedToCtor = false ;
326357 }
327358
359+ protected <T > void setTupleTransformerForResultType (Class <T > resultClass ) {
360+ if ( Tuple .class .equals ( resultClass ) ) {
361+ setTupleTransformer ( NativeQueryTupleTransformer .INSTANCE );
362+ }
363+ else if ( Map .class .equals ( resultClass ) ) {
364+ setTupleTransformer ( NativeQueryMapTransformer .INSTANCE );
365+ }
366+ else if ( List .class .equals ( resultClass ) ) {
367+ setTupleTransformer ( NativeQueryListTransformer .INSTANCE );
368+ }
369+ else if ( resultClass != Object .class && resultClass != Object [].class ) {
370+ if ( isClass ( resultClass ) && !hasJavaTypeDescriptor ( resultClass ) ) {
371+ // not a basic type
372+ setTupleTransformer ( new NativeQueryConstructorTransformer <>( resultClass ) );
373+ }
374+ }
375+ }
376+
377+ private <T > boolean hasJavaTypeDescriptor (Class <T > resultClass ) {
378+ final JavaType <Object > descriptor = getTypeConfiguration ().getJavaTypeRegistry ().findDescriptor ( resultClass );
379+ return descriptor != null && descriptor .getClass () != UnknownBasicJavaType .class ;
380+ }
381+
328382 @ FunctionalInterface
329383 private interface ResultSetMappingHandler {
330384 boolean resolveResultSetMapping (
@@ -436,11 +490,16 @@ public QueryParameterBindings getParameterBindings() {
436490 return getQueryParameterBindings ();
437491 }
438492
493+ @ Override
494+ public Class <R > getResultType () {
495+ return resultType ;
496+ }
497+
439498 @ Override
440499 public NamedNativeQueryMemento <?> toMemento (String name ) {
441500 return new NamedNativeQueryMementoImpl <>(
442501 name ,
443- extractResultClass ( resultSetMapping ),
502+ resultType != null ? resultType : extractResultClass ( resultSetMapping ),
444503 sqlString ,
445504 originalSqlString ,
446505 resultSetMapping .getMappingIdentifier (),
@@ -459,14 +518,14 @@ public NamedNativeQueryMemento<?> toMemento(String name) {
459518 );
460519 }
461520
462- private Class <? > extractResultClass (ResultSetMapping resultSetMapping ) {
521+ private Class <R > extractResultClass (ResultSetMapping resultSetMapping ) {
463522 final List <ResultBuilder > resultBuilders = resultSetMapping .getResultBuilders ();
464523 if ( resultBuilders .size () == 1 ) {
465524 final ResultBuilder resultBuilder = resultBuilders .get ( 0 );
466525 if ( resultBuilder instanceof ImplicitResultClassBuilder
467526 || resultBuilder instanceof ImplicitModelPartResultBuilderEntity
468527 || resultBuilder instanceof DynamicResultBuilderEntityCalculated ) {
469- return resultBuilder .getJavaType ();
528+ return ( Class < R >) resultBuilder .getJavaType ();
470529 }
471530 }
472531 return null ;
@@ -618,13 +677,28 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
618677 }
619678
620679 protected SelectQueryPlan <R > resolveSelectQueryPlan () {
680+ final ResultSetMapping mapping ;
681+ if ( resultType != null && resultSetMapping .isDynamic () && resultSetMapping .getNumberOfResultBuilders () == 0 ) {
682+ mapping = ResultSetMapping .resolveResultSetMapping ( originalSqlString , true , getSessionFactory () );
683+
684+ if ( getSessionFactory ().getMappingMetamodel ().isEntityClass ( resultType ) ) {
685+ mapping .addResultBuilder (
686+ Builders .entityCalculated ( unqualify ( resultType .getName () ), resultType .getName (),
687+ LockMode .READ , getSessionFactory () ) );
688+ }
689+ else {
690+ mapping .addResultBuilder ( Builders .resultClassBuilder ( resultType , getSessionFactory () ) );
691+ }
692+ }
693+ else {
694+ mapping = resultSetMapping ;
695+ }
621696 return isCacheableQuery ()
622- ? getInterpretationCache ()
623- .resolveSelectQueryPlan ( selectInterpretationsKey (), this ::createQueryPlan )
624- : createQueryPlan ();
697+ ? getInterpretationCache ().resolveSelectQueryPlan ( selectInterpretationsKey (), () -> createQueryPlan ( mapping ) )
698+ : createQueryPlan ( mapping );
625699 }
626700
627- private NativeSelectQueryPlan <R > createQueryPlan () {
701+ private NativeSelectQueryPlan <R > createQueryPlan (ResultSetMapping resultSetMapping ) {
628702 final NativeSelectQueryDefinition <R > queryDefinition = new NativeSelectQueryDefinition <>() {
629703 final String sqlString = expandParameterLists ();
630704
0 commit comments