HHH-10818 - Allow AttributeConverter on attributes marked as Lob (REALLY this time)

This commit is contained in:
Steve Ebersole 2016-09-29 11:26:36 -05:00
parent b6aa191720
commit 9aa164ed27
16 changed files with 720 additions and 12 deletions

View File

@ -28,6 +28,7 @@ import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IdentityGenerator;
@ -565,11 +566,18 @@ public class SimpleValue implements KeyValue {
if ( isNationalized() ) {
jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode );
}
// find the standard SqlTypeDescriptor for that JDBC type code.
final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode );
// find the standard SqlTypeDescriptor for that JDBC type code (allow itr to be remapped if needed!)
final SqlTypeDescriptor sqlTypeDescriptor = metadata.getMetadataBuildingOptions().getServiceRegistry()
.getService( JdbcServices.class )
.getJdbcEnvironment()
.getDialect()
.remapSqlTypeDescriptor( SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode ) );
// 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(

View File

@ -55,10 +55,9 @@ public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescri
@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();
// any remapping of the underlying SqlTypeDescriptor should have
// happened prior to it being passed to us.
return false;
}

View File

@ -0,0 +1,73 @@
/*
* 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.caching;
import javax.persistence.Cacheable;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
* @author Steve Ebersole
*/
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Address {
@Id
private Integer id;
private String streetLine1;
private String streetLine2;
@Convert(converter = PostalAreaConverter.class)
private PostalArea postalArea;
public Address() {
}
public Address(
Integer id,
String streetLine1,
String streetLine2,
PostalArea postalArea) {
this.id = id;
this.streetLine1 = streetLine1;
this.streetLine2 = streetLine2;
this.postalArea = postalArea;
}
public Integer getId() {
return id;
}
public String getStreetLine1() {
return streetLine1;
}
public void setStreetLine1(String streetLine1) {
this.streetLine1 = streetLine1;
}
public String getStreetLine2() {
return streetLine2;
}
public void setStreetLine2(String streetLine2) {
this.streetLine2 = streetLine2;
}
public PostalArea getPostalArea() {
return postalArea;
}
public void setPostalArea(PostalArea postalArea) {
this.postalArea = postalArea;
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.caching;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.cache.CachingRegionFactory;
import org.hibernate.testing.cache.EntityRegionImpl;
import org.hibernate.testing.cache.ReadWriteEntityRegionAccessStrategy;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
public class BasicStructuredCachingOfConvertedValueTest extends BaseNonConfigCoreFunctionalTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9615" )
@SuppressWarnings("unchecked")
public void basicCacheStructureTest() {
EntityPersister persister = sessionFactory().getMetamodel().entityPersisters().get( Address.class.getName() );
EntityRegionImpl region = (EntityRegionImpl) persister.getCacheAccessStrategy().getRegion();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during store...
PostalAreaConverter.clearCounts();
Session session = openSession();
session.getTransaction().begin();
session.save( new Address( 1, "123 Main St.", null, PostalArea._78729 ) );
session.getTransaction().commit();
session.close();
{
final Object cachedItem = region.getDataMap().values().iterator().next();
final Map<String, ?> state = (Map) ( (ReadWriteEntityRegionAccessStrategy.Item) cachedItem ).getValue();
// this is the point of the Jira.. that this "should be" the converted value
assertThat( state.get( "postalArea" ), instanceOf( PostalArea.class ) );
}
assertThat( PostalAreaConverter.toDatabaseCallCount, is(1) );
assertThat( PostalAreaConverter.toDomainCallCount, is(0) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during load...
PostalAreaConverter.clearCounts();
region.evictAll();
session = openSession();
session.getTransaction().begin();
Address address = session.get( Address.class, 1 );
session.getTransaction().commit();
session.close();
{
final Object cachedItem = region.getDataMap().values().iterator().next();
final Map<String, ?> state = (Map) ( (ReadWriteEntityRegionAccessStrategy.Item) cachedItem ).getValue();
// this is the point of the Jira.. that this "should be" the converted value
assertThat( state.get( "postalArea" ), instanceOf( PostalArea.class ) );
}
assertThat( PostalAreaConverter.toDatabaseCallCount, is(0) );
assertThat( PostalAreaConverter.toDomainCallCount, is(1) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// cleanup
session = openSession();
session.getTransaction().begin();
session.delete( address );
session.getTransaction().commit();
session.close();
}
@Override
protected void addSettings(Map settings) {
super.addSettings( settings );
settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" );
settings.put( AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class );
settings.put( AvailableSettings.USE_STRUCTURED_CACHE, "true" );
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class };
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.caching;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.cache.spi.entry.StandardCacheEntryImpl;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.cache.CachingRegionFactory;
import org.hibernate.testing.cache.EntityRegionImpl;
import org.hibernate.testing.cache.ReadWriteEntityRegionAccessStrategy;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
public class BasicUnstructuredCachingOfConvertedValueTest extends BaseNonConfigCoreFunctionalTestCase {
public static final int postalAreaAttributeIndex = 0;
@Test
@TestForIssue( jiraKey = "HHH-9615" )
@SuppressWarnings("unchecked")
public void basicCacheStructureTest() {
EntityPersister persister = sessionFactory().getMetamodel().entityPersisters().get( Address.class.getName() );
EntityRegionImpl region = (EntityRegionImpl) persister.getCacheAccessStrategy().getRegion();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during store...
PostalAreaConverter.clearCounts();
Session session = openSession();
session.getTransaction().begin();
session.save( new Address( 1, "123 Main St.", null, PostalArea._78729 ) );
session.getTransaction().commit();
session.close();
{
final Object cachedItem = region.getDataMap().values().iterator().next();
final StandardCacheEntryImpl state = (StandardCacheEntryImpl) ( (ReadWriteEntityRegionAccessStrategy.Item) cachedItem ).getValue();
assertThat( state.getDisassembledState()[postalAreaAttributeIndex], instanceOf( PostalArea.class ) );
}
assertThat( PostalAreaConverter.toDatabaseCallCount, is(1) );
assertThat( PostalAreaConverter.toDomainCallCount, is(0) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during load...
PostalAreaConverter.clearCounts();
region.evictAll();
session = openSession();
session.getTransaction().begin();
Address address = session.get( Address.class, 1 );
session.getTransaction().commit();
session.close();
{
final Object cachedItem = region.getDataMap().values().iterator().next();
final StandardCacheEntryImpl state = (StandardCacheEntryImpl) ( (ReadWriteEntityRegionAccessStrategy.Item) cachedItem ).getValue();
assertThat( state.getDisassembledState()[postalAreaAttributeIndex], instanceOf( PostalArea.class ) );
}
assertThat( PostalAreaConverter.toDatabaseCallCount, is(0) );
assertThat( PostalAreaConverter.toDomainCallCount, is(1) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// cleanup
session = openSession();
session.getTransaction().begin();
session.delete( address );
session.getTransaction().commit();
session.close();
}
@Override
protected void addSettings(Map settings) {
super.addSettings( settings );
settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" );
settings.put( AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class );
settings.put( AvailableSettings.USE_STRUCTURED_CACHE, "false" );
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class };
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.caching;
import org.hibernate.annotations.Immutable;
/**
* @author Steve Ebersole
*/
@Immutable
public enum PostalArea {
_78729( "78729", "North Austin", "Austin", State.TX );
private final String zipCode;
private final String name;
private final String cityName;
private final State state;
PostalArea(
String zipCode,
String name,
String cityName,
State state) {
this.zipCode = zipCode;
this.name = name;
this.cityName = cityName;
this.state = state;
}
public static PostalArea fromZipCode(String zipCode) {
if ( _78729.zipCode.equals( zipCode ) ) {
return _78729;
}
throw new IllegalArgumentException( "Unknown zip code" );
}
public String getZipCode() {
return zipCode;
}
public String getName() {
return name;
}
public String getCityName() {
return cityName;
}
public State getState() {
return state;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.caching;
import javax.persistence.AttributeConverter;
/**
* @author Steve Ebersole
*/
public class PostalAreaConverter
implements AttributeConverter<PostalArea, String> {
static int toDatabaseCallCount = 0;
static int toDomainCallCount = 0;
@Override
public String convertToDatabaseColumn(PostalArea attribute) {
toDatabaseCallCount++;
if ( attribute == null ) {
return null;
}
else {
return attribute.getZipCode();
}
}
@Override
public PostalArea convertToEntityAttribute(String dbData) {
toDomainCallCount++;
if ( dbData == null ) {
return null;
}
return PostalArea.fromZipCode( dbData );
}
static void clearCounts() {
toDatabaseCallCount = 0;
toDomainCallCount = 0;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.caching;
import org.hibernate.annotations.Immutable;
/**
* @author Steve Ebersole
*/
@Immutable
public enum State {
TX( "TX", "Texas" );
private final String code;
private final String name;
State(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.lob;
import javax.persistence.Cacheable;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
* @author Steve Ebersole
*/
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Address {
@Id
Integer id;
String streetLine1;
String streetLine2;
@Lob
@Convert(converter = PostalAreaConverter.class)
PostalArea postalArea;
public Address() {
}
public Address(
Integer id,
String streetLine1,
String streetLine2,
PostalArea postalArea) {
this.id = id;
this.streetLine1 = streetLine1;
this.streetLine2 = streetLine2;
this.postalArea = postalArea;
}
public Integer getId() {
return id;
}
public String getStreetLine1() {
return streetLine1;
}
public void setStreetLine1(String streetLine1) {
this.streetLine1 = streetLine1;
}
public String getStreetLine2() {
return streetLine2;
}
public void setStreetLine2(String streetLine2) {
this.streetLine2 = streetLine2;
}
public PostalArea getPostalArea() {
return postalArea;
}
public void setPostalArea(PostalArea postalArea) {
this.postalArea = postalArea;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.lob;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
public class ConverterAndLobTest extends BaseNonConfigCoreFunctionalTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9615" )
@SuppressWarnings("unchecked")
public void basicTest() {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during store...
PostalAreaConverter.clearCounts();
Session session = openSession();
session.getTransaction().begin();
session.save( new Address( 1, "123 Main St.", null, PostalArea._78729 ) );
session.getTransaction().commit();
session.close();
assertThat( PostalAreaConverter.toDatabaseCallCount, is(1) );
assertThat( PostalAreaConverter.toDomainCallCount, is(0) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// test during load...
PostalAreaConverter.clearCounts();
session = openSession();
session.getTransaction().begin();
Address address = session.get( Address.class, 1 );
session.getTransaction().commit();
session.close();
assertThat( PostalAreaConverter.toDatabaseCallCount, is(0) );
assertThat( PostalAreaConverter.toDomainCallCount, is(1) );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// cleanup
session = openSession();
session.getTransaction().begin();
session.delete( address );
session.getTransaction().commit();
session.close();
}
@Override
protected void addSettings(Map settings) {
super.addSettings( settings );
settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" );
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class };
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.lob;
import org.hibernate.annotations.Immutable;
/**
* @author Steve Ebersole
*/
@Immutable
public enum PostalArea {
_78729( "78729", "North Austin", "Austin", State.TX );
private final String zipCode;
private final String name;
private final String cityName;
private final State state;
PostalArea(
String zipCode,
String name,
String cityName,
State state) {
this.zipCode = zipCode;
this.name = name;
this.cityName = cityName;
this.state = state;
}
public static PostalArea fromZipCode(String zipCode) {
if ( _78729.zipCode.equals( zipCode ) ) {
return _78729;
}
throw new IllegalArgumentException( "Unknown zip code" );
}
public String getZipCode() {
return zipCode;
}
public String getName() {
return name;
}
public String getCityName() {
return cityName;
}
public State getState() {
return state;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.lob;
import javax.persistence.AttributeConverter;
/**
* @author Steve Ebersole
*/
public class PostalAreaConverter
implements AttributeConverter<PostalArea, String> {
static int toDatabaseCallCount = 0;
static int toDomainCallCount = 0;
@Override
public String convertToDatabaseColumn(PostalArea attribute) {
toDatabaseCallCount++;
if ( attribute == null ) {
return null;
}
else {
return attribute.getZipCode();
}
}
@Override
public PostalArea convertToEntityAttribute(String dbData) {
toDomainCallCount++;
if ( dbData == null ) {
return null;
}
return PostalArea.fromZipCode( dbData );
}
static void clearCounts() {
toDatabaseCallCount = 0;
toDomainCallCount = 0;
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.lob;
import org.hibernate.annotations.Immutable;
/**
* @author Steve Ebersole
*/
@Immutable
public enum State {
TX( "TX", "Texas" );
private final String code;
private final String name;
State(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@ -21,7 +21,7 @@ import org.jboss.logging.Logger;
/**
* @author Strong Liu
*/
abstract class AbstractReadWriteAccessStrategy extends BaseRegionAccessStrategy {
public abstract class AbstractReadWriteAccessStrategy extends BaseRegionAccessStrategy {
private static final Logger LOG = Logger.getLogger( AbstractReadWriteAccessStrategy.class.getName() );
private final UUID uuid = UUID.randomUUID();
@ -181,7 +181,7 @@ abstract class AbstractReadWriteAccessStrategy extends BaseRegionAccessStrategy
/**
* Interface type implemented by all wrapper objects in the cache.
*/
protected interface Lockable {
public interface Lockable {
/**
* Returns <code>true</code> if the enclosed value can be read by a transaction started at the given time.
@ -214,7 +214,7 @@ abstract class AbstractReadWriteAccessStrategy extends BaseRegionAccessStrategy
/**
* Wrapper type representing unlocked items.
*/
protected final static class Item implements Serializable, Lockable {
public final static class Item implements Serializable, Lockable {
private static final long serialVersionUID = 1L;
private final Object value;
@ -259,7 +259,7 @@ abstract class AbstractReadWriteAccessStrategy extends BaseRegionAccessStrategy
/**
* Wrapper type representing locked items.
*/
protected final static class Lock implements Serializable, Lockable, SoftLock {
public final static class Lock implements Serializable, Lockable, SoftLock {
private static final long serialVersionUID = 2L;

View File

@ -16,7 +16,7 @@ import org.jboss.logging.Logger;
/**
* @author Strong Liu
*/
abstract class BaseRegionAccessStrategy implements RegionAccessStrategy {
public abstract class BaseRegionAccessStrategy implements RegionAccessStrategy {
private static final Logger LOG = Logger.getLogger( BaseRegionAccessStrategy.class );

View File

@ -20,7 +20,7 @@ import org.hibernate.persister.entity.EntityPersister;
/**
* @author Strong Liu
*/
class ReadWriteEntityRegionAccessStrategy extends AbstractReadWriteAccessStrategy
public class ReadWriteEntityRegionAccessStrategy extends AbstractReadWriteAccessStrategy
implements EntityRegionAccessStrategy {
private final EntityRegionImpl region;