HHH-8111 - AttributeConverter doesn't override built-in type mappings

This commit is contained in:
Steve Ebersole 2013-08-19 14:53:56 -05:00
parent 5a7d179f81
commit 7bcf161d36
7 changed files with 449 additions and 108 deletions

View File

@ -26,10 +26,6 @@ package org.hibernate.mapping;
import javax.persistence.AttributeConverter;
import java.lang.annotation.Annotation;
import java.lang.reflect.TypeVariable;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -52,13 +48,9 @@ import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
@ -348,7 +340,6 @@ public class SimpleValue implements KeyValue {
return result;
}
@SuppressWarnings("unchecked")
public void setTypeUsingReflection(String className, String propertyName) throws MappingException {
// NOTE : this is called as the last piece in setting SimpleValue type information, and implementations
// rely on that fact, using it as a signal that all information it is going to get is defined at this point...
@ -374,34 +365,89 @@ public class SimpleValue implements KeyValue {
}
// we had an AttributeConverter...
type = buildAttributeConverterTypeAdapter();
}
// todo : we should validate the number of columns present
// todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode specify-able separately
// then we can "play them against each other" in terms of determining proper typing
// todo : see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching
/**
* Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works totally in
* memory, meaning it converts between one Java representation (the entity attribute representation) and another
* (the value bound into JDBC statements or extracted from results). However, the Hibernate Type system operates
* at the lower level of actually dealing directly with those JDBC objects. So even though we have an
* AttributeConverter, we still need to "fill out" the rest of the BasicType data and bridge calls
* to bind/extract through the converter.
* <p/>
* Essentially the idea here is that an intermediate Java type needs to be used. Let's use an example as a means
* to illustrate... Consider an {@code AttributeConverter<Integer,String>}. This tells Hibernate that the domain
* model defines this attribute as an Integer value (the 'entityAttributeJavaType'), but that we need to treat the
* value as a String (the 'databaseColumnJavaType') when dealing with JDBC (aka, the database type is a
* VARCHAR/CHAR):<ul>
* <li>
* When binding values to PreparedStatements we need to convert the Integer value from the entity
* into a String and pass that String to setString. The conversion is handled by calling
* {@link AttributeConverter#convertToDatabaseColumn(Object)}
* </li>
* <li>
* When extracting values from ResultSets (or CallableStatement parameters) we need to handle the
* value via getString, and convert that returned String to an Integer. That conversion is handled
* by calling {@link AttributeConverter#convertToEntityAttribute(Object)}
* </li>
* </ul>
*
* For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" part of
// the AttributeConverter to resolve the corresponding descriptor. For the SqlTypeDescriptor portion we use the
// "database column representation" part of the AttributeConverter to resolve the "recommended" JDBC type-code
// and use that type-code to resolve the SqlTypeDescriptor to use.
*
*
* <p/>
*
* <p/>
* <p/>
*
* @todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode specify-able separately
* then we can "play them against each other" in terms of determining proper typing
*
* @todo : see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching
*
* @return The built AttributeConverter -> Type adapter
*/
@SuppressWarnings("unchecked")
private Type buildAttributeConverterTypeAdapter() {
// todo : validate the number of columns present here?
// AttributeConverter works totally in memory, meaning it converts between one Java representation (the entity
// attribute representation) and another (the value bound into JDBC statements or extracted from results).
// However, the Hibernate Type system operates at the lower level of actually dealing with those JDBC objects.
// So even though we have an AttributeConverter, we still need to "fill out" the rest of the BasicType
// data. For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" part of
// the AttributeConverter to resolve the corresponding descriptor. For the SqlTypeDescriptor portion we use the
// "database column representation" part of the AttributeConverter to resolve the "recommended" JDBC type-code
// and use that type-code to resolve the SqlTypeDescriptor to use.
final Class entityAttributeJavaType = jpaAttributeConverterDefinition.getEntityAttributeType();
final Class databaseColumnJavaType = jpaAttributeConverterDefinition.getDatabaseColumnType();
final int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType );
final JavaTypeDescriptor javaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( entityAttributeJavaType );
// resolve the JavaTypeDescriptor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" part of
// the AttributeConverter to resolve the corresponding descriptor.
final JavaTypeDescriptor entityAttributeJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( entityAttributeJavaType );
// build the SqlTypeDescriptor adapter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Going back to the illustration, this should be a SqlTypeDescriptor that handles the Integer <-> String
// conversions. This is the more complicated piece. First we need to determine the JDBC type code
// corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read that value out
// of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return
// VARCHAR/CHAR
final int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType );
// find the standard SqlTypeDescriptor for that JDBC type code.
final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode );
// the adapter here injects the AttributeConverter calls into the binding/extraction process...
// find the JavaTypeDescriptor representing the "intermediate database type representation". Back to the
// illustration, this should be the type descriptor for Strings
final JavaTypeDescriptor intermediateJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( databaseColumnJavaType );
// and finally construct the adapter, which injects the AttributeConverter calls into the binding/extraction
// process...
final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter(
jpaAttributeConverterDefinition.getAttributeConverter(),
sqlTypeDescriptor
sqlTypeDescriptor,
intermediateJavaTypeDescriptor
);
final String name = "BasicType adapter for AttributeConverter<" + entityAttributeJavaType + "," + databaseColumnJavaType + ">";
type = new AbstractSingleColumnStandardBasicType( sqlTypeDescriptorAdapter, javaTypeDescriptor ) {
final Type type = new AbstractSingleColumnStandardBasicType( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor ) {
@Override
public String getName() {
return name;
@ -410,6 +456,8 @@ public class SimpleValue implements KeyValue {
log.debug( "Created : " + name );
// todo : cache the BasicType we just created in case that AttributeConverter is applied multiple times.
return type;
}
private Class extractType(TypeVariable typeVariable) {
@ -461,64 +509,6 @@ public class SimpleValue implements KeyValue {
this.jpaAttributeConverterDefinition = jpaAttributeConverterDefinition;
}
public static class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescriptor {
private final AttributeConverter converter;
private final SqlTypeDescriptor delegate;
public AttributeConverterSqlTypeDescriptorAdapter(AttributeConverter converter, SqlTypeDescriptor delegate) {
this.converter = converter;
this.delegate = delegate;
}
@Override
public int getSqlType() {
return delegate.getSqlType();
}
@Override
public boolean canBeRemapped() {
return delegate.canBeRemapped();
}
@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
final ValueBinder realBinder = delegate.getBinder( javaTypeDescriptor );
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
@SuppressWarnings("unchecked")
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
realBinder.bind( st, converter.convertToDatabaseColumn( value ), index, options );
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
final ValueExtractor realExtractor = delegate.getExtractor( javaTypeDescriptor );
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
@SuppressWarnings("unchecked")
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( rs, name, options ) );
}
@Override
@SuppressWarnings("unchecked")
protected X doExtract(CallableStatement statement, int index, WrapperOptions options)
throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, index, options ) );
}
@Override
@SuppressWarnings("unchecked")
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, new String[] {name}, options ) );
}
};
}
}
private void createParameterImpl() {
try {
String[] columnsNames = new String[columns.size()];

View File

@ -0,0 +1,121 @@
/*
* 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.type.descriptor.converter;
import javax.persistence.AttributeConverter;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
/**
* Adapter for incorporating JPA {@link AttributeConverter} handling into the SqlTypeDescriptor contract.
* <p/>
* Essentially this is responsible for mapping to/from the intermediate database type representation. Continuing the
* {@code AttributeConverter<Integer,String>} example from
* {@link org.hibernate.mapping.SimpleValue#buildAttributeConverterTypeAdapter()}, the "intermediate database type
* representation" would be the String representation. So on binding, we convert the incoming Integer to String;
* on extraction we extract the value as String and convert to Integer.
*
* @author Steve Ebersole
*/
public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescriptor {
private final AttributeConverter converter;
private final SqlTypeDescriptor delegate;
private final JavaTypeDescriptor intermediateJavaTypeDescriptor;
public AttributeConverterSqlTypeDescriptorAdapter(
AttributeConverter converter,
SqlTypeDescriptor delegate,
JavaTypeDescriptor intermediateJavaTypeDescriptor) {
this.converter = converter;
this.delegate = delegate;
this.intermediateJavaTypeDescriptor = intermediateJavaTypeDescriptor;
}
@Override
public int getSqlType() {
return delegate.getSqlType();
}
@Override
public boolean canBeRemapped() {
// todo : consider the ramifications of this.
// certainly we need to account for the remapping of the delegate sql-type, but is it really valid to
// allow remapping of the converter sql-type?
return delegate.canBeRemapped();
}
// Binding ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
@SuppressWarnings("unchecked")
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
// Get the binder for the intermediate type representation
final ValueBinder realBinder = delegate.getBinder( intermediateJavaTypeDescriptor );
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
@SuppressWarnings("unchecked")
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
realBinder.bind( st, converter.convertToDatabaseColumn( value ), index, options );
}
};
}
// Extraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
final ValueExtractor realExtractor = delegate.getExtractor( intermediateJavaTypeDescriptor );
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
@SuppressWarnings("unchecked")
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( rs, name, options ) );
}
@Override
@SuppressWarnings("unchecked")
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, index, options ) );
}
@Override
@SuppressWarnings("unchecked")
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, new String[] {name}, options ) );
}
};
}
}

View File

@ -0,0 +1,5 @@
/**
* Support for handling JPA {@link javax.persistence.AttributeConverter} instances as part of the
* Hibernate {@link org.hibernate.type.Type} system.
*/
package org.hibernate.type.descriptor.converter;

View File

@ -28,6 +28,8 @@ import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.type.descriptor.WrapperOptions;
@ -37,6 +39,8 @@ import org.hibernate.type.descriptor.WrapperOptions;
* @author Steve Ebersole
*/
public class JavaTypeDescriptorRegistry {
private static final Logger log = Logger.getLogger( JavaTypeDescriptorRegistry.class );
public static final JavaTypeDescriptorRegistry INSTANCE = new JavaTypeDescriptorRegistry();
private ConcurrentHashMap<Class,JavaTypeDescriptor> descriptorsByClass = new ConcurrentHashMap<Class, JavaTypeDescriptor>();
@ -61,20 +65,20 @@ public class JavaTypeDescriptorRegistry {
return descriptor;
}
if ( Serializable.class.isAssignableFrom( cls ) ) {
return new SerializableTypeDescriptor( cls );
}
// find the first "assignable" match
for ( Map.Entry<Class,JavaTypeDescriptor> entry : descriptorsByClass.entrySet() ) {
if ( cls.isAssignableFrom( entry.getKey() ) ) {
if ( entry.getKey().isAssignableFrom( cls ) ) {
log.debugf( "Using cached JavaTypeDescriptor instance for Java class [%s]", cls.getName() );
return entry.getValue();
}
}
// we could not find one; warn the user (as stuff is likely to break later) and create a fallback instance...
if ( Serializable.class.isAssignableFrom( cls ) ) {
return new SerializableTypeDescriptor( cls );
}
else {
return new FallbackJavaTypeDescriptor<T>( cls );
}
log.warnf( "Could not find matching type descriptor for requested Java class [%s]; using fallback", cls.getName() );
return new FallbackJavaTypeDescriptor<T>( cls );
}
public static class FallbackJavaTypeDescriptor<T> extends AbstractTypeDescriptor<T> {

View File

@ -101,33 +101,48 @@ public class SerializableTypeDescriptor<T extends Serializable> extends Abstract
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
} else if ( byte[].class.isAssignableFrom( type ) ) {
}
else if ( type.isInstance( value ) ) {
return (X) value;
}
else if ( byte[].class.isAssignableFrom( type ) ) {
return (X) toBytes( value );
} else if ( InputStream.class.isAssignableFrom( type ) ) {
}
else if ( InputStream.class.isAssignableFrom( type ) ) {
return (X) new ByteArrayInputStream( toBytes( value ) );
} else if ( BinaryStream.class.isAssignableFrom( type ) ) {
}
else if ( BinaryStream.class.isAssignableFrom( type ) ) {
return (X) new BinaryStreamImpl( toBytes( value ) );
} else if ( Blob.class.isAssignableFrom( type )) {
}
else if ( Blob.class.isAssignableFrom( type )) {
return (X) options.getLobCreator().createBlob( toBytes(value) );
}
throw unknownUnwrap( type );
}
@SuppressWarnings("unchecked")
public <X> T wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
} else if ( byte[].class.isInstance( value ) ) {
}
else if ( byte[].class.isInstance( value ) ) {
return fromBytes( (byte[]) value );
} else if ( InputStream.class.isInstance( value ) ) {
}
else if ( InputStream.class.isInstance( value ) ) {
return fromBytes( DataHelper.extractBytes( (InputStream) value ) );
} else if ( Blob.class.isInstance( value )) {
}
else if ( Blob.class.isInstance( value )) {
try {
return fromBytes( DataHelper.extractBytes( ( (Blob) value ).getBinaryStream() ) );
} catch ( SQLException e ) {
}
catch ( SQLException e ) {
throw new HibernateException(e);
}
}
else if ( getJavaTypeClass().isInstance( value ) ) {
return (T) value;
}
throw unknownWrap( value.getClass() );
}

View File

@ -41,9 +41,12 @@ import org.jboss.logging.Logger;
import org.hibernate.mapping.Array;
/**
* Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the recommendations
* contained here come from the JDBC spec itself, as outlined at <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html#1034737"/>
* Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} contribute this information.
* Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the mappings contained here come
* from the recommendations defined by the JDBC spec itself, as outlined at
* <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html#1034737"/>.
* <p/>
* Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} and
* {@link java.sql.DatabaseMetaData#getTypeInfo()} contribute this information.
*
* @author Steve Ebersole
*/

View File

@ -28,11 +28,16 @@ import javax.persistence.Convert;
import javax.persistence.Converter;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.Serializable;
import java.sql.Clob;
import java.sql.Timestamp;
import java.sql.Types;
import org.hibernate.IrrelevantEntity;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AttributeConverterDefinition;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
@ -40,6 +45,7 @@ import org.hibernate.mapping.SimpleValue;
import org.hibernate.type.AbstractStandardBasicType;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor;
import org.hibernate.type.descriptor.java.StringTypeDescriptor;
import org.junit.Test;
@ -75,7 +81,7 @@ public class AttributeConverterTest extends BaseUnitTestCase {
}
@Test
public void testNormalOperation() {
public void testBasicConverterApplication() {
Configuration cfg = new Configuration();
cfg.addAttributeConverter( StringClobConverter.class, true );
cfg.addAnnotatedClass( Tester.class );
@ -107,15 +113,105 @@ public class AttributeConverterTest extends BaseUnitTestCase {
}
}
@Test
public void testBasicUsage() {
Configuration cfg = new Configuration();
cfg.addAttributeConverter( IntegerToVarcharConverter.class, false );
cfg.addAnnotatedClass( Tester4.class );
cfg.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" );
@Entity
SessionFactory sf = cfg.buildSessionFactory();
try {
Session session = sf.openSession();
session.beginTransaction();
session.save( new Tester4( 1L, "steve", 200 ) );
session.getTransaction().commit();
session.close();
sf.getStatistics().clear();
session = sf.openSession();
session.beginTransaction();
session.get( Tester4.class, 1L );
session.getTransaction().commit();
session.close();
assertEquals( 0, sf.getStatistics().getEntityUpdateCount() );
session = sf.openSession();
session.beginTransaction();
Tester4 t4 = (Tester4) session.get( Tester4.class, 1L );
t4.code = 300;
session.getTransaction().commit();
session.close();
session = sf.openSession();
session.beginTransaction();
t4 = (Tester4) session.get( Tester4.class, 1L );
assertEquals( 300, t4.code.longValue() );
session.delete( t4 );
session.getTransaction().commit();
session.close();
}
finally {
sf.close();
}
}
@Test
public void testBasicTimestampUsage() {
Configuration cfg = new Configuration();
cfg.addAttributeConverter( InstantConverter.class, false );
cfg.addAnnotatedClass( IrrelevantInstantEntity.class );
cfg.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" );
SessionFactory sf = cfg.buildSessionFactory();
try {
Session session = sf.openSession();
session.beginTransaction();
session.save( new IrrelevantInstantEntity( 1L ) );
session.getTransaction().commit();
session.close();
sf.getStatistics().clear();
session = sf.openSession();
session.beginTransaction();
IrrelevantInstantEntity e = (IrrelevantInstantEntity) session.get( IrrelevantInstantEntity.class, 1L );
session.getTransaction().commit();
session.close();
assertEquals( 0, sf.getStatistics().getEntityUpdateCount() );
session = sf.openSession();
session.beginTransaction();
session.delete( e );
session.getTransaction().commit();
session.close();
}
finally {
sf.close();
}
}
// Entity declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Entity(name = "T1")
public static class Tester {
@Id
private Long id;
private String name;
public Tester() {
}
public Tester(Long id, String name) {
this.id = id;
this.name = name;
}
}
@Entity
@Entity(name = "T2")
public static class Tester2 {
@Id
private Long id;
@ -123,14 +219,94 @@ public class AttributeConverterTest extends BaseUnitTestCase {
private String name;
}
@Entity
@Entity(name = "T3")
public static class Tester3 {
@Id
private Long id;
@org.hibernate.annotations.Type( type = "string" )
@Convert(disableConversion = true)
private String name;
}
@Entity(name = "T4")
public static class Tester4 {
@Id
private Long id;
private String name;
@Convert( converter = IntegerToVarcharConverter.class )
private Integer code;
public Tester4() {
}
public Tester4(Long id, String name, Integer code) {
this.id = id;
this.name = name;
this.code = code;
}
}
// This class is for mimicking an Instant from Java 8, which a converter might convert to a java.sql.Timestamp
public static class Instant implements Serializable {
private static final long serialVersionUID = 1L;
private long javaMillis;
public Instant(long javaMillis) {
this.javaMillis = javaMillis;
}
public long toJavaMillis() {
return javaMillis;
}
public static Instant fromJavaMillis(long javaMillis) {
return new Instant( javaMillis );
}
public static Instant now() {
return new Instant( System.currentTimeMillis() );
}
}
@Entity
public static class IrrelevantInstantEntity {
@Id
private Long id;
private Instant dateCreated;
public IrrelevantInstantEntity() {
}
public IrrelevantInstantEntity(Long id) {
this( id, Instant.now() );
}
public IrrelevantInstantEntity(Long id, Instant dateCreated) {
this.id = id;
this.dateCreated = dateCreated;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Instant getDateCreated() {
return dateCreated;
}
public void setDateCreated(Instant dateCreated) {
this.dateCreated = dateCreated;
}
}
// Converter declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Converter( autoApply = true )
public static class StringClobConverter implements AttributeConverter<String,Clob> {
@Override
@ -143,4 +319,31 @@ public class AttributeConverterTest extends BaseUnitTestCase {
return null;
}
}
@Converter( autoApply = true )
public static class IntegerToVarcharConverter implements AttributeConverter<Integer,String> {
@Override
public String convertToDatabaseColumn(Integer attribute) {
return attribute == null ? null : attribute.toString();
}
@Override
public Integer convertToEntityAttribute(String dbData) {
return dbData == null ? null : Integer.valueOf( dbData );
}
}
@Converter( autoApply = true )
public static class InstantConverter implements AttributeConverter<Instant, Timestamp> {
@Override
public Timestamp convertToDatabaseColumn(Instant attribute) {
return new Timestamp( attribute.toJavaMillis() );
}
@Override
public Instant convertToEntityAttribute(Timestamp dbData) {
return Instant.fromJavaMillis( dbData.getTime() );
}
}
}