HHH-7468 Recreate toString value holder after deserialization

This commit is contained in:
Eric Dalquist 2012-08-01 10:40:49 -05:00 committed by Strong Liu
parent b76e7c4987
commit e813d329bc
4 changed files with 109 additions and 21 deletions

View File

@ -102,7 +102,8 @@ libraries = [
shrinkwrap_api: 'org.jboss.shrinkwrap:shrinkwrap-api:1.0.0-beta-6', shrinkwrap_api: 'org.jboss.shrinkwrap:shrinkwrap-api:1.0.0-beta-6',
shrinkwrap: 'org.jboss.shrinkwrap:shrinkwrap-impl-base:1.0.0-beta-6', shrinkwrap: 'org.jboss.shrinkwrap:shrinkwrap-impl-base:1.0.0-beta-6',
validator: 'org.hibernate:hibernate-validator:4.2.0.Final', validator: 'org.hibernate:hibernate-validator:4.2.0.Final',
h2: "com.h2database:h2:${h2Version}" h2: "com.h2database:h2:${h2Version}",
mockito: 'org.mockito:mockito-core:1.9.0'
] ]

View File

@ -23,6 +23,7 @@ dependencies {
testCompile( libraries.validation ) testCompile( libraries.validation )
testCompile( libraries.jandex ) testCompile( libraries.jandex )
testCompile( libraries.classmate ) testCompile( libraries.classmate )
testCompile( libraries.mockito )
testCompile( libraries.validator ) { testCompile( libraries.validator ) {
// for test runtime // for test runtime
transitive = true transitive = true

View File

@ -23,6 +23,8 @@
*/ */
package org.hibernate.cache.spi; package org.hibernate.cache.spi;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
@ -44,7 +46,7 @@ public class NaturalIdCacheKey implements Serializable {
private final String entityName; private final String entityName;
private final String tenantId; private final String tenantId;
private final int hashCode; private final int hashCode;
private final transient ValueHolder<String> toString; private transient ValueHolder<String> toString;
/** /**
* Construct a new key for a caching natural identifier resolutions into the second level cache. * Construct a new key for a caching natural identifier resolutions into the second level cache.
@ -73,7 +75,8 @@ public class NaturalIdCacheKey implements Serializable {
result = prime * result + ( ( this.entityName == null ) ? 0 : this.entityName.hashCode() ); result = prime * result + ( ( this.entityName == null ) ? 0 : this.entityName.hashCode() );
result = prime * result + ( ( this.tenantId == null ) ? 0 : this.tenantId.hashCode() ); result = prime * result + ( ( this.tenantId == null ) ? 0 : this.tenantId.hashCode() );
for ( int i = 0; i < naturalIdValues.length; i++ ) { for ( int i = 0; i < naturalIdValues.length; i++ ) {
final Type type = propertyTypes[naturalIdPropertyIndexes[i]]; final int naturalIdPropertyIndex = naturalIdPropertyIndexes[i];
final Type type = propertyTypes[naturalIdPropertyIndex];
final Object value = naturalIdValues[i]; final Object value = naturalIdValues[i];
result = prime * result + (value != null ? type.getHashCode( value, factory ) : 0); result = prime * result + (value != null ? type.getHashCode( value, factory ) : 0);
@ -82,25 +85,29 @@ public class NaturalIdCacheKey implements Serializable {
} }
this.hashCode = result; this.hashCode = result;
this.toString = new ValueHolder<String>( initTransients();
new ValueHolder.DeferredInitializer<String>() { }
@Override
public String initialize() { private void initTransients() {
//Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys this.toString = new ValueHolder<String>(
//the only same way to differentiate the keys is to included the disassembled values in the string. new ValueHolder.DeferredInitializer<String>() {
final StringBuilder toStringBuilder = new StringBuilder( entityName ).append( "##NaturalId[" ); @Override
for ( int i = 0; i < naturalIdValues.length; i++ ) { public String initialize() {
toStringBuilder.append( naturalIdValues[i] ); //Complex toString is needed as naturalIds for entities are not simply based on a single value like primary keys
if ( i + 1 < naturalIdValues.length ) { //the only same way to differentiate the keys is to included the disassembled values in the string.
toStringBuilder.append( ", " ); final StringBuilder toStringBuilder = new StringBuilder( entityName ).append( "##NaturalId[" );
} for ( int i = 0; i < naturalIdValues.length; i++ ) {
} toStringBuilder.append( naturalIdValues[i] );
toStringBuilder.append( "]" ); if ( i + 1 < naturalIdValues.length ) {
toStringBuilder.append( ", " );
}
}
toStringBuilder.append( "]" );
return toStringBuilder.toString(); return toStringBuilder.toString();
} }
} }
); );
} }
@SuppressWarnings( {"UnusedDeclaration"}) @SuppressWarnings( {"UnusedDeclaration"})
@ -144,4 +151,10 @@ public class NaturalIdCacheKey implements Serializable {
&& EqualsHelper.equals( tenantId, other.tenantId ) && EqualsHelper.equals( tenantId, other.tenantId )
&& Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues ); && Arrays.deepEquals( this.naturalIdValues, other.naturalIdValues );
} }
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
initTransients();
}
} }

View File

@ -0,0 +1,73 @@
package org.hibernate.cache.spi;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.Type;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class NaturalIdCacheKeyTest {
@Test
public void testSerializationRoundTrip() throws Exception {
final EntityPersister entityPersister = mock(EntityPersister.class);
final SessionImplementor sessionImplementor = mock(SessionImplementor.class);
final SessionFactoryImplementor sessionFactoryImplementor = mock(SessionFactoryImplementor.class);
final Type mockType = mock(Type.class);
when (entityPersister.getRootEntityName()).thenReturn("EntityName");
when(sessionImplementor.getFactory()).thenReturn(sessionFactoryImplementor);
when(entityPersister.getNaturalIdentifierProperties()).thenReturn(new int[] {0, 1, 2});
when(entityPersister.getPropertyTypes()).thenReturn(new Type[] {
mockType,
mockType,
mockType
});
when(mockType.getHashCode(anyObject(), eq(sessionFactoryImplementor))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArguments()[0].hashCode();
}
});
when(mockType.disassemble(anyObject(), eq(sessionImplementor), eq(null))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArguments()[0];
}
});
final NaturalIdCacheKey key = new NaturalIdCacheKey(new Object[] {"a", "b", "c"}, entityPersister, sessionImplementor);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(key);
final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
final NaturalIdCacheKey keyClone = (NaturalIdCacheKey)ois.readObject();
assertEquals(key, keyClone);
assertEquals(key.hashCode(), keyClone.hashCode());
assertEquals(key.toString(), keyClone.toString());
assertEquals(key.getEntityName(), keyClone.getEntityName());
assertArrayEquals(key.getNaturalIdValues(), keyClone.getNaturalIdValues());
assertEquals(key.getTenantId(), keyClone.getTenantId());
}
}