HHH-7686 Clarify and test initialization code in the writeReplace() method in proxies

If we copy the behavior of "traditional" (non-map) proxies to the
"dynamic-map" proxies, we'd better know what this behavior is and be
sure it works correctly.
This commit is contained in:
Yoann Rodière 2018-07-05 09:41:04 +02:00 committed by Guillaume Smet
parent e0900b17e2
commit f2b4aedc03
3 changed files with 106 additions and 12 deletions

View File

@ -233,6 +233,26 @@ public abstract class AbstractLazyInitializer implements LazyInitializer {
} }
} }
/**
* Attempt to initialize the proxy without loading anything from the database.
*
* This will only have any effect if the proxy is still attached to a session,
* and the entity being proxied has been loaded and added to the persistence context
* of that session since the proxy was created.
*/
protected final void initializeWithoutLoadIfPossible() {
if ( !initialized && session != null && session.isOpen() ) {
final EntityKey key = session.generateEntityKey(
getIdentifier(),
session.getFactory().getMetamodel().entityPersister( getEntityName() )
);
final Object entity = session.getPersistenceContext().getEntity( key );
if ( entity != null ) {
setImplementation( entity );
}
}
}
/** /**
* Initialize internal state based on the currently attached session, * Initialize internal state based on the currently attached session,
* in order to be ready to load data even after the proxy is detached from the session. * in order to be ready to load data even after the proxy is detached from the session.

View File

@ -9,7 +9,6 @@ package org.hibernate.proxy.pojo;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.MarkerObject; import org.hibernate.internal.util.MarkerObject;
import org.hibernate.proxy.AbstractLazyInitializer; import org.hibernate.proxy.AbstractLazyInitializer;
@ -91,17 +90,11 @@ public abstract class BasicLazyInitializer extends AbstractLazyInitializer {
} }
private Object getReplacement() { private Object getReplacement() {
final SharedSessionContractImplementor session = getSession(); /*
if ( isUninitialized() && session != null && session.isOpen() ) { * If the target has already been loaded somewhere, just not set on the proxy,
final EntityKey key = session.generateEntityKey( * then use it to initialize the proxy so that we will serialize that instead of the proxy.
getIdentifier(), */
session.getFactory().getMetamodel().entityPersister( getEntityName() ) initializeWithoutLoadIfPossible();
);
final Object entity = session.getPersistenceContext().getEntity( key );
if ( entity != null ) {
setImplementation( entity );
}
}
if ( isUninitialized() ) { if ( isUninitialized() ) {
if ( replacement == null ) { if ( replacement == null ) {

View File

@ -8,6 +8,7 @@ package org.hibernate.serialization;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType; import javax.persistence.FetchType;
@ -94,6 +95,86 @@ public class EntityProxySerializationTest extends BaseCoreFunctionalTestCase {
} }
} }
/**
* Tests that serializing an initialized proxy will serialize the target instead.
*/
@SuppressWarnings("unchecked")
@Test
public void testInitializedProxySerializationIfTargetInPersistenceContext() {
final Session s = openSession();
final Transaction t = s.beginTransaction();
try {
final ChildEntity child = s.find( ChildEntity.class, 1L );
final SimpleEntity parent = child.getParent();
// assert we have an uninitialized proxy
assertTrue( parent instanceof HibernateProxy );
assertFalse( Hibernate.isInitialized( parent ) );
// Initialize the proxy
parent.getName();
assertTrue( Hibernate.isInitialized( parent ) );
// serialize/deserialize the proxy
final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent );
// assert the deserialized object is no longer a proxy, but the target of the proxy
assertFalse( deserializedParent instanceof HibernateProxy );
assertEquals( "TheParent", deserializedParent.getName() );
}
finally {
if ( t.isActive() ) {
t.rollback();
}
s.close();
}
}
/**
* Tests that serializing a proxy which is not initialized
* but whose target has been (separately) added to the persistence context
* will serialize the target instead.
*/
@SuppressWarnings("unchecked")
@Test
public void testUninitializedProxySerializationIfTargetInPersistenceContext() {
final Session s = openSession();
final Transaction t = s.beginTransaction();
try {
final ChildEntity child = s.find( ChildEntity.class, 1L );
final SimpleEntity parent = child.getParent();
// assert we have an uninitialized proxy
assertTrue( parent instanceof HibernateProxy );
assertFalse( Hibernate.isInitialized( parent ) );
// Load the target of the proxy without the proxy being made aware of it
s.detach( parent );
s.find( SimpleEntity.class, 1L );
s.update( parent );
// assert we still have an uninitialized proxy
assertFalse( Hibernate.isInitialized( parent ) );
// serialize/deserialize the proxy
final SimpleEntity deserializedParent = (SimpleEntity) SerializationHelper.clone( parent );
// assert the deserialized object is no longer a proxy, but the target of the proxy
assertFalse( deserializedParent instanceof HibernateProxy );
assertEquals( "TheParent", deserializedParent.getName() );
}
finally {
if ( t.isActive() ) {
t.rollback();
}
s.close();
}
}
/** /**
* Tests that lazy loading without transaction nor open session is generally * Tests that lazy loading without transaction nor open session is generally
* working. The magic is done by {@link AbstractLazyInitializer} who opens a * working. The magic is done by {@link AbstractLazyInitializer} who opens a