HHH-8478 - AttributeConverters need to be applied to JPQL and Criteria queries

This commit is contained in:
Steve Ebersole 2013-09-23 12:55:55 -05:00
parent 2243fa68ff
commit 498735aa37
7 changed files with 266 additions and 4 deletions

View File

@ -53,6 +53,7 @@ import java.util.jar.JarFile;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.MapsId; import javax.persistence.MapsId;
@ -2632,6 +2633,42 @@ public class Configuration implements Serializable {
addAttributeConverter( attributeConverter, autoApply ); addAttributeConverter( attributeConverter, autoApply );
} }
/**
* Adds the AttributeConverter Class to this Configuration.
*
* @param attributeConverterClass The AttributeConverter class.
*/
public void addAttributeConverter(Class<? extends AttributeConverter> attributeConverterClass) {
final AttributeConverter attributeConverter;
try {
attributeConverter = attributeConverterClass.newInstance();
}
catch (Exception e) {
throw new AnnotationException(
"Unable to instantiate AttributeConverter [" + attributeConverterClass.getName() + "]"
);
}
addAttributeConverter( attributeConverter );
}
/**
* Adds the AttributeConverter instance to this Configuration. This form is mainly intended for developers
* to programatically add their own AttributeConverter instance. HEM, instead, uses the
* {@link #addAttributeConverter(Class, boolean)} form
*
* @param attributeConverter The AttributeConverter instance.
*/
public void addAttributeConverter(AttributeConverter attributeConverter) {
boolean autoApply = false;
Converter converterAnnotation = attributeConverter.getClass().getAnnotation( Converter.class );
if ( converterAnnotation != null ) {
autoApply = converterAnnotation.autoApply();
}
addAttributeConverter( new AttributeConverterDefinition( attributeConverter, autoApply ) );
}
/** /**
* Adds the AttributeConverter instance to this Configuration. This form is mainly intended for developers * Adds the AttributeConverter instance to this Configuration. This form is mainly intended for developers
* to programatically add their own AttributeConverter instance. HEM, instead, uses the * to programatically add their own AttributeConverter instance. HEM, instead, uses the

View File

@ -228,10 +228,10 @@ public class BinaryLogicOperatorNode extends HqlSqlWalkerNode implements BinaryO
protected Type extractDataType(Node operand) { protected Type extractDataType(Node operand) {
Type type = null; Type type = null;
if ( operand instanceof SqlNode ) { if ( operand instanceof SqlNode ) {
type = ( ( SqlNode ) operand ).getDataType(); type = ( (SqlNode) operand ).getDataType();
} }
if ( type == null && operand instanceof ExpectedTypeAwareNode ) { if ( type == null && operand instanceof ExpectedTypeAwareNode ) {
type = ( ( ExpectedTypeAwareNode ) operand ).getExpectedType(); type = ( (ExpectedTypeAwareNode) operand ).getExpectedType();
} }
return type; return type;
} }

View File

@ -23,25 +23,37 @@
*/ */
package org.hibernate.hql.internal.ast.tree; package org.hibernate.hql.internal.ast.tree;
import javax.persistence.AttributeConverter;
import java.sql.Types;
import antlr.SemanticException; import antlr.SemanticException;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.hql.internal.ast.util.ColumnHelper;
import org.hibernate.type.SingleColumnType;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
/** /**
* Represents a literal. * Represents a literal.
* *
* @author josh * @author josh
* @author Steve Ebersole
*/ */
public class LiteralNode extends AbstractSelectExpression implements HqlSqlTokenTypes { public class LiteralNode extends AbstractSelectExpression implements HqlSqlTokenTypes, ExpectedTypeAwareNode {
private Type expectedType;
public void setScalarColumnText(int i) throws SemanticException { public void setScalarColumnText(int i) throws SemanticException {
ColumnHelper.generateSingleScalarColumn( this, i ); ColumnHelper.generateSingleScalarColumn( this, i );
} }
public Type getDataType() { public Type getDataType() {
if ( expectedType != null ) {
return expectedType;
}
switch ( getType() ) { switch ( getType() ) {
case NUM_INT: case NUM_INT:
return StandardBasicTypes.INTEGER; return StandardBasicTypes.INTEGER;
@ -64,4 +76,53 @@ public class LiteralNode extends AbstractSelectExpression implements HqlSqlToken
return null; return null;
} }
} }
public Object getLiteralValue() {
String text = getText();
if ( getType() == QUOTED_STRING ) {
text = text.substring( 1, text.length() -1 );
}
final Type inherentType = getDataType();
if ( inherentType == null ) {
return text;
}
return ( (SingleColumnType) inherentType ).fromStringValue( text );
}
@Override
public void setExpectedType(Type expectedType) {
if ( this.expectedType != null ) {
return;
}
if ( AttributeConverterTypeAdapter.class.isInstance( expectedType ) ) {
final AttributeConverterTypeAdapter adapterType = (AttributeConverterTypeAdapter) expectedType;
if ( getDataType().getReturnedClass().equals( adapterType.getModelType() ) ) {
// apply the converter
final AttributeConverter converter = ( (AttributeConverterTypeAdapter) expectedType ).getAttributeConverter();
final Object converted = converter.convertToDatabaseColumn( getLiteralValue() );
if ( isCharacterData( adapterType.sqlType() ) ) {
setText( "'" + converted.toString() + "'" );
}
else {
setText( converted.toString() );
}
}
this.expectedType = expectedType;
}
}
private boolean isCharacterData(int typeCode) {
return Types.VARCHAR == typeCode
|| Types.CHAR == typeCode
|| Types.NVARCHAR == typeCode
|| Types.NCHAR == typeCode;
}
@Override
public Type getExpectedType() {
return expectedType;
}
} }

View File

@ -446,6 +446,8 @@ public class SimpleValue implements KeyValue {
name, name,
attributeConverterDefinition.getAttributeConverter(), attributeConverterDefinition.getAttributeConverter(),
sqlTypeDescriptorAdapter, sqlTypeDescriptorAdapter,
entityAttributeJavaType,
databaseColumnJavaType,
entityAttributeJavaTypeDescriptor entityAttributeJavaTypeDescriptor
); );
} }

View File

@ -40,15 +40,22 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
private static final Logger log = Logger.getLogger( AttributeConverterTypeAdapter.class ); private static final Logger log = Logger.getLogger( AttributeConverterTypeAdapter.class );
private final String name; private final String name;
private final Class modelType;
private final Class jdbcType;
private final AttributeConverter<? extends T,?> attributeConverter; private final AttributeConverter<? extends T,?> attributeConverter;
public AttributeConverterTypeAdapter( public AttributeConverterTypeAdapter(
String name, String name,
AttributeConverter<? extends T,?> attributeConverter, AttributeConverter<? extends T,?> attributeConverter,
SqlTypeDescriptor sqlTypeDescriptorAdapter, SqlTypeDescriptor sqlTypeDescriptorAdapter,
Class modelType,
Class jdbcType,
JavaTypeDescriptor<T> entityAttributeJavaTypeDescriptor) { JavaTypeDescriptor<T> entityAttributeJavaTypeDescriptor) {
super( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor ); super( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor );
this.name = name; this.name = name;
this.modelType = modelType;
this.jdbcType = jdbcType;
this.attributeConverter = attributeConverter; this.attributeConverter = attributeConverter;
log.debug( "Created AttributeConverterTypeAdapter -> " + name ); log.debug( "Created AttributeConverterTypeAdapter -> " + name );
@ -59,6 +66,14 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
return name; return name;
} }
public Class getModelType() {
return modelType;
}
public Class getJdbcType() {
return jdbcType;
}
public AttributeConverter<? extends T,?> getAttributeConverter() { public AttributeConverter<? extends T,?> getAttributeConverter() {
return attributeConverter; return attributeConverter;
} }

View File

@ -1102,7 +1102,12 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
List<Class> loadedAnnotatedClasses = (List<Class>) configurationValues.remove( AvailableSettings.LOADED_CLASSES ); List<Class> loadedAnnotatedClasses = (List<Class>) configurationValues.remove( AvailableSettings.LOADED_CLASSES );
if ( loadedAnnotatedClasses != null ) { if ( loadedAnnotatedClasses != null ) {
for ( Class cls : loadedAnnotatedClasses ) { for ( Class cls : loadedAnnotatedClasses ) {
cfg.addAnnotatedClass( cls ); if ( AttributeConverter.class.isAssignableFrom( cls ) ) {
cfg.addAttributeConverter( (Class<? extends AttributeConverter>) cls );
}
else {
cfg.addAnnotatedClass( cls );
}
} }
} }

View File

@ -0,0 +1,142 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa.test.convert;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static junit.framework.Assert.assertNotNull;
/**
* Test AttributeConverter functioning in various Query scenarios.
*
* @author Steve Ebersole
*/
public class QueryTest extends BaseEntityManagerFunctionalTestCase {
public static final float SALARY = 267.89f;
@Test
public void testJpqlLiteral() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Employee jDoe = em.createQuery( "from Employee e where e.salary = " + SALARY + "f", Employee.class ).getSingleResult();
assertNotNull( jDoe );
em.getTransaction().commit();
em.close();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Employee.class, SalaryConverter.class };
}
@Before
public void setUpTestData() {
EntityManager em = entityManagerFactory().createEntityManager();
em.getTransaction().begin();
em.persist( new Employee( 1, new Name( "John", "Q.", "Doe" ), SALARY ) );
em.getTransaction().commit();
em.close();
}
@After
public void cleanUpTestData() {
EntityManager em = entityManagerFactory().createEntityManager();
em.getTransaction().begin();
em.createQuery( "delete Employee" ).executeUpdate();
em.getTransaction().commit();
em.close();
}
@Entity( name = "Employee" )
@Table( name = "EMP" )
public static class Employee {
@Id
public Integer id;
@Embedded
public Name name;
public Float salary;
public Employee() {
}
public Employee(Integer id, Name name, Float salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
}
@Embeddable
public static class Name {
public String first;
public String middle;
public String last;
public Name() {
}
public Name(String first, String middle, String last) {
this.first = first;
this.middle = middle;
this.last = last;
}
}
@Converter( autoApply = true )
public static class SalaryConverter implements AttributeConverter<Float,Long> {
@Override
@SuppressWarnings("UnnecessaryBoxing")
public Long convertToDatabaseColumn(Float attribute) {
if ( attribute == null ) {
return null;
}
return new Long( (long)(attribute*100) );
}
@Override
@SuppressWarnings("UnnecessaryBoxing")
public Float convertToEntityAttribute(Long dbData) {
if ( dbData == null ) {
return null;
}
return new Float( ( dbData.floatValue() ) / 100 );
}
}
}