HHH-9615 - Allow AttributeConverter on attributes marked as Lob

This commit is contained in:
Steve Ebersole 2015-10-03 13:44:20 -05:00
parent eaf28166d2
commit 3ac508882c
4 changed files with 217 additions and 5 deletions

View File

@ -9,6 +9,7 @@ package org.hibernate.cfg.annotations;
import java.io.Serializable; import java.io.Serializable;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.persistence.Id; import javax.persistence.Id;
@ -73,6 +74,7 @@ public class SimpleValueBinder {
private String defaultType = ""; private String defaultType = "";
private Properties typeParameters = new Properties(); private Properties typeParameters = new Properties();
private boolean isNationalized; private boolean isNationalized;
private boolean isLob;
private Table table; private Table table;
private SimpleValue simpleValue; private SimpleValue simpleValue;
@ -134,6 +136,7 @@ public class SimpleValueBinder {
// we cannot guess anything // we cannot guess anything
return; return;
} }
XClass returnedClassOrElement = returnedClass; XClass returnedClassOrElement = returnedClass;
boolean isArray = false; boolean isArray = false;
if ( property.isArray() ) { if ( property.isArray() ) {
@ -203,6 +206,7 @@ public class SimpleValueBinder {
explicitType = type; explicitType = type;
} }
else if ( !key && property.isAnnotationPresent( Lob.class ) ) { else if ( !key && property.isAnnotationPresent( Lob.class ) ) {
isLob = true;
if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { if ( buildingContext.getBuildingOptions().getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) {
type = isNationalized type = isNationalized
? StandardBasicTypes.NCLOB.getName() ? StandardBasicTypes.NCLOB.getName()
@ -247,7 +251,7 @@ public class SimpleValueBinder {
else { else {
type = "blob"; type = "blob";
} }
explicitType = type; defaultType = type;
} }
else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) ) else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) )
|| ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) { || ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) {
@ -389,6 +393,9 @@ public class SimpleValueBinder {
if ( isNationalized ) { if ( isNationalized ) {
simpleValue.makeNationalized(); simpleValue.makeNationalized();
} }
if ( isLob ) {
simpleValue.makeLob();
}
linkWithValue(); linkWithValue();
@ -474,8 +481,21 @@ public class SimpleValueBinder {
} }
if ( persistentClassName != null || attributeConverterDefinition != null ) { if ( persistentClassName != null || attributeConverterDefinition != null ) {
try {
simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); 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() ) { if ( !simpleValue.isTypeSpecified() && isVersion() ) {
simpleValue.setTypeName( "integer" ); simpleValue.setTypeName( "integer" );

View File

@ -6,10 +6,13 @@
*/ */
package org.hibernate.mapping; package org.hibernate.mapping;
import java.io.Serializable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
@ -34,11 +37,13 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter; import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
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.JdbcTypeJavaClassMappings; 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.NationalizedTypeMappings;
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;
@ -60,6 +65,7 @@ public class SimpleValue implements KeyValue {
private String typeName; private String typeName;
private Properties typeParameters; private Properties typeParameters;
private boolean isNationalized; private boolean isNationalized;
private boolean isLob;
private Properties identifierGeneratorProperties; private Properties identifierGeneratorProperties;
private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
@ -168,6 +174,14 @@ public class SimpleValue implements KeyValue {
return isNationalized; return isNationalized;
} }
public void makeLob() {
this.isLob = true;
}
public boolean isLob() {
return isLob;
}
public void setTable(Table table) { public void setTable(Table table) {
this.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 // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return
// VARCHAR/CHAR // VARCHAR/CHAR
int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType ); 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() ) { if ( isNationalized() ) {
jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode ); jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode );
} }

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Integer,Integer> lobCodeByNonLobCode;
private LobTypeMappings() {
this.lobCodeByNonLobCode = new BoundedConcurrentHashMap<Integer, Integer>();
// 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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.
* <p/>
* 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<String, Integer> {
@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;
}
}