HHH-16077 - Added named native queries cannot specify result-class

This commit is contained in:
Steve Ebersole 2023-01-23 20:26:49 -06:00
parent 4a37bf8017
commit e7b2f9b121
7 changed files with 58 additions and 18 deletions

View File

@ -858,7 +858,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
}
else if ( resultClass != Object.class && resultClass != Object[].class ) {
query.addScalar( 1, resultClass );
query.addResultTypeClass( resultClass );
}
return query;
}

View File

@ -246,8 +246,13 @@ public class Builders {
public static ResultBuilder resultClassBuilder(
Class<?> resultMappingClass,
ResultSetMappingResolutionContext resolutionContext) {
final MappingMetamodelImplementor mappingMetamodel = resolutionContext
.getSessionFactory()
return resultClassBuilder( resultMappingClass, resolutionContext.getSessionFactory() );
}
public static ResultBuilder resultClassBuilder(
Class<?> resultMappingClass,
SessionFactoryImplementor sessionFactory) {
final MappingMetamodelImplementor mappingMetamodel = sessionFactory
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass );

View File

@ -91,6 +91,9 @@ public class ResultSetMappingImpl implements ResultSetMapping {
}
public List<ResultBuilder> getResultBuilders() {
if ( resultBuilders == null ) {
return Collections.emptyList();
}
return Collections.unmodifiableList( resultBuilders );
}

View File

@ -12,6 +12,7 @@ import java.util.Set;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.named.AbstractNamedQueryMemento;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sql.spi.NamedNativeQueryMemento;
@ -127,7 +128,7 @@ public class NamedNativeQueryMementoImpl extends AbstractNamedQueryMemento imple
originalSqlString,
resultSetMappingName,
resultSetMappingClass,
querySpaces,
CollectionHelper.makeCopy( querySpaces ),
getCacheable(),
getCacheRegion(),
getCacheMode(),

View File

@ -61,6 +61,8 @@ import org.hibernate.query.results.ResultSetMappingImpl;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.query.results.dynamic.DynamicResultBuilderEntityStandard;
import org.hibernate.query.results.dynamic.DynamicResultBuilderInstantiation;
import org.hibernate.query.results.implicit.ImplicitModelPartResultBuilderEntity;
import org.hibernate.query.results.implicit.ImplicitResultClassBuilder;
import org.hibernate.query.spi.AbstractQuery;
import org.hibernate.query.spi.DomainQueryExecutionContext;
import org.hibernate.query.spi.MutableQueryOptions;
@ -246,12 +248,6 @@ public class NativeQueryImpl<R>
return true;
}
if ( resultJavaType != null && resultJavaType != Tuple.class && !resultJavaType.isArray() ) {
// todo : allow the expected Java type imply a builder to use
// resultSetMapping.addResultBuilder( resultClassBuilder( resultJavaType, context ) );
// return true;
}
return false;
},
session
@ -260,22 +256,24 @@ public class NativeQueryImpl<R>
if ( resultJavaType == Tuple.class ) {
setTupleTransformer( new NativeQueryTupleTransformer() );
}
else if ( resultJavaType != null && resultJavaType != Object[].class ) {
else if ( resultJavaType != null && !resultJavaType.isArray() ) {
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
case 0:
case 0: {
throw new IllegalArgumentException( "Named query exists, but did not specify a resultClass" );
case 1:
// would be nice to support types that are "wrappable", as in `JavaType#wrap`
}
case 1: {
final Class<?> actualResultJavaType = resultSetMapping.getResultBuilders().get( 0 ).getJavaType();
if ( actualResultJavaType != null && !resultJavaType.isAssignableFrom( actualResultJavaType ) ) {
throw buildIncompatibleException( resultJavaType, actualResultJavaType );
}
break;
default:
}
default: {
throw new IllegalArgumentException( "Cannot create TypedQuery for query with more than one return" );
}
}
}
}
/**
* Constructs a NativeQueryImpl given a sql query defined in the mappings.
@ -449,7 +447,7 @@ public class NativeQueryImpl<R>
sqlString,
originalSqlString,
resultSetMapping.getMappingIdentifier(),
null,
extractResultClass( resultSetMapping ),
querySpaces,
isCacheable(),
getCacheRegion(),
@ -465,6 +463,22 @@ public class NativeQueryImpl<R>
);
}
private Class<?> extractResultClass(ResultSetMappingImpl resultSetMapping) {
final List<ResultBuilder> resultBuilders = resultSetMapping.getResultBuilders();
if ( resultBuilders.size() == 1 ) {
final ResultBuilder resultBuilder = resultBuilders.get( 0 );
if ( resultBuilder instanceof ImplicitResultClassBuilder ) {
final ImplicitResultClassBuilder resultTypeBuilder = (ImplicitResultClassBuilder) resultBuilder;
return resultTypeBuilder.getJavaType();
}
else if ( resultBuilder instanceof ImplicitModelPartResultBuilderEntity ) {
final ImplicitModelPartResultBuilderEntity resultTypeBuilder = (ImplicitModelPartResultBuilderEntity) resultBuilder;
return resultTypeBuilder.getJavaType();
}
}
return null;
}
@Override
public LockModeType getLockMode() {
// the JPA spec requires IllegalStateException here, even
@ -841,6 +855,11 @@ public class NativeQueryImpl<R>
: null;
}
public void addResultTypeClass(Class<?> resultClass) {
assert CollectionHelper.isEmpty( resultSetMapping.getResultBuilders() );
registerBuilder( Builders.resultClassBuilder( resultClass, getSessionFactory() ) );
}
@Override
public NativeQueryImplementor<R> addScalar(String columnAlias) {
return registerBuilder( Builders.scalar( columnAlias ) );

View File

@ -26,6 +26,7 @@ import jakarta.persistence.NamedNativeQuery;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@ -195,6 +196,19 @@ public class NamedQueryTest extends BaseEntityManagerFunctionalTestCase {
} );
}
@Test
@TestForIssue(jiraKey = "HHH-11413")
public void testNamedQueryAddedFromTypedNativeQuery() {
doInJPA( this::entityManagerFactory, entityManager -> {
final Query query = entityManager.createNativeQuery( "select g.title from Game g where title = ?", String.class );
entityManagerFactory().addNamedQuery( "the-query", query );
final TypedQuery<String> namedQuery = entityManager.createNamedQuery( "the-query", String.class );
namedQuery.setParameter( 1, "abc" );
namedQuery.getResultList();
} );
}
@Test
@TestForIssue(jiraKey = "HHH-14816")
public void testQueryHintLockMode() {

View File

@ -13,7 +13,6 @@ import org.hibernate.query.Query;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -67,7 +66,6 @@ public class NativeQueryAsNamedTests {
* Seems like this should work, but currently does not
*/
@Test
@FailureExpected( reason = "Session#createNamedQuery for a native-query does not like passing the result-class" )
public void testResultClass(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final NativeQuery<String> nativeQuery = session.createNativeQuery( THE_SELECT, String.class );