HHH-8111 - AttributeConverter doesn't override built-in type mappings
This commit is contained in:
parent
5a7d179f81
commit
7bcf161d36
|
@ -26,10 +26,6 @@ package org.hibernate.mapping;
|
||||||
import javax.persistence.AttributeConverter;
|
import javax.persistence.AttributeConverter;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.TypeVariable;
|
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.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -52,13 +48,9 @@ import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||||
import org.hibernate.internal.util.ReflectHelper;
|
import org.hibernate.internal.util.ReflectHelper;
|
||||||
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
|
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
import org.hibernate.type.descriptor.ValueBinder;
|
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
|
||||||
import org.hibernate.type.descriptor.ValueExtractor;
|
|
||||||
import org.hibernate.type.descriptor.WrapperOptions;
|
|
||||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry;
|
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.JdbcTypeJavaClassMappings;
|
||||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
|
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
|
||||||
|
@ -348,7 +340,6 @@ public class SimpleValue implements KeyValue {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setTypeUsingReflection(String className, String propertyName) throws MappingException {
|
public void setTypeUsingReflection(String className, String propertyName) throws MappingException {
|
||||||
// NOTE : this is called as the last piece in setting SimpleValue type information, and implementations
|
// 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...
|
// 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...
|
// 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
|
* Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works totally in
|
||||||
// then we can "play them against each other" in terms of determining proper typing
|
* memory, meaning it converts between one Java representation (the entity attribute representation) and another
|
||||||
// todo : see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching
|
* (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 entityAttributeJavaType = jpaAttributeConverterDefinition.getEntityAttributeType();
|
||||||
final Class databaseColumnJavaType = jpaAttributeConverterDefinition.getDatabaseColumnType();
|
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 );
|
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(
|
final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter(
|
||||||
jpaAttributeConverterDefinition.getAttributeConverter(),
|
jpaAttributeConverterDefinition.getAttributeConverter(),
|
||||||
sqlTypeDescriptor
|
sqlTypeDescriptor,
|
||||||
|
intermediateJavaTypeDescriptor
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
final String name = "BasicType adapter for AttributeConverter<" + entityAttributeJavaType + "," + databaseColumnJavaType + ">";
|
final String name = "BasicType adapter for AttributeConverter<" + entityAttributeJavaType + "," + databaseColumnJavaType + ">";
|
||||||
type = new AbstractSingleColumnStandardBasicType( sqlTypeDescriptorAdapter, javaTypeDescriptor ) {
|
final Type type = new AbstractSingleColumnStandardBasicType( sqlTypeDescriptorAdapter, entityAttributeJavaTypeDescriptor ) {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
|
@ -410,6 +456,8 @@ public class SimpleValue implements KeyValue {
|
||||||
log.debug( "Created : " + name );
|
log.debug( "Created : " + name );
|
||||||
|
|
||||||
// todo : cache the BasicType we just created in case that AttributeConverter is applied multiple times.
|
// todo : cache the BasicType we just created in case that AttributeConverter is applied multiple times.
|
||||||
|
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Class extractType(TypeVariable typeVariable) {
|
private Class extractType(TypeVariable typeVariable) {
|
||||||
|
@ -461,64 +509,6 @@ public class SimpleValue implements KeyValue {
|
||||||
this.jpaAttributeConverterDefinition = jpaAttributeConverterDefinition;
|
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() {
|
private void createParameterImpl() {
|
||||||
try {
|
try {
|
||||||
String[] columnsNames = new String[columns.size()];
|
String[] columnsNames = new String[columns.size()];
|
||||||
|
|
|
@ -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 ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -28,6 +28,8 @@ import java.util.Comparator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.type.descriptor.WrapperOptions;
|
import org.hibernate.type.descriptor.WrapperOptions;
|
||||||
|
|
||||||
|
@ -37,6 +39,8 @@ import org.hibernate.type.descriptor.WrapperOptions;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class JavaTypeDescriptorRegistry {
|
public class JavaTypeDescriptorRegistry {
|
||||||
|
private static final Logger log = Logger.getLogger( JavaTypeDescriptorRegistry.class );
|
||||||
|
|
||||||
public static final JavaTypeDescriptorRegistry INSTANCE = new JavaTypeDescriptorRegistry();
|
public static final JavaTypeDescriptorRegistry INSTANCE = new JavaTypeDescriptorRegistry();
|
||||||
|
|
||||||
private ConcurrentHashMap<Class,JavaTypeDescriptor> descriptorsByClass = new ConcurrentHashMap<Class, JavaTypeDescriptor>();
|
private ConcurrentHashMap<Class,JavaTypeDescriptor> descriptorsByClass = new ConcurrentHashMap<Class, JavaTypeDescriptor>();
|
||||||
|
@ -61,20 +65,20 @@ public class JavaTypeDescriptorRegistry {
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( Serializable.class.isAssignableFrom( cls ) ) {
|
||||||
|
return new SerializableTypeDescriptor( cls );
|
||||||
|
}
|
||||||
|
|
||||||
// find the first "assignable" match
|
// find the first "assignable" match
|
||||||
for ( Map.Entry<Class,JavaTypeDescriptor> entry : descriptorsByClass.entrySet() ) {
|
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();
|
return entry.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we could not find one; warn the user (as stuff is likely to break later) and create a fallback instance...
|
log.warnf( "Could not find matching type descriptor for requested Java class [%s]; using fallback", cls.getName() );
|
||||||
if ( Serializable.class.isAssignableFrom( cls ) ) {
|
return new FallbackJavaTypeDescriptor<T>( cls );
|
||||||
return new SerializableTypeDescriptor( cls );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return new FallbackJavaTypeDescriptor<T>( cls );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FallbackJavaTypeDescriptor<T> extends AbstractTypeDescriptor<T> {
|
public static class FallbackJavaTypeDescriptor<T> extends AbstractTypeDescriptor<T> {
|
||||||
|
|
|
@ -101,33 +101,48 @@ public class SerializableTypeDescriptor<T extends Serializable> extends Abstract
|
||||||
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
|
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
|
||||||
if ( value == null ) {
|
if ( value == null ) {
|
||||||
return 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 );
|
return (X) toBytes( value );
|
||||||
} else if ( InputStream.class.isAssignableFrom( type ) ) {
|
}
|
||||||
|
else if ( InputStream.class.isAssignableFrom( type ) ) {
|
||||||
return (X) new ByteArrayInputStream( toBytes( value ) );
|
return (X) new ByteArrayInputStream( toBytes( value ) );
|
||||||
} else if ( BinaryStream.class.isAssignableFrom( type ) ) {
|
}
|
||||||
|
else if ( BinaryStream.class.isAssignableFrom( type ) ) {
|
||||||
return (X) new BinaryStreamImpl( toBytes( value ) );
|
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) );
|
return (X) options.getLobCreator().createBlob( toBytes(value) );
|
||||||
}
|
}
|
||||||
|
|
||||||
throw unknownUnwrap( type );
|
throw unknownUnwrap( type );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <X> T wrap(X value, WrapperOptions options) {
|
public <X> T wrap(X value, WrapperOptions options) {
|
||||||
if ( value == null ) {
|
if ( value == null ) {
|
||||||
return null;
|
return null;
|
||||||
} else if ( byte[].class.isInstance( value ) ) {
|
}
|
||||||
|
else if ( byte[].class.isInstance( value ) ) {
|
||||||
return fromBytes( (byte[]) value );
|
return fromBytes( (byte[]) value );
|
||||||
} else if ( InputStream.class.isInstance( value ) ) {
|
}
|
||||||
|
else if ( InputStream.class.isInstance( value ) ) {
|
||||||
return fromBytes( DataHelper.extractBytes( (InputStream) value ) );
|
return fromBytes( DataHelper.extractBytes( (InputStream) value ) );
|
||||||
} else if ( Blob.class.isInstance( value )) {
|
}
|
||||||
|
else if ( Blob.class.isInstance( value )) {
|
||||||
try {
|
try {
|
||||||
return fromBytes( DataHelper.extractBytes( ( (Blob) value ).getBinaryStream() ) );
|
return fromBytes( DataHelper.extractBytes( ( (Blob) value ).getBinaryStream() ) );
|
||||||
} catch ( SQLException e ) {
|
}
|
||||||
|
catch ( SQLException e ) {
|
||||||
throw new HibernateException(e);
|
throw new HibernateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ( getJavaTypeClass().isInstance( value ) ) {
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
throw unknownWrap( value.getClass() );
|
throw unknownWrap( value.getClass() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,9 +41,12 @@ import org.jboss.logging.Logger;
|
||||||
import org.hibernate.mapping.Array;
|
import org.hibernate.mapping.Array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the recommendations
|
* Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the mappings contained here come
|
||||||
* 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"/>
|
* from the recommendations defined by the JDBC spec itself, as outlined at
|
||||||
* Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} contribute this information.
|
* <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
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,11 +28,16 @@ import javax.persistence.Convert;
|
||||||
import javax.persistence.Converter;
|
import javax.persistence.Converter;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.sql.Clob;
|
import java.sql.Clob;
|
||||||
|
import java.sql.Timestamp;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
|
||||||
import org.hibernate.IrrelevantEntity;
|
import org.hibernate.IrrelevantEntity;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
import org.hibernate.cfg.AttributeConverterDefinition;
|
import org.hibernate.cfg.AttributeConverterDefinition;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
|
@ -40,6 +45,7 @@ import org.hibernate.mapping.SimpleValue;
|
||||||
import org.hibernate.type.AbstractStandardBasicType;
|
import org.hibernate.type.AbstractStandardBasicType;
|
||||||
import org.hibernate.type.BasicType;
|
import org.hibernate.type.BasicType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor;
|
||||||
import org.hibernate.type.descriptor.java.StringTypeDescriptor;
|
import org.hibernate.type.descriptor.java.StringTypeDescriptor;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -75,7 +81,7 @@ public class AttributeConverterTest extends BaseUnitTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalOperation() {
|
public void testBasicConverterApplication() {
|
||||||
Configuration cfg = new Configuration();
|
Configuration cfg = new Configuration();
|
||||||
cfg.addAttributeConverter( StringClobConverter.class, true );
|
cfg.addAttributeConverter( StringClobConverter.class, true );
|
||||||
cfg.addAnnotatedClass( Tester.class );
|
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 {
|
public static class Tester {
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
private String name;
|
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 {
|
public static class Tester2 {
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
|
@ -123,14 +219,94 @@ public class AttributeConverterTest extends BaseUnitTestCase {
|
||||||
private String name;
|
private String name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity(name = "T3")
|
||||||
public static class Tester3 {
|
public static class Tester3 {
|
||||||
@Id
|
@Id
|
||||||
private Long id;
|
private Long id;
|
||||||
@org.hibernate.annotations.Type( type = "string" )
|
@org.hibernate.annotations.Type( type = "string" )
|
||||||
|
@Convert(disableConversion = true)
|
||||||
private String name;
|
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 )
|
@Converter( autoApply = true )
|
||||||
public static class StringClobConverter implements AttributeConverter<String,Clob> {
|
public static class StringClobConverter implements AttributeConverter<String,Clob> {
|
||||||
@Override
|
@Override
|
||||||
|
@ -143,4 +319,31 @@ public class AttributeConverterTest extends BaseUnitTestCase {
|
||||||
return null;
|
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() );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue