HHH-15930 - Support scalar resultClass in @NamedNativeQuery
This commit is contained in:
parent
501d3869d4
commit
263768d5c5
|
@ -11,6 +11,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.ColumnResult;
|
||||
import jakarta.persistence.ConstructorResult;
|
||||
|
@ -41,7 +42,8 @@ import jakarta.persistence.Version;
|
|||
name = "find_person_name",
|
||||
query =
|
||||
"SELECT name " +
|
||||
"FROM Person "
|
||||
"FROM Person ",
|
||||
resultClass = String.class
|
||||
)
|
||||
//end::sql-scalar-NamedNativeQuery-example[]
|
||||
//tag::sql-multiple-scalar-values-NamedNativeQuery-example[]
|
||||
|
|
|
@ -9,14 +9,13 @@ package org.hibernate.userguide.sql;
|
|||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException;
|
||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
import org.hibernate.transform.Transformers;
|
||||
|
@ -38,6 +37,9 @@ import org.hibernate.testing.TestForIssue;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -586,7 +588,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-scalar-named-query-example[]
|
||||
List<String> names = entityManager.createNamedQuery(
|
||||
"find_person_name")
|
||||
"find_person_name", String.class)
|
||||
.getResultList();
|
||||
//end::sql-jpa-scalar-named-query-example[]
|
||||
assertEquals(3, names.size());
|
||||
|
@ -611,7 +613,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-multiple-scalar-values-named-query-example[]
|
||||
List<Object[]> tuples = entityManager.createNamedQuery(
|
||||
"find_person_name_and_nickName")
|
||||
"find_person_name_and_nickName", Object[].class)
|
||||
.getResultList();
|
||||
|
||||
for(Object[] tuple : tuples) {
|
||||
|
@ -646,10 +648,12 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-multiple-scalar-values-dto-named-query-example[]
|
||||
List<PersonNames> personNames = entityManager.createNamedQuery(
|
||||
"find_person_name_and_nickName_dto")
|
||||
"find_person_name_and_nickName_dto", PersonNames.class)
|
||||
.getResultList();
|
||||
//end::sql-jpa-multiple-scalar-values-dto-named-query-example[]
|
||||
assertEquals(3, personNames.size());
|
||||
assertThat( personNames.get(0) ).isNotNull();
|
||||
assertThat( personNames.get(0) ).isInstanceOf(PersonNames.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -686,11 +690,13 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-entity-named-query-example[]
|
||||
List<Person> persons = entityManager.createNamedQuery(
|
||||
"find_person_by_name")
|
||||
"find_person_by_name", Person.class)
|
||||
.setParameter("name", "J%")
|
||||
.getResultList();
|
||||
//end::sql-jpa-entity-named-query-example[]
|
||||
assertEquals(1, persons.size());
|
||||
assertThat( persons ).hasSize( 1 );
|
||||
assertThat( persons.get( 0 ) ).isInstanceOf( Person.class );
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -716,7 +722,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-entity-associations_named-query-example[]
|
||||
List<Object[]> tuples = entityManager.createNamedQuery(
|
||||
"find_person_with_phones_by_name")
|
||||
"find_person_with_phones_by_name", Object[].class)
|
||||
.setParameter("name", "J%")
|
||||
.getResultList();
|
||||
|
||||
|
@ -726,6 +732,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
}
|
||||
//end::sql-jpa-entity-associations_named-query-example[]
|
||||
assertEquals(1, tuples.size());
|
||||
assertThat( tuples.get(0).getClass().isArray() ).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -756,7 +763,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
//tag::sql-jpa-composite-key-entity-associations_named-query-example[]
|
||||
List<Object[]> tuples = entityManager.createNamedQuery(
|
||||
"find_all_spaceships")
|
||||
"find_all_spaceships", Object[].class)
|
||||
.getResultList();
|
||||
|
||||
for(Object[] tuple : tuples) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping;
|
|||
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
|
||||
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.NativeQuery;
|
||||
import org.hibernate.query.internal.ResultSetMappingResolutionContext;
|
||||
|
@ -40,6 +41,7 @@ import org.hibernate.query.results.implicit.ImplicitFetchBuilderEntity;
|
|||
import org.hibernate.query.results.implicit.ImplicitFetchBuilderEntityPart;
|
||||
import org.hibernate.query.results.implicit.ImplicitFetchBuilderPlural;
|
||||
import org.hibernate.query.results.implicit.ImplicitModelPartResultBuilderEntity;
|
||||
import org.hibernate.query.results.implicit.ImplicitResultClassBuilder;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
|
@ -241,14 +243,23 @@ public class Builders {
|
|||
return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, joinPropertyName, new ArrayList<>(), new HashMap<>() );
|
||||
}
|
||||
|
||||
public static ResultBuilder implicitEntityResultBuilder(
|
||||
public static ResultBuilder resultClassBuilder(
|
||||
Class<?> resultMappingClass,
|
||||
ResultSetMappingResolutionContext resolutionContext) {
|
||||
final EntityMappingType entityMappingType = resolutionContext
|
||||
final MappingMetamodelImplementor mappingMetamodel = resolutionContext
|
||||
.getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getEntityMappingType( resultMappingClass );
|
||||
return new ImplicitModelPartResultBuilderEntity( entityMappingType );
|
||||
.getMappingMetamodel();
|
||||
final EntityMappingType entityMappingType = mappingMetamodel.findEntityDescriptor( resultMappingClass );
|
||||
if ( entityMappingType != null ) {
|
||||
// the resultClass is an entity
|
||||
return new ImplicitModelPartResultBuilderEntity( entityMappingType );
|
||||
}
|
||||
|
||||
// todo : support for known embeddables might be nice
|
||||
|
||||
// otherwise, assume it's a "basic" mapping
|
||||
return new ImplicitResultClassBuilder( resultMappingClass );
|
||||
}
|
||||
|
||||
public static ImplicitFetchBuilder implicitFetchBuilder(
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||
*/
|
||||
package org.hibernate.query.results.implicit;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||
import org.hibernate.query.results.ResultBuilder;
|
||||
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.NamedNativeQuery;
|
||||
|
||||
/**
|
||||
* ResultBuilder for handling {@link NamedNativeQuery#resultClass()} when the
|
||||
* class does not refer to an entity
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class ImplicitResultClassBuilder implements ResultBuilder {
|
||||
private final Class<?> suppliedResultClass;
|
||||
|
||||
public ImplicitResultClassBuilder(Class<?> suppliedResultClass) {
|
||||
this.suppliedResultClass = suppliedResultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainResult<?> buildResult(
|
||||
JdbcValuesMetadata jdbcResultsMetadata,
|
||||
int resultPosition,
|
||||
BiFunction<String, String, DynamicFetchBuilderLegacy> legacyFetchResolver,
|
||||
DomainResultCreationState domainResultCreationState) {
|
||||
final MappingMetamodelImplementor mappingMetamodel = domainResultCreationState.getSqlAstCreationState()
|
||||
.getCreationContext()
|
||||
.getMappingMetamodel();
|
||||
final TypeConfiguration typeConfiguration = mappingMetamodel.getTypeConfiguration();
|
||||
final int jdbcResultPosition = resultPosition + 1;
|
||||
|
||||
final BasicType<Object> basicType = jdbcResultsMetadata.resolveType(
|
||||
jdbcResultPosition,
|
||||
typeConfiguration.getJavaTypeRegistry().resolveDescriptor( suppliedResultClass ),
|
||||
typeConfiguration
|
||||
);
|
||||
|
||||
return new BasicResult<>(
|
||||
resultPosition,
|
||||
jdbcResultsMetadata.resolveColumnName( jdbcResultPosition ),
|
||||
basicType
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getJavaType() {
|
||||
return suppliedResultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultBuilder cacheKeyInstance() {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,6 @@ import java.util.Set;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -31,7 +29,6 @@ import org.hibernate.MappingException;
|
|||
import org.hibernate.QueryException;
|
||||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.query.spi.NativeQueryInterpreter;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
|
@ -92,6 +89,8 @@ import org.hibernate.type.BasicType;
|
|||
import org.hibernate.type.BasicTypeReference;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.FlushModeType;
|
||||
|
@ -103,6 +102,7 @@ import jakarta.persistence.Tuple;
|
|||
import jakarta.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
|
||||
import static org.hibernate.query.results.Builders.resultClassBuilder;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -157,7 +157,7 @@ public class NativeQueryImpl<R>
|
|||
|
||||
if ( memento.getResultMappingClass() != null ) {
|
||||
resultSetMapping.addResultBuilder(
|
||||
Builders.implicitEntityResultBuilder(
|
||||
resultClassBuilder(
|
||||
memento.getResultMappingClass(),
|
||||
context
|
||||
)
|
||||
|
@ -239,36 +239,18 @@ public class NativeQueryImpl<R>
|
|||
}
|
||||
|
||||
if ( memento.getResultMappingClass() != null ) {
|
||||
resultSetMapping.addResultBuilder(
|
||||
Builders.implicitEntityResultBuilder(
|
||||
memento.getResultMappingClass(),
|
||||
context
|
||||
)
|
||||
);
|
||||
resultSetMapping.addResultBuilder( resultClassBuilder(
|
||||
memento.getResultMappingClass(),
|
||||
context
|
||||
) );
|
||||
return true;
|
||||
}
|
||||
// if ( resultJavaType != null && resultJavaType != Tuple.class ) {
|
||||
// todo (6.0): in 5.x we didn't add implicit result builders and by doing so,
|
||||
// the result type check at the end of the constructor will fail like in 5.x
|
||||
// final JpaMetamodel jpaMetamodel = context.getSessionFactory().getJpaMetamodel();
|
||||
// if ( jpaMetamodel.findEntityType( resultJavaType ) != null ) {
|
||||
// resultSetMapping.addResultBuilder(
|
||||
// Builders.implicitEntityResultBuilder( resultJavaType, context )
|
||||
// );
|
||||
// }
|
||||
// else {
|
||||
// resultSetMapping.addResultBuilder(
|
||||
// Builders.scalar(
|
||||
// 1,
|
||||
// context.getSessionFactory()
|
||||
// .getTypeConfiguration()
|
||||
// .getBasicTypeForJavaType( resultJavaType )
|
||||
// )
|
||||
// );
|
||||
// }
|
||||
//
|
||||
|
||||
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;
|
||||
},
|
||||
|
@ -281,8 +263,9 @@ public class NativeQueryImpl<R>
|
|||
else if ( resultJavaType != null && resultJavaType != Object[].class ) {
|
||||
switch ( resultSetMapping.getNumberOfResultBuilders() ) {
|
||||
case 0:
|
||||
throw new IllegalArgumentException( "Named query exists but its result type is not compatible" );
|
||||
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`
|
||||
final Class<?> actualResultJavaType = resultSetMapping.getResultBuilders().get( 0 ).getJavaType();
|
||||
if ( actualResultJavaType != null && !resultJavaType.isAssignableFrom( actualResultJavaType ) ) {
|
||||
throw buildIncompatibleException( resultJavaType, actualResultJavaType );
|
||||
|
|
|
@ -64,6 +64,7 @@ import org.hibernate.sql.results.spi.ScrollableResultsConsumer;
|
|||
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.CacheRetrieveMode;
|
||||
import jakarta.persistence.CacheStoreMode;
|
||||
|
@ -565,14 +566,14 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
public <J> BasicType<J> resolveType(
|
||||
int position,
|
||||
JavaType<J> explicitJavaType,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
TypeConfiguration typeConfiguration) {
|
||||
if ( columnNames == null ) {
|
||||
initializeArrays();
|
||||
}
|
||||
final BasicType<J> basicType = resultSetAccess.resolveType(
|
||||
position,
|
||||
explicitJavaType,
|
||||
sessionFactory
|
||||
typeConfiguration
|
||||
);
|
||||
types[position - 1] = basicType;
|
||||
return basicType;
|
||||
|
@ -622,7 +623,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
public <J> BasicType<J> resolveType(
|
||||
int position,
|
||||
JavaType<J> explicitJavaType,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
TypeConfiguration typeConfiguration) {
|
||||
final BasicType<?> type = types[position - 1];
|
||||
if ( type == null ) {
|
||||
throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position );
|
||||
|
@ -632,7 +633,7 @@ public class JdbcSelectExecutorStandardImpl implements JdbcSelectExecutor {
|
|||
return (BasicType<J>) type;
|
||||
}
|
||||
else {
|
||||
return sessionFactory.getTypeConfiguration().getBasicTypeRegistry().resolve(
|
||||
return typeConfiguration.getBasicTypeRegistry().resolve(
|
||||
explicitJavaType,
|
||||
type.getJdbcType()
|
||||
);
|
||||
|
|
|
@ -10,8 +10,6 @@ import java.sql.ResultSet;
|
|||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import jakarta.persistence.EnumType;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
@ -22,6 +20,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
|
|||
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import jakarta.persistence.EnumType;
|
||||
|
||||
/**
|
||||
* Access to a JDBC ResultSet and information about it.
|
||||
*
|
||||
|
@ -72,11 +72,7 @@ public interface ResultSetAccess extends JdbcValuesMetadata {
|
|||
}
|
||||
|
||||
@Override
|
||||
default <J> BasicType<J> resolveType(
|
||||
int position,
|
||||
JavaType<J> explicitJavaType,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
final TypeConfiguration typeConfiguration = getFactory().getTypeConfiguration();
|
||||
default <J> BasicType<J> resolveType(int position, JavaType<J> explicitJavaType, TypeConfiguration typeConfiguration) {
|
||||
final JdbcServices jdbcServices = getFactory().getJdbcServices();
|
||||
try {
|
||||
final ResultSetMetaData metaData = getResultSet().getMetaData();
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.sql.results.jdbc.spi;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
* Access to information about the underlying JDBC values
|
||||
|
@ -31,11 +32,20 @@ public interface JdbcValuesMetadata {
|
|||
String resolveColumnName(int position);
|
||||
|
||||
/**
|
||||
* The basic type of a particular result value by position
|
||||
* Determine the mapping to use for a particular position in the result
|
||||
*/
|
||||
default <J> BasicType<J> resolveType(
|
||||
int position,
|
||||
JavaType<J> explicitJavaType,
|
||||
SessionFactoryImplementor sessionFactory) {
|
||||
return resolveType( position, explicitJavaType, sessionFactory.getTypeConfiguration() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the mapping to use for a particular position in the result
|
||||
*/
|
||||
<J> BasicType<J> resolveType(
|
||||
int position,
|
||||
JavaType<J> explicitJavaType,
|
||||
SessionFactoryImplementor sessionFactory);
|
||||
|
||||
TypeConfiguration typeConfiguration);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue