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.LazyInitializationException;
import org.hibernate.SessionException; import org.hibernate.SessionException;
import org.hibernate.TransientObjectException; import org.hibernate.TransientObjectException;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; 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() { protected void prepareForPossibleLoadingOutsideTransaction() {
if ( session != null ) { if ( session != null ) {
allowLoadOutsideTransaction = session.getFactory().getSessionFactoryOptions().isInitializeLazyStateOutsideTransactionsEnabled(); 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 * Get whether the proxy can load data even
* attached to a session. * if it's not attached to a session with an ongoing transaction.
* <p/> *
* 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 * This method should only be called during deserialization, before associating
* the proxy with a session. * the proxy with a session.
* *
* @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when * @param readOnlyBeforeAttachedToSession, the read-only/modifiable setting to use when
* associated with a session; null indicates that the default should be used. * 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 * @throws IllegalStateException if isReadOnlySettingAvailable() == true
*/ */
/* package-private */ /* package-private */
final void setReadOnlyBeforeAttachedToSession(Boolean readOnlyBeforeAttachedToSession) { final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession,
String sessionFactoryUuid, boolean allowLoadOutsideTransaction) {
if ( isReadOnlySettingAvailable() ) { if ( isReadOnlySettingAvailable() ) {
throw new IllegalStateException( throw new IllegalStateException(
"Cannot call setReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]"
); );
} }
this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession;
this.sessionFactoryUuid = sessionFactoryUuid;
this.allowLoadOutsideTransaction = allowLoadOutsideTransaction;
} }
@Override @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>. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/ */
package org.hibernate.proxy; package org.hibernate.proxy;
import java.io.Serializable; import java.io.Serializable;
/** /**
@ -16,6 +17,8 @@ public abstract class AbstractSerializableProxy implements Serializable {
private String entityName; private String entityName;
private Serializable id; private Serializable id;
private Boolean readOnly; private Boolean readOnly;
private String sessionFactoryUuid;
private boolean allowLoadOutsideTransaction;
/** /**
* For serialization * For serialization
@ -23,10 +26,21 @@ public abstract class AbstractSerializableProxy implements Serializable {
protected AbstractSerializableProxy() { protected AbstractSerializableProxy() {
} }
/**
* @deprecated use {@link #AbstractSerializableProxy(String, Serializable, Boolean, String, boolean)} instead.
*/
@Deprecated
protected AbstractSerializableProxy(String entityName, Serializable id, Boolean readOnly) { 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.entityName = entityName;
this.id = id; this.id = id;
this.readOnly = readOnly; this.readOnly = readOnly;
this.sessionFactoryUuid = sessionFactoryUuid;
this.allowLoadOutsideTransaction = allowLoadOutsideTransaction;
} }
protected String getEntityName() { protected String getEntityName() {
@ -46,8 +60,23 @@ public abstract class AbstractSerializableProxy implements Serializable {
* @param li the read-only/modifiable setting to use when * @param li the read-only/modifiable setting to use when
* associated with a session; null indicates that the default should be used. * associated with a session; null indicates that the default should be used.
* @throws IllegalStateException if isReadOnlySettingAvailable() == true * @throws IllegalStateException if isReadOnlySettingAvailable() == true
*
* @deprecated Use {@link #afterDeserialization(AbstractLazyInitializer)} instead.
*/ */
@Deprecated
protected void setReadOnlyBeforeAttachedToSession(AbstractLazyInitializer li) { 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 ( isUninitialized() ) {
if ( replacement == null ) { if ( replacement == null ) {
prepareForPossibleLoadingOutsideTransaction();
replacement = serializableProxy(); replacement = serializableProxy();
} }
return replacement; return replacement;

View File

@ -17,13 +17,6 @@ import org.hibernate.proxy.ProxyConfiguration;
import org.hibernate.proxy.pojo.BasicLazyInitializer; import org.hibernate.proxy.pojo.BasicLazyInitializer;
import org.hibernate.type.CompositeType; 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; import static org.hibernate.internal.CoreLogging.messageLogger;
public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor { public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor {
@ -94,6 +87,8 @@ public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyC
interfaces, interfaces,
getIdentifier(), getIdentifier(),
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
getSessionFactoryUuid(),
isAllowLoadOutsideTransaction(),
getIdentifierMethod, getIdentifierMethod,
setIdentifierMethod, setIdentifierMethod,
componentIdType componentIdType

View File

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

View File

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

View File

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

View File

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