Skip to content

Commit c5f5e10

Browse files
committed
HHH-18629 Fix inconsistent column alias generated while result class is used for placeholder
1 parent 800a3f0 commit c5f5e10

File tree

4 files changed

+185
-54
lines changed

4 files changed

+185
-54
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: 112 additions & 26 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,17 +109,21 @@
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;
115122
import static org.hibernate.internal.util.collections.CollectionHelper.makeCopy;
116123
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
117124
import static org.hibernate.query.results.internal.Builders.resultClassBuilder;
118125
import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping;
126+
import static org.hibernate.query.sqm.internal.SqmUtil.isResultTypeAlwaysAllowed;
119127

120128
/**
121129
* @author Steve Ebersole
@@ -129,6 +137,7 @@ public class NativeQueryImpl<R>
129137
private final List<ParameterOccurrence> parameterOccurrences;
130138
private final QueryParameterBindings parameterBindings;
131139

140+
private final Class<R> resultType;
132141
private final ResultSetMapping resultSetMapping;
133142
private final boolean resultMappingSuppliedToCtor;
134143

@@ -166,6 +175,7 @@ public NativeQueryImpl(
166175
return false;
167176
}
168177
},
178+
null,
169179
session
170180
);
171181
}
@@ -218,26 +228,9 @@ public NativeQueryImpl(
218228
return false;
219229
}
220230
},
231+
resultJavaType,
221232
session
222233
);
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-
}
241234
}
242235

243236
/**
@@ -258,6 +251,7 @@ public NativeQueryImpl(
258251
mappingMemento.resolve( resultSetMapping, querySpaceConsumer, context );
259252
return true;
260253
},
254+
null,
261255
session
262256
);
263257

@@ -268,6 +262,15 @@ public NativeQueryImpl(
268262
Supplier<ResultSetMapping> resultSetMappingCreator,
269263
ResultSetMappingHandler resultSetMappingHandler,
270264
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) {
271274
super( session );
272275

273276
this.originalSqlString = memento.getOriginalSqlString();
@@ -279,13 +282,35 @@ public NativeQueryImpl(
279282
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
280283
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
281284
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
285+
this.resultType = resultType;
282286
this.querySpaces = new HashSet<>();
283287

284288
this.resultSetMapping = resultSetMappingCreator.get();
285289

286290
this.resultMappingSuppliedToCtor =
287291
resultSetMappingHandler.resolveResultSetMapping( resultSetMapping, querySpaces::add, this );
288292

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+
}
289314
applyOptions( memento );
290315
}
291316

@@ -301,6 +326,7 @@ public NativeQueryImpl(
301326
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
302327
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
303328
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
329+
this.resultType = null;
304330
this.querySpaces = new HashSet<>();
305331

306332
this.resultSetMapping = buildResultSetMapping( resultSetMappingMemento.getName(), false, session );
@@ -310,6 +336,10 @@ public NativeQueryImpl(
310336
}
311337

312338
public NativeQueryImpl(String sqlString, SharedSessionContractImplementor session) {
339+
this( sqlString, null, session );
340+
}
341+
342+
public NativeQueryImpl(String sqlString, @Nullable Class<R> resultType, SharedSessionContractImplementor session) {
313343
super( session );
314344

315345
this.querySpaces = new HashSet<>();
@@ -320,11 +350,46 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio
320350
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
321351
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
322352
this.parameterBindings = parameterMetadata.createBindings( session.getFactory() );
353+
this.resultType = resultType;
354+
if ( resultType != null ) {
355+
setTupleTransformerForResultType( resultType );
356+
}
323357

324358
this.resultSetMapping = resolveResultSetMapping( sqlString, true, session.getFactory() );
325359
this.resultMappingSuppliedToCtor = false;
326360
}
327361

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+
328393
@FunctionalInterface
329394
private interface ResultSetMappingHandler {
330395
boolean resolveResultSetMapping(
@@ -436,11 +501,16 @@ public QueryParameterBindings getParameterBindings() {
436501
return getQueryParameterBindings();
437502
}
438503

504+
@Override
505+
public Class<R> getResultType() {
506+
return resultType;
507+
}
508+
439509
@Override
440510
public NamedNativeQueryMemento<?> toMemento(String name) {
441511
return new NamedNativeQueryMementoImpl<>(
442512
name,
443-
extractResultClass( resultSetMapping ),
513+
resultType != null ? resultType : extractResultClass( resultSetMapping ),
444514
sqlString,
445515
originalSqlString,
446516
resultSetMapping.getMappingIdentifier(),
@@ -459,14 +529,14 @@ public NamedNativeQueryMemento<?> toMemento(String name) {
459529
);
460530
}
461531

462-
private Class<?> extractResultClass(ResultSetMapping resultSetMapping) {
532+
private Class<R> extractResultClass(ResultSetMapping resultSetMapping) {
463533
final List<ResultBuilder> resultBuilders = resultSetMapping.getResultBuilders();
464534
if ( resultBuilders.size() == 1 ) {
465535
final ResultBuilder resultBuilder = resultBuilders.get( 0 );
466536
if ( resultBuilder instanceof ImplicitResultClassBuilder
467537
|| resultBuilder instanceof ImplicitModelPartResultBuilderEntity
468538
|| resultBuilder instanceof DynamicResultBuilderEntityCalculated ) {
469-
return resultBuilder.getJavaType();
539+
return (Class<R>) resultBuilder.getJavaType();
470540
}
471541
}
472542
return null;
@@ -618,13 +688,29 @@ public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
618688
}
619689

620690
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+
}
621708
return isCacheableQuery()
622-
? getInterpretationCache()
623-
.resolveSelectQueryPlan( selectInterpretationsKey(), this::createQueryPlan )
624-
: createQueryPlan();
709+
? getInterpretationCache().resolveSelectQueryPlan( selectInterpretationsKey( mapping ), () -> createQueryPlan( mapping ) )
710+
: createQueryPlan( mapping );
625711
}
626712

627-
private NativeSelectQueryPlan<R> createQueryPlan() {
713+
private NativeSelectQueryPlan<R> createQueryPlan(ResultSetMapping resultSetMapping) {
628714
final NativeSelectQueryDefinition<R> queryDefinition = new NativeSelectQueryDefinition<>() {
629715
final String sqlString = expandParameterLists();
630716

@@ -834,7 +920,7 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL
834920
return bindValueMaxCount;
835921
}
836922

837-
private SelectInterpretationsKey selectInterpretationsKey() {
923+
private SelectInterpretationsKey selectInterpretationsKey(ResultSetMapping resultSetMapping) {
838924
return new SelectInterpretationsKey(
839925
getQueryString(),
840926
resultSetMapping,

0 commit comments

Comments
 (0)