diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java new file mode 100644 index 0000000000..e4caeb7a0c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/ConverterHelper.java @@ -0,0 +1,22 @@ +/* + * 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.boot.model.convert.internal; + +import java.lang.reflect.ParameterizedType; + +import org.hibernate.internal.util.GenericsHelper; + +import jakarta.persistence.AttributeConverter; + +/** + * Helpers related to handling converters + */ +public class ConverterHelper { + public static ParameterizedType extractAttributeConverterParameterizedType(Class> base) { + return GenericsHelper.extractParameterizedType( base ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java index f588f3e3fd..7a9d93a758 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java @@ -8,20 +8,18 @@ import java.lang.reflect.Constructor; import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; + import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.model.convert.internal.ConverterHelper; import org.hibernate.boot.model.convert.internal.InstanceBasedConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.internal.util.GenericsHelper; /** * Externalized representation of an AttributeConverter @@ -121,7 +119,7 @@ public AttributeConverterDefinition(AttributeConverter attributeConverter, boole this.autoApply = autoApply; final Class attributeConverterClass = attributeConverter.getClass(); - final ParameterizedType attributeConverterSignature = extractAttributeConverterParameterizedType( attributeConverterClass ); + final ParameterizedType attributeConverterSignature = ConverterHelper.extractAttributeConverterParameterizedType( attributeConverterClass ); if ( attributeConverterSignature == null ) { throw new AssertionFailure( "Could not extract ParameterizedType representation of AttributeConverter definition " + @@ -142,7 +140,7 @@ public AttributeConverterDefinition(AttributeConverter attributeConverter, boole + "] specified more than 2 parameterized types" ); } - entityAttributeType = extractClass( attributeConverterSignature.getActualTypeArguments()[0] ); + entityAttributeType = GenericsHelper.extractClass( attributeConverterSignature.getActualTypeArguments()[0] ); if ( entityAttributeType == null ) { throw new AnnotationException( "Could not determine 'entity attribute' type from given AttributeConverter [" + @@ -150,7 +148,7 @@ public AttributeConverterDefinition(AttributeConverter attributeConverter, boole ); } - databaseColumnType = extractClass(attributeConverterSignature.getActualTypeArguments()[1]); + databaseColumnType = GenericsHelper.extractClass(attributeConverterSignature.getActualTypeArguments()[1]); if ( databaseColumnType == null ) { throw new AnnotationException( "Could not determine 'database column' type from given AttributeConverter [" + @@ -159,77 +157,6 @@ public AttributeConverterDefinition(AttributeConverter attributeConverter, boole } } - private ParameterizedType extractAttributeConverterParameterizedType(Type base) { - if ( base != null ) { - Class clazz = extractClass( base ); - List types = new ArrayList<>(); - types.add( clazz.getGenericSuperclass() ); - types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) ); - for ( Type type : types ) { - type = resolveType( type, base ); - if ( ParameterizedType.class.isInstance( type ) ) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - if ( AttributeConverter.class.equals( parameterizedType.getRawType() ) ) { - return parameterizedType; - } - } - ParameterizedType parameterizedType = extractAttributeConverterParameterizedType( type ); - if ( parameterizedType != null ) { - return parameterizedType; - } - } - } - return null; - } - - private static Type resolveType(Type target, Type context) { - if ( target instanceof ParameterizedType ) { - return resolveParameterizedType( (ParameterizedType) target, context ); - } - else if ( target instanceof TypeVariable ) { - return resolveTypeVariable( (TypeVariable) target, (ParameterizedType) context ); - } - return target; - } - - private static ParameterizedType resolveParameterizedType(final ParameterizedType parameterizedType, Type context) { - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - - final Type[] resolvedTypeArguments = new Type[actualTypeArguments.length]; - for ( int idx = 0; idx < actualTypeArguments.length; idx++ ) { - resolvedTypeArguments[idx] = resolveType( actualTypeArguments[idx], context ); - } - return new ParameterizedType() { - - @Override - public Type[] getActualTypeArguments() { - return resolvedTypeArguments; - } - - @Override - public Type getRawType() { - return parameterizedType.getRawType(); - } - - @Override - public Type getOwnerType() { - return parameterizedType.getOwnerType(); - } - - }; - } - - private static Type resolveTypeVariable(TypeVariable typeVariable, ParameterizedType context) { - Class clazz = extractClass( context.getRawType() ); - TypeVariable[] typeParameters = clazz.getTypeParameters(); - for ( int idx = 0; idx < typeParameters.length; idx++ ) { - if ( typeVariable.getName().equals( typeParameters[idx].getName() ) ) { - return resolveType( context.getActualTypeArguments()[idx], context ); - } - } - return typeVariable; - } - public AttributeConverter getAttributeConverter() { return attributeConverter; } @@ -246,16 +173,6 @@ public Class getDatabaseColumnType() { return databaseColumnType; } - private static Class extractClass(Type type) { - if ( type instanceof Class ) { - return (Class) type; - } - else if ( type instanceof ParameterizedType ) { - return extractClass( ( (ParameterizedType) type ).getRawType() ); - } - return null; - } - @Override public Class getConverterClass() { return attributeConverter.getClass(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java new file mode 100644 index 0000000000..ce3688672b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/GenericsHelper.java @@ -0,0 +1,113 @@ +/* + * 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.internal.util; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jakarta.persistence.AttributeConverter; + +public class GenericsHelper { + public static ParameterizedType extractParameterizedType(Type base) { + if ( base == null ) { + return null; + } + + final Class clazz = extractClass( base ); + if ( clazz == null ) { + return null; + } + + final List types = new ArrayList<>(); + types.add( clazz.getGenericSuperclass() ); + types.addAll( Arrays.asList( clazz.getGenericInterfaces() ) ); + + for ( Type type : types ) { + type = resolveType( type, base ); + if ( type instanceof ParameterizedType ) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + if ( AttributeConverter.class.equals( parameterizedType.getRawType() ) ) { + return parameterizedType; + } + } + + final ParameterizedType parameterizedType = extractParameterizedType( type ); + if ( parameterizedType != null ) { + return parameterizedType; + } + } + + return null; + } + + private static Type resolveTypeVariable(TypeVariable typeVariable, ParameterizedType context) { + final Class clazz = extractClass( context.getRawType() ); + if ( clazz == null ) { + return null; + } + + final TypeVariable[] typeParameters = clazz.getTypeParameters(); + for ( int idx = 0; idx < typeParameters.length; idx++ ) { + if ( typeVariable.getName().equals( typeParameters[idx].getName() ) ) { + return resolveType( context.getActualTypeArguments()[idx], context ); + } + } + + return typeVariable; + } + + public static Class extractClass(Type type) { + if ( type instanceof Class ) { + return (Class) type; + } + else if ( type instanceof ParameterizedType ) { + return extractClass( ( (ParameterizedType) type ).getRawType() ); + } + return null; + } + + private static Type resolveType(Type target, Type context) { + if ( target instanceof ParameterizedType ) { + return resolveParameterizedType( (ParameterizedType) target, context ); + } + else if ( target instanceof TypeVariable ) { + return resolveTypeVariable( (TypeVariable) target, (ParameterizedType) context ); + } + return target; + } + + private static ParameterizedType resolveParameterizedType(final ParameterizedType parameterizedType, Type context) { + final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + final Type[] resolvedTypeArguments = new Type[actualTypeArguments.length]; + for ( int idx = 0; idx < actualTypeArguments.length; idx++ ) { + resolvedTypeArguments[idx] = resolveType( actualTypeArguments[idx], context ); + } + return new ParameterizedType() { + + @Override + public Type[] getActualTypeArguments() { + return resolvedTypeArguments; + } + + @Override + public Type getRawType() { + return parameterizedType.getRawType(); + } + + @Override + public Type getOwnerType() { + return parameterizedType.getOwnerType(); + } + + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java index bdd13fbfb0..7850de7f0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoBasicStandard.java @@ -6,22 +6,30 @@ */ package org.hibernate.query.internal; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.function.Consumer; -import jakarta.persistence.ColumnResult; +import org.hibernate.boot.model.convert.internal.ConverterHelper; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.query.named.ResultMementoBasic; import org.hibernate.query.results.ResultBuilderBasicValued; +import org.hibernate.query.results.complete.CompleteResultBuilderBasicValuedConverted; import org.hibernate.query.results.complete.CompleteResultBuilderBasicValuedStandard; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.type.BasicType; import org.hibernate.type.CustomType; +import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.usertype.UserType; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.ColumnResult; + /** * Implementation of ResultMappingMemento for scalar (basic) results. * @@ -50,11 +58,9 @@ * @author Steve Ebersole */ public class ResultMementoBasicStandard implements ResultMementoBasic { - public final String explicitColumnName; - private final BasicType explicitType; - private final JavaType explicitJavaTypeDescriptor; + private final ResultBuilderBasicValued builder; /** * Creation of ScalarResultMappingMemento for JPA descriptor @@ -64,21 +70,42 @@ public ResultMementoBasicStandard( ResultSetMappingResolutionContext context) { this.explicitColumnName = definition.name(); - BasicType resolvedBasicType = null; + final SessionFactoryImplementor sessionFactory = context.getSessionFactory(); + final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); final Class definedType = definition.type(); + if ( void.class == definedType ) { - explicitJavaTypeDescriptor = null; + builder = new CompleteResultBuilderBasicValuedStandard( explicitColumnName, null, null ); + } + else if ( AttributeConverter.class.isAssignableFrom( definedType ) ) { + final Class> converterClass = (Class>) definedType; + final ManagedBean> converterBean = sessionFactory.getServiceRegistry() + .getService( ManagedBeanRegistry.class ) + .getBean( converterClass ); + final JavaType> converterJtd = typeConfiguration + .getJavaTypeDescriptorRegistry() + .getDescriptor( converterClass ); + + final ParameterizedType parameterizedType = ConverterHelper.extractAttributeConverterParameterizedType( converterBean.getBeanClass() ); + + builder = new CompleteResultBuilderBasicValuedConverted( + explicitColumnName, + converterBean, + converterJtd, + determineDomainJavaType( parameterizedType, typeConfiguration.getJavaTypeDescriptorRegistry() ), + resolveUnderlyingMapping( parameterizedType, typeConfiguration ) + ); } else { - final SessionFactoryImplementor sessionFactory = context.getSessionFactory(); - final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); + final BasicType explicitType; + final JavaType explicitJavaTypeDescriptor; - // first see if this is a registered BasicType... - final BasicType registeredBasicType = typeConfiguration.getBasicTypeRegistry() - .getRegisteredType( definition.type().getName() ); + // see if this is a registered BasicType... + final BasicType registeredBasicType = typeConfiguration.getBasicTypeRegistry().getRegisteredType( definition.type().getName() ); if ( registeredBasicType != null ) { - this.explicitJavaTypeDescriptor = registeredBasicType.getJavaTypeDescriptor(); + explicitType = registeredBasicType; + explicitJavaTypeDescriptor = registeredBasicType.getJavaTypeDescriptor(); } else { final JavaTypeRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); @@ -86,22 +113,40 @@ public ResultMementoBasicStandard( final ManagedBeanRegistry beanRegistry = sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class ); if ( BasicType.class.isAssignableFrom( registeredJtd.getJavaTypeClass() ) ) { final ManagedBean> typeBean = (ManagedBean) beanRegistry.getBean( registeredJtd.getJavaTypeClass() ); - resolvedBasicType = typeBean.getBeanInstance(); - this.explicitJavaTypeDescriptor = resolvedBasicType.getJavaTypeDescriptor(); + explicitType = typeBean.getBeanInstance(); + explicitJavaTypeDescriptor = explicitType.getJavaTypeDescriptor(); } else if ( UserType.class.isAssignableFrom( registeredJtd.getJavaTypeClass() ) ) { final ManagedBean> userTypeBean = (ManagedBean) beanRegistry.getBean( registeredJtd.getJavaTypeClass() ); // todo (6.0) : is this the best approach? or should we keep a Class -> CustomType mapping somewhere? - resolvedBasicType = new CustomType<>( (UserType) userTypeBean.getBeanInstance(), sessionFactory.getTypeConfiguration() ); - this.explicitJavaTypeDescriptor = resolvedBasicType.getJavaTypeDescriptor(); + explicitType = new CustomType<>( (UserType) userTypeBean.getBeanInstance(), typeConfiguration ); + explicitJavaTypeDescriptor = explicitType.getJavaTypeDescriptor(); } else { - this.explicitJavaTypeDescriptor = jtdRegistry.getDescriptor( definition.type() ); + explicitType = null; + explicitJavaTypeDescriptor = jtdRegistry.getDescriptor( definition.type() ); } } - } - explicitType = resolvedBasicType; + builder = new CompleteResultBuilderBasicValuedStandard( explicitColumnName, explicitType, explicitJavaTypeDescriptor ); + } + } + + private BasicJavaType determineDomainJavaType( + ParameterizedType parameterizedType, + JavaTypeRegistry jtdRegistry) { + final Type[] typeParameters = parameterizedType.getActualTypeArguments(); + final Type domainTypeType = typeParameters[ 0 ]; + final Class domainClass = (Class) domainTypeType; + + return (BasicJavaType) jtdRegistry.getDescriptor( domainClass ); + } + + private BasicValuedMapping resolveUnderlyingMapping( + ParameterizedType parameterizedType, + TypeConfiguration typeConfiguration) { + final Type[] typeParameters = parameterizedType.getActualTypeArguments(); + return typeConfiguration.standardBasicTypeForJavaType( (Class) typeParameters[ 1 ] ); } public ResultMementoBasicStandard( @@ -109,16 +154,19 @@ public ResultMementoBasicStandard( BasicType explicitType, ResultSetMappingResolutionContext context) { this.explicitColumnName = explicitColumnName; - this.explicitType = explicitType; - this.explicitJavaTypeDescriptor = explicitType != null - ? explicitType.getJavaTypeDescriptor() - : null; + this.builder = new CompleteResultBuilderBasicValuedStandard( + explicitColumnName, + explicitType, + explicitType != null + ? explicitType.getJavaTypeDescriptor() + : null + ); } @Override public ResultBuilderBasicValued resolve( Consumer querySpaceConsumer, ResultSetMappingResolutionContext context) { - return new CompleteResultBuilderBasicValuedStandard( explicitColumnName, explicitType, explicitJavaTypeDescriptor ); + return builder; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java new file mode 100644 index 0000000000..45f56eafaa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java @@ -0,0 +1,144 @@ +/* + * 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.complete; + +import java.util.function.BiFunction; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl; +import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultsHelper; +import org.hibernate.query.results.SqlSelectionImpl; +import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; +import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.sql.ast.spi.SqlSelection; +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.descriptor.java.BasicJavaType; +import org.hibernate.type.descriptor.java.JavaType; + +import jakarta.persistence.AttributeConverter; + +import static org.hibernate.query.results.ResultsHelper.impl; + +/** + * ResultBuilder for scalar results defined via:
    + *
  • JPA {@link jakarta.persistence.ColumnResult}
  • + *
  • `` as part of a `` stanza in `hbm.xml`
  • + *
+ * + * @author Steve Ebersole + */ +public class CompleteResultBuilderBasicValuedConverted implements CompleteResultBuilderBasicValued { + private final String explicitColumnName; + private final ManagedBean> converterBean; + private final JavaType> converterJtd; + private final BasicJavaType domainJavaType; + private final BasicValuedMapping underlyingMapping; + + public CompleteResultBuilderBasicValuedConverted( + String explicitColumnName, + ManagedBean> converterBean, + JavaType> converterJtd, + BasicJavaType domainJavaType, + BasicValuedMapping underlyingMapping) { + this.explicitColumnName = explicitColumnName; + this.converterBean = converterBean; + this.converterJtd = converterJtd; + this.domainJavaType = domainJavaType; + this.underlyingMapping = underlyingMapping; + } + + @Override + public Class getJavaType() { + return domainJavaType.getJavaTypeClass(); + } + + @Override + public BasicResult buildResult( + JdbcValuesMetadata jdbcResultsMetadata, + int resultPosition, + BiFunction legacyFetchResolver, + DomainResultCreationState domainResultCreationState) { + final DomainResultCreationStateImpl creationStateImpl = impl( domainResultCreationState ); + final SessionFactoryImplementor sessionFactory = creationStateImpl.getSessionFactory(); + + final String columnName; + if ( explicitColumnName != null ) { + columnName = explicitColumnName; + } + else { + columnName = jdbcResultsMetadata.resolveColumnName( resultPosition + 1 ); + } + + +// final int jdbcPosition; +// if ( explicitColumnName != null ) { +// jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( explicitColumnName ); +// } +// else { +// jdbcPosition = resultPosition + 1; +// } +// +// final BasicValuedMapping basicType; +// if ( explicitType != null ) { +// basicType = explicitType; +// } +// else { +// basicType = jdbcResultsMetadata.resolveType( jdbcPosition, explicitJavaTypeDescriptor ); +// } +// +// final SqlSelection sqlSelection = creationStateImpl.resolveSqlSelection( +// creationStateImpl.resolveSqlExpression( +// columnName, +// processingState -> { +// final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition ); +// return new SqlSelectionImpl( valuesArrayPosition, basicType ); +// } +// ), +// basicType.getExpressableJavaTypeDescriptor(), +// sessionFactory.getTypeConfiguration() +// ); + + + final SqlSelection sqlSelection = creationStateImpl.resolveSqlSelection( + creationStateImpl.resolveSqlExpression( + columnName, + processingState -> { + final int jdbcPosition; + if ( explicitColumnName != null ) { + jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( explicitColumnName ); + } + else { + jdbcPosition = resultPosition + 1; + } + + final int valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition ); + return new SqlSelectionImpl( valuesArrayPosition, underlyingMapping ); + } + ), + domainJavaType, + sessionFactory.getTypeConfiguration() + ); + + final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl<>( + converterBean, + converterJtd, + domainJavaType, + underlyingMapping.getJdbcMapping().getJavaTypeDescriptor() + ); + + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + columnName, + domainJavaType, + valueConverter + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java index 08b2148983..d52819b25d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java @@ -10,6 +10,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java index 358b68490a..d4f154a51a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/QueryTest.java @@ -6,18 +6,23 @@ */ package org.hibernate.orm.test.mapping.converted.converter; +import java.util.List; + import jakarta.persistence.AttributeConverter; import jakarta.persistence.Column; +import jakarta.persistence.ColumnResult; import jakarta.persistence.Convert; import jakarta.persistence.Converter; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.SqlResultSetMapping; import jakarta.persistence.Table; import org.hibernate.HibernateException; import org.hibernate.Session; +import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.After; @@ -25,6 +30,7 @@ import org.junit.Test; import static junit.framework.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNull; /** @@ -56,6 +62,20 @@ public void testJpqlBooleanLiteral() { session.close(); } + @Test + public void testNativeQueryResult() { + inTransaction( (session) -> { + final NativeQueryImplementor query = session.createNativeQuery( "select id, salary from EMP", "emp_id_salary" ); + + final List results = query.list(); + assertThat( results ).hasSize( 1 ); + + final Object[] values = results.get( 0 ); + assertThat( values[0] ).isEqualTo( 1 ); + assertThat( values[1] ).isEqualTo( SALARY ); + } ); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { Employee.class, SalaryConverter.class }; @@ -81,6 +101,13 @@ public void cleanUpTestData() { @Entity( name = "Employee" ) @Table( name = "EMP" ) + @SqlResultSetMapping( + name = "emp_id_salary", + columns = { + @ColumnResult( name = "id" ), + @ColumnResult( name = "salary", type = SalaryConverter.class ) + } + ) public static class Employee { @Id public Integer id;