diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java
index 57220006eb..6f660262bd 100644
--- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java
+++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java
@@ -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.
+ *
+ * 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}. 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):
+ * -
+ * 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)}
+ *
+ * -
+ * 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)}
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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 ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) {
- final ValueBinder realBinder = delegate.getBinder( javaTypeDescriptor );
- return new BasicBinder( 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 ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) {
- final ValueExtractor realExtractor = delegate.getExtractor( javaTypeDescriptor );
- return new BasicExtractor( 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()];
diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java
new file mode 100644
index 0000000000..80c8511ee2
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterSqlTypeDescriptorAdapter.java
@@ -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.
+ *
+ * Essentially this is responsible for mapping to/from the intermediate database type representation. Continuing the
+ * {@code AttributeConverter} 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 ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) {
+ // Get the binder for the intermediate type representation
+ final ValueBinder realBinder = delegate.getBinder( intermediateJavaTypeDescriptor );
+ return new BasicBinder( 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 ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) {
+ final ValueExtractor realExtractor = delegate.getExtractor( intermediateJavaTypeDescriptor );
+ return new BasicExtractor( 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 ) );
+ }
+ };
+ }
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/package-info.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/package-info.java
new file mode 100644
index 0000000000..2f67826f26
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/package-info.java
@@ -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;
diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java
index 48e94a1dca..d5b1da2fac 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java
@@ -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 descriptorsByClass = new ConcurrentHashMap();
@@ -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 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( cls );
- }
+ log.warnf( "Could not find matching type descriptor for requested Java class [%s]; using fallback", cls.getName() );
+ return new FallbackJavaTypeDescriptor( cls );
}
public static class FallbackJavaTypeDescriptor extends AbstractTypeDescriptor {
diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java
index 493a5247e5..fc87fb9773 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java
@@ -101,33 +101,48 @@ public class SerializableTypeDescriptor extends Abstract
public X unwrap(T value, Class 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 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() );
}
diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java
index 9d894b5343..e837f1b521 100644
--- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java
+++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java
@@ -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
- * 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
+ * .
+ *
+ * Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} and
+ * {@link java.sql.DatabaseMetaData#getTypeInfo()} contribute this information.
*
* @author Steve Ebersole
*/
diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java
index a1ab1ced22..895367f738 100644
--- a/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/test/type/AttributeConverterTest.java
@@ -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 {
@Override
@@ -143,4 +319,31 @@ public class AttributeConverterTest extends BaseUnitTestCase {
return null;
}
}
+
+ @Converter( autoApply = true )
+ public static class IntegerToVarcharConverter implements AttributeConverter {
+ @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 {
+ @Override
+ public Timestamp convertToDatabaseColumn(Instant attribute) {
+ return new Timestamp( attribute.toJavaMillis() );
+ }
+
+ @Override
+ public Instant convertToEntityAttribute(Timestamp dbData) {
+ return Instant.fromJavaMillis( dbData.getTime() );
+ }
+ }
}