HHH-11098 - Attributeconverter converttoEntityAttribute and convertToDatabaseColumn code is called within a query.list() call -> slow

This commit is contained in:
Steve Ebersole 2016-09-14 10:24:51 -05:00
parent 4bf4e3b81a
commit 81aba17771
1 changed files with 317 additions and 0 deletions

View File

@ -0,0 +1,317 @@
/*
* 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 javax.persistence.AttributeConverter;
import javax.persistence.Cacheable;
import javax.persistence.Convert;
import javax.persistence.Converter;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.Immutable;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
public class ExplicitJavaTypeDescriptorTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected void configureBootstrapServiceRegistryBuilder(BootstrapServiceRegistryBuilder bsrb) {
super.configureBootstrapServiceRegistryBuilder( bsrb );
// Let's tell Hibernate to treat MutableState2 as immutable
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(
new JavaTypeDescriptorRegistry.FallbackJavaTypeDescriptor( MutableState2.class ) {
@Override
public MutabilityPlan getMutabilityPlan() {
return ImmutableMutabilityPlan.INSTANCE;
}
}
);
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" );
ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" );
}
@Override
protected void applyMetadataSources(MetadataSources metadataSources) {
super.applyMetadataSources( metadataSources );
metadataSources.addAnnotatedClass( TheEntity.class );
}
@Test
@TestForIssue( jiraKey = "HHH-11098" )
public void testIt() {
// set up test data
Session session = openSession();
session.beginTransaction();
session.persist( new TheEntity(1) );
session.getTransaction().commit();
session.close();
// assertions based on the persist call
assertThat( mutableToDomainCallCount, is(1) ); // 1 instead of 0 because of the deep copy call
assertThat( mutableToDatabaseCallCount, is(2) ); // 2 instead of 1 because of the deep copy call
assertThat( immutableToDomainCallCount, is(0) ); // logical
assertThat( immutableToDatabaseCallCount, is(1) ); // logical
assertThat( immutableMutableToDomainCallCount, is(0) ); // was 1 (like mutable) before the JavaTypeDescriptor registration
assertThat( immutableMutableToDatabaseCallCount, is(1) ); // was 2 (like mutable) before the JavaTypeDescriptor registration
// clean up test data
session = openSession();
session.beginTransaction();
session.delete( session.byId( TheEntity.class ).getReference( 1 ) );
session.getTransaction().commit();
session.close();
}
@Entity( name = "TheEntity")
@Table( name = "T_ENTITY" )
@Cacheable
public static class TheEntity {
@Id
private Integer id;
@Convert( converter = MutableConverterImpl.class )
private MutableState mutableState;
@Convert( converter = ImmutableConverterImpl.class )
private ImmutableState immutableState;
@Convert( converter = ImmutableMutable2ConverterImpl.class )
private MutableState2 immutableMutableState;
public TheEntity() {
}
public TheEntity(Integer id) {
this.id = id;
this.mutableState = new MutableState( id.toString() );
this.immutableState = new ImmutableState( id.toString() );
this.immutableMutableState = new MutableState2( id.toString() );
}
}
@Before
public void clearCounts() {
// in case we add additional tests
sessionFactory().getStatistics().clear();
mutableToDatabaseCallCount = 0;
mutableToDomainCallCount = 0;
immutableToDatabaseCallCount = 0;
immutableToDomainCallCount = 0;
immutableMutableToDatabaseCallCount = 0;
immutableMutableToDomainCallCount = 0;
}
private static int mutableToDatabaseCallCount;
private static int mutableToDomainCallCount;
private static int immutableToDatabaseCallCount;
private static int immutableToDomainCallCount;
private static int immutableMutableToDatabaseCallCount;
private static int immutableMutableToDomainCallCount;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purely mutable state
public static class MutableState {
private String state;
public MutableState(String state) {
this.state = state;
}
public String getState() {
return state;
}
// mutable
public void setState(String state) {
this.state = state;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
MutableState that = (MutableState) o;
return getState() != null ? getState().equals( that.getState() ) : that.getState() == null;
}
@Override
public int hashCode() {
return getState() != null ? getState().hashCode() : 0;
}
}
@Converter
public static class MutableConverterImpl implements AttributeConverter<MutableState,String> {
@Override
public String convertToDatabaseColumn(MutableState attribute) {
mutableToDatabaseCallCount++;
return attribute == null ? null : attribute.getState();
}
@Override
public MutableState convertToEntityAttribute(String dbData) {
mutableToDomainCallCount++;
return new MutableState( dbData );
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purely immutable state
@Immutable
public static class ImmutableState {
private final String state;
public ImmutableState(String state) {
this.state = state;
}
public String getState() {
return state;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
ImmutableState that = (ImmutableState) o;
return getState().equals( that.getState() );
}
@Override
public int hashCode() {
return getState().hashCode();
}
}
@Converter
public static class ImmutableConverterImpl implements AttributeConverter<ImmutableState,String> {
@Override
public String convertToDatabaseColumn(ImmutableState attribute) {
immutableToDatabaseCallCount++;
return attribute == null ? null : attribute.getState();
}
@Override
public ImmutableState convertToEntityAttribute(String dbData) {
immutableToDomainCallCount++;
return new ImmutableState( dbData );
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Mutable state we treat as immutable
public static class MutableState2 {
private String state;
public MutableState2(String state) {
this.state = state;
}
public String getState() {
return state;
}
// mutable
public void setState(String state) {
// just a safety net - the idea is that the user is promising to not mutate the internal state
throw new UnsupportedOperationException( "illegal attempt to mutate state" );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
MutableState2 that = (MutableState2) o;
return getState() != null ? getState().equals( that.getState() ) : that.getState() == null;
}
@Override
public int hashCode() {
return getState() != null ? getState().hashCode() : 0;
}
}
@Converter
public static class ImmutableMutable2ConverterImpl implements AttributeConverter<MutableState2,String> {
@Override
public String convertToDatabaseColumn(MutableState2 attribute) {
immutableMutableToDatabaseCallCount++;
return attribute == null ? null : attribute.getState();
}
@Override
public MutableState2 convertToEntityAttribute(String dbData) {
immutableMutableToDomainCallCount++;
return new MutableState2( dbData );
}
}
}