HHH-12720 Allow lazy loading outside of a transaction after proxy deserialization if the proper settings were enabled
This commit is contained in:
parent
500edf4b8a
commit
3336489e40
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ public abstract class BasicLazyInitializer extends AbstractLazyInitializer {
|
|||
|
||||
if ( isUninitialized() ) {
|
||||
if ( replacement == null ) {
|
||||
prepareForPossibleLoadingOutsideTransaction();
|
||||
replacement = serializableProxy();
|
||||
}
|
||||
return replacement;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ public class JavassistLazyInitializer extends BasicLazyInitializer implements Me
|
|||
interfaces,
|
||||
getIdentifier(),
|
||||
( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ),
|
||||
getSessionFactoryUuid(),
|
||||
isAllowLoadOutsideTransaction(),
|
||||
getIdentifierMethod,
|
||||
setIdentifierMethod,
|
||||
componentIdType
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue