From 782336faeddb36012309bcbe855b470cbe2299e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 4 Jul 2018 13:05:45 +0200 Subject: [PATCH] HHH-7686 Allow lazy loading outside of a transaction after dynamic map proxy deserialization if the proper settings were enabled In theory, trying to deserialize MapLazyInitializer instances that were serialized before this patch should still work, although using such instances (i.e. trying to access any method on the proxy) would still fail, just like it used to before this patch. --- .../proxy/AbstractLazyInitializer.java | 15 ++++++-- .../proxy/AbstractSerializableProxy.java | 2 +- .../proxy/map/MapLazyInitializer.java | 16 +++++++++ .../org/hibernate/proxy/map/MapProxy.java | 36 ++++++++++++++++--- .../proxy/map/SerializableMapProxy.java | 34 ++++++++++++++++++ .../MapProxySerializationTest.java | 4 --- 6 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java index 5378447c8a..be5b30b371 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -45,8 +45,17 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { private boolean allowLoadOutsideTransaction; /** - * For serialization from the non-pojo initializers (HHH-3309) + * @deprecated This constructor was initially intended for serialization only, and is not useful anymore. + * In any case it should not be relied on by user code. + * Subclasses should rather implement Serializable with an {@code Object writeReplace()} method returning + * a subclass of {@link AbstractSerializableProxy}, + * which in turn implements Serializable and an {@code Object readResolve()} method + * instantiating the {@link AbstractLazyInitializer} subclass + * and calling {@link AbstractSerializableProxy#afterDeserialization(AbstractLazyInitializer)} on it. + * See {@link org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor} and + * {@link org.hibernate.proxy.pojo.bytebuddy.SerializableProxy} for examples. */ + @Deprecated protected AbstractLazyInitializer() { } @@ -240,7 +249,7 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { * 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() { + public final void initializeWithoutLoadIfPossible() { if ( !initialized && session != null && session.isOpen() ) { final EntityKey key = session.generateEntityKey( getIdentifier(), @@ -380,7 +389,7 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { * * @throws IllegalStateException if isReadOnlySettingAvailable() == true */ - protected final Boolean isReadOnlyBeforeAttachedToSession() { + public final Boolean isReadOnlyBeforeAttachedToSession() { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( "Cannot call isReadOnlyBeforeAttachedToSession when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java index ec7a0c01ef..95f0a6dfff 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -9,7 +9,7 @@ package org.hibernate.proxy; import java.io.Serializable; /** - * Convenience base class for SerializableProxy. + * Convenience base class for the serialized form of {@link AbstractLazyInitializer}. * * @author Gail Badner */ diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java index 7561579352..6c08dd18d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapLazyInitializer.java @@ -31,4 +31,20 @@ public class MapLazyInitializer extends AbstractLazyInitializer implements Seria throw new UnsupportedOperationException("dynamic-map entity representation"); } + // Expose the following methods to MapProxy by overriding them (so that classes in this package see them) + + @Override + protected void prepareForPossibleLoadingOutsideTransaction() { + super.prepareForPossibleLoadingOutsideTransaction(); + } + + @Override + protected boolean isAllowLoadOutsideTransaction() { + return super.isAllowLoadOutsideTransaction(); + } + + @Override + protected String getSessionFactoryUuid() { + return super.getSessionFactoryUuid(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java index 7585b2b447..6416358ab2 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/MapProxy.java @@ -22,14 +22,12 @@ public class MapProxy implements HibernateProxy, Map, Serializable { private MapLazyInitializer li; + private Object replacement; + MapProxy(MapLazyInitializer li) { this.li = li; } - public Object writeReplace() { - return this; - } - public LazyInitializer getHibernateLazyInitializer() { return li; } @@ -82,4 +80,34 @@ public class MapProxy implements HibernateProxy, Map, Serializable { return li.getMap().put(key, value); } + @Override + public Object writeReplace() { + /* + * If the target has already been loaded somewhere, just not set on the proxy, + * then use it to initialize the proxy so that we will serialize that instead of the proxy. + */ + li.initializeWithoutLoadIfPossible(); + + if ( li.isUninitialized() ) { + if ( replacement == null ) { + li.prepareForPossibleLoadingOutsideTransaction(); + replacement = serializableProxy(); + } + return replacement; + } + else { + return li.getImplementation(); + } + } + + private Object serializableProxy() { + return new SerializableMapProxy( + li.getEntityName(), + li.getIdentifier(), + ( li.isReadOnlySettingAvailable() ? Boolean.valueOf( li.isReadOnly() ) : li.isReadOnlyBeforeAttachedToSession() ), + li.getSessionFactoryUuid(), + li.isAllowLoadOutsideTransaction() + ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java new file mode 100644 index 0000000000..189ae366eb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.proxy.map; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.hibernate.proxy.AbstractSerializableProxy; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor; +import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyFactory; +import org.hibernate.type.CompositeType; + +public final class SerializableMapProxy extends AbstractSerializableProxy { + + public SerializableMapProxy( + String entityName, + Serializable id, + Boolean readOnly, + String sessionFactoryUuid, + boolean allowLoadOutsideTransaction) { + super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + } + + private Object readResolve() { + MapLazyInitializer initializer = new MapLazyInitializer( getEntityName(), getId(), null ); + afterDeserialization( initializer ); + return new MapProxy( initializer ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java index 896ad7b2cd..0b8eb3993c 100644 --- a/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/serialization/MapProxySerializationTest.java @@ -21,7 +21,6 @@ import org.hibernate.proxy.AbstractLazyInitializer; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.map.MapProxy; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Before; @@ -86,7 +85,6 @@ public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testInitializedProxySerializationIfTargetInPersistenceContext() { final Session s = openSession(); @@ -128,7 +126,6 @@ public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testUninitializedProxySerializationIfTargetInPersistenceContext() { final Session s = openSession(); @@ -210,7 +207,6 @@ public class MapProxySerializationTest extends BaseCoreFunctionalTestCase { @SuppressWarnings("unchecked") @Test @TestForIssue(jiraKey = "HHH-7686") - @FailureExpected(jiraKey = "HHH-7686") public void testProxyInitializationWithoutTXAfterDeserialization() { final Session s = openSession();