Skip to content

Commit

Permalink
HHH-9615 - Allow AttributeConverter on attributes marked as Lob
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Oct 3, 2015
1 parent eaf2816 commit 3ac5088
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 5 deletions.
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -134,8 +136,9 @@ public void setType(XProperty property, XClass returnedClass, String declaringCl
// we cannot guess anything
return;
}

XClass returnedClassOrElement = returnedClass;
boolean isArray = false;
boolean isArray = false;
if ( property.isArray() ) {
returnedClassOrElement = property.getElementClass();
isArray = true;
Expand Down Expand Up @@ -203,6 +206,7 @@ else if ( buildingContext.getBuildingOptions().getReflectionManager().equals( re
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()
Expand Down Expand Up @@ -247,7 +251,7 @@ else if ( buildingContext.getBuildingOptions().getReflectionManager()
else {
type = "blob";
}
explicitType = type;
defaultType = type;
}
else if ( ( !key && property.isAnnotationPresent( Enumerated.class ) )
|| ( key && property.isAnnotationPresent( MapKeyEnumerated.class ) ) ) {
Expand Down Expand Up @@ -389,6 +393,9 @@ public SimpleValue make() {
if ( isNationalized ) {
simpleValue.makeNationalized();
}
if ( isLob ) {
simpleValue.makeLob();
}

linkWithValue();

Expand Down Expand Up @@ -474,7 +481,20 @@ public void fillSimpleValue() {
}

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() ) {
Expand Down
Expand Up @@ -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;

Expand All @@ -34,11 +37,13 @@
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;
Expand All @@ -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;
Expand Down Expand Up @@ -104,11 +110,11 @@ public void addColumn(Column column) {
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
Expand Down Expand Up @@ -168,6 +174,14 @@ public boolean isNationalized() {
return isNationalized;
}

public void makeLob() {
this.isLob = true;
}

public boolean isLob() {
return isLob;
}

public void setTable(Table table) {
this.table = table;
}
Expand Down Expand Up @@ -478,6 +492,26 @@ private Type buildAttributeConverterTypeAdapter() {
// 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 );
}
Expand Down
@@ -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;
}
}
@@ -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;
}
}

0 comments on commit 3ac5088

Please sign in to comment.