diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index 451eb2bd58..f70b186d04 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -9,6 +9,7 @@ package org.hibernate.cfg.annotations; import java.io.Serializable; import java.util.Calendar; import java.util.Date; +import java.util.Locale; import java.util.Properties; import javax.persistence.Enumerated; import javax.persistence.Id; @@ -73,6 +74,7 @@ public class SimpleValueBinder { private String defaultType = ""; private Properties typeParameters = new Properties(); private boolean isNationalized; + private boolean isLob; private Table table; private SimpleValue simpleValue; @@ -134,8 +136,9 @@ public class SimpleValueBinder { // we cannot guess anything return; } + XClass returnedClassOrElement = returnedClass; - boolean isArray = false; + boolean isArray = false; if ( property.isArray() ) { returnedClassOrElement = property.getElementClass(); isArray = true; @@ -203,6 +206,7 @@ public class SimpleValueBinder { explicitType = type; } else if ( !key && property.isAnnotationPresent( Lob.class ) ) { + isLob = true; if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { type = isNationalized ? StandardBasicTypes.NCLOB.getName() @@ -247,7 +251,7 @@ public class SimpleValueBinder { else { type = "blob"; } - explicitType = type; + defaultType = type; } else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) ) || ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) { @@ -389,6 +393,9 @@ public class SimpleValueBinder { if ( isNationalized ) { simpleValue.makeNationalized(); } + if ( isLob ) { + simpleValue.makeLob(); + } linkWithValue(); @@ -474,7 +481,20 @@ public class SimpleValueBinder { } if ( persistentClassName != null || attributeConverterDefinition != null ) { - simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); + try { + simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); + } + catch (Exception e) { + throw new MappingException( + String.format( + Locale.ROOT, + "Unable to determine basic type mapping via reflection [%s -> %s]", + persistentClassName, + propertyName + ), + e + ); + } } if ( !simpleValue.isTypeSpecified() && isVersion() ) { 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 2bb1d2da1d..4eb21fe2b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -6,10 +6,13 @@ */ package org.hibernate.mapping; +import java.io.Serializable; import java.lang.annotation.Annotation; +import java.sql.Types; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Properties; import javax.persistence.AttributeConverter; @@ -34,11 +37,13 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.JdbcTypeNameMapper; import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter; import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry; import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings; +import org.hibernate.type.descriptor.sql.LobTypeMappings; import org.hibernate.type.descriptor.sql.NationalizedTypeMappings; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry; @@ -60,6 +65,7 @@ public class SimpleValue implements KeyValue { private String typeName; private Properties typeParameters; private boolean isNationalized; + private boolean isLob; private Properties identifierGeneratorProperties; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; @@ -104,11 +110,11 @@ public class SimpleValue implements KeyValue { columns.add(column); } column.setValue(this); - column.setTypeIndex( columns.size()-1 ); + column.setTypeIndex( columns.size() - 1 ); } public void addFormula(Formula formula) { - columns.add(formula); + columns.add( formula ); } @Override @@ -168,6 +174,14 @@ public class SimpleValue implements KeyValue { return isNationalized; } + public void makeLob() { + this.isLob = true; + } + + public boolean isLob() { + return isLob; + } + public void setTable(Table table) { this.table = table; } @@ -478,6 +492,26 @@ public class SimpleValue implements KeyValue { // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return // VARCHAR/CHAR int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType ); + if ( isLob() ) { + if ( LobTypeMappings.INSTANCE.hasCorrespondingLobCode( jdbcTypeCode ) ) { + jdbcTypeCode = LobTypeMappings.INSTANCE.getCorrespondingLobCode( jdbcTypeCode ); + } + else { + if ( Serializable.class.isAssignableFrom( entityAttributeJavaType ) ) { + jdbcTypeCode = Types.BLOB; + } + else { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent, and Java type is not Serializable (to use BLOB)", + jdbcTypeCode, + JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) + ) + ); + } + } + } if ( isNationalized() ) { jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java new file mode 100644 index 0000000000..ce5694cf39 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LobTypeMappings.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.Types; +import java.util.Locale; +import java.util.Map; + +import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; +import org.hibernate.type.descriptor.JdbcTypeNameMapper; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class LobTypeMappings { + private static final Logger log = Logger.getLogger( LobTypeMappings.class ); + + /** + * Singleton access + */ + public static final LobTypeMappings INSTANCE = new LobTypeMappings(); + + private final Map lobCodeByNonLobCode; + + private LobTypeMappings() { + this.lobCodeByNonLobCode = new BoundedConcurrentHashMap(); + + // BLOB mappings + this.lobCodeByNonLobCode.put( Types.BLOB, Types.BLOB ); + this.lobCodeByNonLobCode.put( Types.BINARY, Types.BLOB ); + this.lobCodeByNonLobCode.put( Types.VARBINARY, Types.BLOB ); + this.lobCodeByNonLobCode.put( Types.LONGVARBINARY, Types.BLOB ); + + // CLOB mappings + this.lobCodeByNonLobCode.put( Types.CLOB, Types.CLOB ); + this.lobCodeByNonLobCode.put( Types.CHAR, Types.CLOB ); + this.lobCodeByNonLobCode.put( Types.VARCHAR, Types.CLOB ); + this.lobCodeByNonLobCode.put( Types.LONGVARCHAR, Types.CLOB ); + + // NCLOB mappings + this.lobCodeByNonLobCode.put( Types.NCLOB, Types.NCLOB ); + this.lobCodeByNonLobCode.put( Types.NCHAR, Types.NCLOB ); + this.lobCodeByNonLobCode.put( Types.NVARCHAR, Types.NCLOB ); + this.lobCodeByNonLobCode.put( Types.LONGNVARCHAR, Types.NCLOB ); + } + + public boolean hasCorrespondingLobCode(int jdbcTypeCode) { + return lobCodeByNonLobCode.containsKey( jdbcTypeCode ); + } + + public int getCorrespondingLobCode(int jdbcTypeCode) { + Integer lobTypeCode = lobCodeByNonLobCode.get( jdbcTypeCode ); + if ( lobTypeCode == null ) { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent", + jdbcTypeCode, + JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) + ) + ); + } + return lobTypeCode; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/AndLobTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/AndLobTest.java new file mode 100644 index 0000000000..953d8882ab --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/AndLobTest.java @@ -0,0 +1,87 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.converter; + +import java.sql.Types; +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Lob; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.type.Type; +import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; + +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; + +/** + * Test mapping a model with an attribute combining {@code @Lob} with an AttributeConverter. + *

+ * Originally developed to diagnose HHH-9615 + * + * @author Steve Ebersole + */ +public class AndLobTest extends BaseUnitTestCase { + private StandardServiceRegistry ssr; + + @Before + public void before() { + ssr = new StandardServiceRegistryBuilder().build(); + } + + @After + public void after() { + if ( ssr != null ) { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } + + @Test + public void testMappingAttributeWithLobAndAttributeConverter() { + final Metadata metadata = new MetadataSources( ssr ) + .addAnnotatedClass( EntityImpl.class ) + .buildMetadata(); + + final Type type = metadata.getEntityBinding( EntityImpl.class.getName() ).getProperty( "status" ).getType(); + final AttributeConverterTypeAdapter concreteType = assertTyping( AttributeConverterTypeAdapter.class, type ); + assertEquals( Types.BLOB, concreteType.getSqlTypeDescriptor().getSqlType() ); + } + + @Converter + public static class ConverterImpl implements AttributeConverter { + @Override + public Integer convertToDatabaseColumn(String attribute) { + return attribute.length(); + } + + @Override + public String convertToEntityAttribute(Integer dbData) { + return ""; + } + } + + @Entity + public static class EntityImpl { + @Id + private Integer id; + + @Lob + @Convert(converter = ConverterImpl.class) + private String status; + } +}