Skip to content

Commit 0d5b0f7

Browse files
committed
HHH-18629 Fix inconsistent column alias generated while result class is used for placeholder
1 parent 38c7370 commit 0d5b0f7

File tree

3 files changed

+172
-48
lines changed

3 files changed

+172
-48
lines changed

hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.function.Function;
1919

2020
import jakarta.persistence.EntityGraph;
21+
import org.checkerframework.checker.nullness.qual.Nullable;
2122
import org.hibernate.CacheMode;
2223
import org.hibernate.EntityNameResolver;
2324
import org.hibernate.Filter;
@@ -905,22 +906,8 @@ public <R> QueryImplementor<R> createQuery(TypedQueryReference<R> typedQueryRefe
905906
// dynamic native (SQL) query handling
906907

907908
@Override @SuppressWarnings("rawtypes")
908-
public NativeQueryImpl createNativeQuery(String sqlString) {
909-
checkOpen();
910-
pulseTransactionCoordinator();
911-
delayedAfterCompletion();
912-
913-
try {
914-
final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, this );
915-
if ( isEmpty( query.getComment() ) ) {
916-
query.setComment( "dynamic native SQL query" );
917-
}
918-
applyQuerySettingsAndHints( query );
919-
return query;
920-
}
921-
catch (RuntimeException he) {
922-
throw getExceptionConverter().convert( he );
923-
}
909+
public NativeQueryImplementor createNativeQuery(String sqlString) {
910+
return createNativeQuery( sqlString, (Class) null );
924911
}
925912

926913
@Override @SuppressWarnings("rawtypes")
@@ -953,12 +940,28 @@ protected NamedResultSetMappingMemento getResultSetMappingMemento(String resultS
953940
@Override @SuppressWarnings({"rawtypes", "unchecked"})
954941
//note: we're doing something a bit funny here to work around
955942
// the clashing signatures declared by the supertypes
956-
public NativeQueryImplementor createNativeQuery(String sqlString, Class resultClass) {
957-
final NativeQueryImpl query = createNativeQuery( sqlString );
958-
addResultType( resultClass, query );
959-
return query;
943+
public NativeQueryImplementor createNativeQuery(String sqlString, @Nullable Class resultClass) {
944+
checkOpen();
945+
pulseTransactionCoordinator();
946+
delayedAfterCompletion();
947+
948+
try {
949+
final NativeQueryImpl query = new NativeQueryImpl<>( sqlString, resultClass, this );
950+
if ( isEmpty( query.getComment() ) ) {
951+
query.setComment( "dynamic native SQL query" );
952+
}
953+
applyQuerySettingsAndHints( query );
954+
return query;
955+
}
956+
catch (RuntimeException he) {
957+
throw getExceptionConverter().convert( he );
958+
}
960959
}
961960

961+
/**
962+
* @deprecated Use {@link NativeQueryImpl#NativeQueryImpl(String, Class, SharedSessionContractImplementor)} instead
963+
*/
964+
@Deprecated(forRemoval = true)
962965
protected <T> void addResultType(Class<T> resultClass, NativeQueryImplementor<T> query) {
963966
if ( Tuple.class.equals( resultClass ) ) {
964967
query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE );

hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
import java.util.function.Consumer;
1818
import java.util.function.Supplier;
1919

20+
import org.checkerframework.checker.nullness.qual.Nullable;
2021
import org.hibernate.CacheMode;
2122
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;
2226
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
2327
import org.hibernate.query.QueryFlushMode;
2428
import org.hibernate.HibernateException;
@@ -105,10 +109,13 @@
105109
import jakarta.persistence.TypedQuery;
106110
import jakarta.persistence.metamodel.SingularAttribute;
107111
import org.hibernate.type.BasicTypeRegistry;
112+
import org.hibernate.type.descriptor.java.JavaType;
113+
import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType;
108114
import org.hibernate.type.spi.TypeConfiguration;
109115

110116
import static java.lang.Character.isWhitespace;
111117
import static java.util.Collections.addAll;
118+
import static org.hibernate.internal.util.ReflectHelper.isClass;
112119
import static org.hibernate.internal.util.StringHelper.unqualify;
113120
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
114121
import 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

Comments
 (0)