HHH-12720 Allow lazy loading outside of a transaction after proxy deserialization if the proper settings were enabled

This commit is contained in:
Yoann Rodière 2018-07-04 13:06:06 +02:00 committed by Guillaume Smet
parent 500edf4b8a
commit 3336489e40
8 changed files with 126 additions and 19 deletions

View File

@ -13,6 +13,7 @@ import org.hibernate.HibernateException;
import org.hibernate.LazyInitializationException;
import org.hibernate.SessionException;
import org.hibernate.TransientObjectException;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -232,6 +233,13 @@ public abstract class AbstractLazyInitializer implements LazyInitializer {
}
}
/**
* 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.
*
* This method only has any effect if
* {@link SessionFactoryOptions#isInitializeLazyStateOutsideTransactionsEnabled()} is {@code true}.
*/
protected void prepareForPossibleLoadingOutsideTransaction() {
if ( session != null ) {
allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled();
@ -362,25 +370,57 @@ public abstract class AbstractLazyInitializer implements LazyInitializer {
}
/**
* Set the read-only/modifiable setting that should be put in affect when it is
* attached to a session.
* <p/>
* Get whether the proxy can load data even
* if it's not attached to a session with an ongoing transaction.
*
* This method should only be called during serialization,
* and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}.
*
* @return {@code true} if out-of-transaction loads are allowed, {@code false} otherwise.
*/
protected boolean isAllowLoadOutsideTransaction() {
return allowLoadOutsideTransaction;
}
/**
* Get the session factory UUID.
*
* This method should only be called during serialization,
* and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}.
*
* @return the session factory UUID.
*/
protected String getSessionFactoryUuid() {
return sessionFactoryUuid;
}
/**
* Restore settings that are not passed to the constructor,
* but are still preserved during serialization.
*
* This method should only be called during deserialization, before associating
* the proxy with a session.
*
* @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when
* associated with a session; null indicates that the default should be used.
* @param sessionFactoryUuid the session factory uuid, to be used if {@code allowLoadOutsideTransaction} is {@code true}.
* @param allowLoadOutsideTransaction whether the proxy can load data even
* if it's not attached to a session with an ongoing transaction.
*
* @throws IllegalStateException if isReadOnlySettingAvailable() == true
*/
/* package-private */
final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) {
final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession,
String sessionFactoryUuid, boolean allowLoadOutsideTransaction) {
if ( isReadOnlySettingAvailable() ) {
throw new IllegalStateException(
"Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]"
"Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]"
);
}
this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession;
this.sessionFactoryUuid = sessionFactoryUuid;
this.allowLoadOutsideTransaction = allowLoadOutsideTransaction;
}
@Override

View File

@ -5,6 +5,7 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.proxy;
import java.io.Serializable;
/**
@ -16,6 +17,8 @@ public abstract class AbstractSerializableProxy implements Serializable {
private String entityName;
private Serializable id;
private Boolean readOnly;
private String sessionFactoryUuid;
private boolean allowLoadOutsideTransaction;
/**
* For serialization
@ -23,10 +26,21 @@ public abstract class AbstractSerializableProxy implements Serializable {
protected AbstractSerializableProxy() {
}
/**
* @deprecated use {@link #AbstractSerializableProxy(String, Serializable, Boolean, String, boolean)} instead.
*/
@Deprecated
protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly) {
this( entityName, id, readOnly, null, false );
}
protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly,
String sessionFactoryUuid, boolean allowLoadOutsideTransaction) {
this.entityName = entityName;
this.id = id;
this.readOnly = readOnly;
this.sessionFactoryUuid = sessionFactoryUuid;
this.allowLoadOutsideTransaction = allowLoadOutsideTransaction;
}
protected String getEntityName() {
@ -46,8 +60,23 @@ public abstract class AbstractSerializableProxy implements Serializable {
* @param li the read-only/modifiable setting to use when
* associated with a session; null indicates that the default should be used.
* @throws IllegalStateException if isReadOnlySettingAvailable() == true
*
* @deprecated Use {@link #afterDeserialization(AbstractLazyInitializer)} instead.
*/
@Deprecated
protected void setReadOnlyBeforeAttachedToSession(AbstractLazyInitializer li) {
li.setReadOnlyBeforeAttachedToSession( readOnly );
li.afterDeserialization( readOnly, null, false );
}
/**
* Initialize an {@link AbstractLazyInitializer} after deserialization.
*
* This method should only be called during deserialization,
* before associating the AbstractLazyInitializer with a session.
*
* @param li the {@link AbstractLazyInitializer} to initialize.
*/
protected void afterDeserialization(AbstractLazyInitializer li) {
li.afterDeserialization( readOnly, sessionFactoryUuid, allowLoadOutsideTransaction );
}
}

View File

@ -105,6 +105,7 @@ public abstract class BasicLazyInitializer extends AbstractLazyInitializer {
if ( isUninitialized() ) {
if ( replacement == null ) {
prepareForPossibleLoadingOutsideTransaction();
replacement = serializableProxy();
}
return replacement;

View File

@ -17,13 +17,6 @@ import org.hibernate.proxy.ProxyConfiguration;
import org.hibernate.proxy.pojo.BasicLazyInitializer;
import org.hibernate.type.CompositeType;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.StubValue;
import net.bytebuddy.implementation.bind.annotation.This;
import static org.hibernate.internal.CoreLogging.messageLogger;
public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor {
@ -94,6 +87,8 @@ public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyC
interfaces,
getIdentifier(),
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
getSessionFactoryUuid(),
isAllowLoadOutsideTransaction(),
getIdentifierMethod,
setIdentifierMethod,
componentIdType

View File

@ -26,6 +26,10 @@ public final class SerializableProxy extends AbstractSerializableProxy {
private final CompositeType componentIdType;
/**
* @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead.
*/
@Deprecated
public SerializableProxy(
String entityName,
Class persistentClass,
@ -35,7 +39,24 @@ public final class SerializableProxy extends AbstractSerializableProxy {
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType) {
super( entityName, id, readOnly );
this(
entityName, persistentClass, interfaces, id, readOnly, null, false,
getIdentifierMethod, setIdentifierMethod, componentIdType
);
}
public SerializableProxy(
String entityName,
Class persistentClass,
Class[] interfaces,
Serializable id,
Boolean readOnly,
String sessionFactoryUuid,
boolean allowLoadOutsideTransaction,
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType) {
super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction );
this.persistentClass = persistentClass;
this.interfaces = interfaces;
if ( getIdentifierMethod != null ) {
@ -105,7 +126,7 @@ public final class SerializableProxy extends AbstractSerializableProxy {
private Object readResolve() {
HibernateProxy proxy = ByteBuddyProxyFactory.deserializeProxy( this );
setReadOnlyBeforeAttachedToSession( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() );
afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() );
return proxy;
}
}

View File

@ -125,6 +125,8 @@ public class JavassistLazyInitializer extends BasicLazyInitializer implements Me
interfaces,
getIdentifier(),
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
getSessionFactoryUuid(),
isAllowLoadOutsideTransaction(),
getIdentifierMethod,
setIdentifierMethod,
componentIdType

View File

@ -29,6 +29,10 @@ public final class SerializableProxy extends AbstractSerializableProxy {
private final CompositeType componentIdType;
/**
* @deprecated use {@link #SerializableProxy(String, Class, Class[], Serializable, Boolean, String, boolean, Method, Method, CompositeType)} instead.
*/
@Deprecated
public SerializableProxy(
String entityName,
Class persistentClass,
@ -38,7 +42,24 @@ public final class SerializableProxy extends AbstractSerializableProxy {
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType) {
super( entityName, id, readOnly );
this(
entityName, persistentClass, interfaces, id, readOnly, null, false,
getIdentifierMethod, setIdentifierMethod, componentIdType
);
}
public SerializableProxy(
String entityName,
Class persistentClass,
Class[] interfaces,
Serializable id,
Boolean readOnly,
String sessionFactoryUuid,
boolean allowLoadOutsideTransaction,
Method getIdentifierMethod,
Method setIdentifierMethod,
CompositeType componentIdType) {
super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction );
this.persistentClass = persistentClass;
this.interfaces = interfaces;
if ( getIdentifierMethod != null ) {
@ -114,7 +135,7 @@ public final class SerializableProxy extends AbstractSerializableProxy {
*/
private Object readResolve() {
HibernateProxy proxy = JavassistProxyFactory.deserializeProxy( this );
setReadOnlyBeforeAttachedToSession( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() );
afterDeserialization( ( JavassistLazyInitializer ) proxy.getHibernateLazyInitializer() );
return proxy;
}
}

View File

@ -31,7 +31,6 @@ import org.hibernate.internal.util.SerializationHelper;
import org.hibernate.proxy.AbstractLazyInitializer;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
@ -139,7 +138,6 @@ public class EntityProxySerializationTest extends BaseCoreFunctionalTestCase {
@SuppressWarnings("unchecked")
@Test
@TestForIssue(jiraKey = "HHH-12720")
@FailureExpected(jiraKey = "HHH-12720")
public void testProxyInitializationWithoutTXAfterDeserialization() {
final Session s = openSession();