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.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
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue