HHH-13082 - Support specifying an AttributeConverter class as a @ColumnResult#type

This commit is contained in:
Steve Ebersole 2021-12-12 15:14:28 -06:00
parent bd784b6e90
commit 38fa2aef75
7 changed files with 385 additions and 113 deletions

View File

@ -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<? extends AttributeConverter<?,?>> base) {
return GenericsHelper.extractParameterizedType( base );
}
}

View File

@ -8,20 +8,18 @@ package org.hibernate.cfg;
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 class AttributeConverterDefinition implements AttributeConverterInfo {
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 class AttributeConverterDefinition implements AttributeConverterInfo {
+ "] 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 class AttributeConverterDefinition implements AttributeConverterInfo {
);
}
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 class AttributeConverterDefinition implements AttributeConverterInfo {
}
}
private ParameterizedType extractAttributeConverterParameterizedType(Type base) {
if ( base != null ) {
Class clazz = extractClass( base );
List<Type> 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 AttributeConverterDefinition implements AttributeConverterInfo {
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<? extends AttributeConverter> getConverterClass() {
return attributeConverter.getClass();

View File

@ -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<Type> 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();
}
};
}
}

View File

@ -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 @@ import org.hibernate.usertype.UserType;
* @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 class ResultMementoBasicStandard implements ResultMementoBasic {
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<? extends AttributeConverter<?, ?>> converterClass = (Class<? extends AttributeConverter<?, ?>>) definedType;
final ManagedBean<? extends AttributeConverter<?,?>> converterBean = sessionFactory.getServiceRegistry()
.getService( ManagedBeanRegistry.class )
.getBean( converterClass );
final JavaType<? extends AttributeConverter<?,?>> 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<Object> registeredBasicType = typeConfiguration.getBasicTypeRegistry()
.getRegisteredType( definition.type().getName() );
// see if this is a registered BasicType...
final BasicType<Object> 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 class ResultMementoBasicStandard implements ResultMementoBasic {
final ManagedBeanRegistry beanRegistry = sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class );
if ( BasicType.class.isAssignableFrom( registeredJtd.getJavaTypeClass() ) ) {
final ManagedBean<BasicType<?>> 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<UserType<?>> userTypeBean = (ManagedBean) beanRegistry.getBean( registeredJtd.getJavaTypeClass() );
// todo (6.0) : is this the best approach? or should we keep a Class<? extends UserType> -> CustomType mapping somewhere?
resolvedBasicType = new CustomType<>( (UserType<Object>) userTypeBean.getBeanInstance(), sessionFactory.getTypeConfiguration() );
this.explicitJavaTypeDescriptor = resolvedBasicType.getJavaTypeDescriptor();
explicitType = new CustomType<>( (UserType<Object>) 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 class ResultMementoBasicStandard implements ResultMementoBasic {
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<String> querySpaceConsumer,
ResultSetMappingResolutionContext context) {
return new CompleteResultBuilderBasicValuedStandard( explicitColumnName, explicitType, explicitJavaTypeDescriptor );
return builder;
}
}

View File

@ -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:<ul>
* <li>JPA {@link jakarta.persistence.ColumnResult}</li>
* <li>`<return-scalar/>` as part of a `<resultset/>` stanza in `hbm.xml`</li>
* </ul>
*
* @author Steve Ebersole
*/
public class CompleteResultBuilderBasicValuedConverted<O,R> implements CompleteResultBuilderBasicValued {
private final String explicitColumnName;
private final ManagedBean<? extends AttributeConverter<O, R>> converterBean;
private final JavaType<? extends AttributeConverter<O, R>> converterJtd;
private final BasicJavaType<O> domainJavaType;
private final BasicValuedMapping underlyingMapping;
public CompleteResultBuilderBasicValuedConverted(
String explicitColumnName,
ManagedBean<? extends AttributeConverter<O, R>> converterBean,
JavaType<? extends AttributeConverter<O, R>> converterJtd,
BasicJavaType<O> 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<String, String, DynamicFetchBuilderLegacy> 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<O,R> valueConverter = new JpaAttributeConverterImpl<>(
converterBean,
converterJtd,
domainJavaType,
underlyingMapping.getJdbcMapping().getJavaTypeDescriptor()
);
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
columnName,
domainJavaType,
valueConverter
);
}
}

View File

@ -10,6 +10,7 @@ import java.util.function.BiFunction;
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;

View File

@ -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.Before;
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 class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
session.close();
}
@Test
public void testNativeQueryResult() {
inTransaction( (session) -> {
final NativeQueryImplementor<Object[]> query = session.createNativeQuery( "select id, salary from EMP", "emp_id_salary" );
final List<Object[]> 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 class QueryTest extends BaseNonConfigCoreFunctionalTestCase {
@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;