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 7d267b271e..fed06faac4 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractLazyInitializer.java @@ -41,6 +41,7 @@ public abstract class AbstractLazyInitializer implements LazyInitializer { private Boolean readOnlyBeforeAttachedToSession; private String sessionFactoryUuid; + private String sessionFactoryName; private boolean allowLoadOutsideTransaction; /** @@ -277,6 +278,9 @@ protected void prepareForPossibleLoadingOutsideTransaction() { // such configuration, so to know if such operation isn't allowed or configured otherwise. sessionFactoryUuid = session.getFactory().getUuid(); } + if ( sessionFactoryName == null ) { + sessionFactoryName = session.getFactory().getName(); + } } } @@ -441,6 +445,18 @@ protected String getSessionFactoryUuid() { return sessionFactoryUuid; } + /** + * Get the session factory name. + * + * This method should only be called during serialization, + * and only makes sense after a call to {@link #prepareForPossibleLoadingOutsideTransaction()}. + * + * @return the session factory name. + */ + protected String getSessionFactoryName() { + return sessionFactoryName; + } + /** * Restore settings that are not passed to the constructor, * but are still preserved during serialization. @@ -458,7 +474,7 @@ protected String getSessionFactoryUuid() { */ /* package-private */ final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, - String sessionFactoryUuid, boolean allowLoadOutsideTransaction) { + String sessionFactoryUuid, String sessionFactoryName, boolean allowLoadOutsideTransaction) { if ( isReadOnlySettingAvailable() ) { throw new IllegalStateException( "Cannot call afterDeserialization when isReadOnlySettingAvailable == true [" + entityName + "#" + id + "]" @@ -467,6 +483,7 @@ final void afterDeserialization(Boolean readOnlyBeforeAttachedToSession, this.readOnlyBeforeAttachedToSession = readOnlyBeforeAttachedToSession; this.sessionFactoryUuid = sessionFactoryUuid; + this.sessionFactoryName = sessionFactoryName; this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } 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 ece5c8673e..3055d08903 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/AbstractSerializableProxy.java @@ -18,6 +18,7 @@ public abstract class AbstractSerializableProxy implements Serializable { private final Object id; private final Boolean readOnly; protected final String sessionFactoryUuid; + protected final String sessionFactoryName; private final boolean allowLoadOutsideTransaction; protected AbstractSerializableProxy( @@ -25,11 +26,13 @@ protected AbstractSerializableProxy( Object id, Boolean readOnly, String sessionFactoryUuid, + String sessionFactoryName, boolean allowLoadOutsideTransaction) { this.entityName = entityName; this.id = id; this.readOnly = readOnly; this.sessionFactoryUuid = sessionFactoryUuid; + this.sessionFactoryName = sessionFactoryName; this.allowLoadOutsideTransaction = allowLoadOutsideTransaction; } @@ -50,6 +53,6 @@ protected Object getId() { * @param li the {@link AbstractLazyInitializer} to initialize. */ protected void afterDeserialization(AbstractLazyInitializer li) { - li.afterDeserialization( readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + li.afterDeserialization( readOnly, sessionFactoryUuid, sessionFactoryName, allowLoadOutsideTransaction ); } } 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 6253ff804d..25da3b36c0 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 @@ -53,4 +53,9 @@ protected boolean isAllowLoadOutsideTransaction() { protected String getSessionFactoryUuid() { return super.getSessionFactoryUuid(); } + + @Override + protected String getSessionFactoryName() { + return super.getSessionFactoryName(); + } } 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 cd763cc75e..44f08a180b 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 @@ -120,6 +120,7 @@ private Object serializableProxy() { li.getInternalIdentifier(), ( li.isReadOnlySettingAvailable() ? Boolean.valueOf( li.isReadOnly() ) : li.isReadOnlyBeforeAttachedToSession() ), li.getSessionFactoryUuid(), + li.getSessionFactoryName(), 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 index 7095f6140d..ed27bb5548 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/map/SerializableMapProxy.java @@ -15,8 +15,9 @@ public SerializableMapProxy( Object id, Boolean readOnly, String sessionFactoryUuid, + String sessionFactoryName, boolean allowLoadOutsideTransaction) { - super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + super( entityName, id, readOnly, sessionFactoryUuid, sessionFactoryName, allowLoadOutsideTransaction ); } private Object readResolve() { diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java index 8de150ae1e..3ba6923609 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyInterceptor.java @@ -87,6 +87,7 @@ protected Object serializableProxy() { getInternalIdentifier(), ( isReadOnlySettingAvailable() ? Boolean.valueOf( isReadOnly() ) : isReadOnlyBeforeAttachedToSession() ), getSessionFactoryUuid(), + getSessionFactoryName(), isAllowLoadOutsideTransaction(), getIdentifierMethod, setIdentifierMethod, diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java index 98a3c57ba3..ad0113ac92 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/SerializableProxy.java @@ -37,11 +37,12 @@ public SerializableProxy( Object id, Boolean readOnly, String sessionFactoryUuid, + String sessionFactoryName, boolean allowLoadOutsideTransaction, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType) { - super( entityName, id, readOnly, sessionFactoryUuid, allowLoadOutsideTransaction ); + super( entityName, id, readOnly, sessionFactoryUuid, sessionFactoryName, allowLoadOutsideTransaction ); this.persistentClass = persistentClass; this.interfaces = interfaces; if ( getIdentifierMethod != null ) { @@ -110,16 +111,16 @@ CompositeType getComponentIdType() { } private Object readResolve() { - final SessionFactoryImplementor sessionFactory = retrieveMatchingSessionFactory( this.sessionFactoryUuid ); + final SessionFactoryImplementor sessionFactory = retrieveMatchingSessionFactory( this.sessionFactoryUuid, this.sessionFactoryName ); BytecodeProviderImpl byteBuddyBytecodeProvider = retrieveByteBuddyBytecodeProvider( sessionFactory ); HibernateProxy proxy = byteBuddyBytecodeProvider.getByteBuddyProxyHelper().deserializeProxy( this ); afterDeserialization( (ByteBuddyInterceptor) proxy.getHibernateLazyInitializer() ); return proxy; } - private static SessionFactoryImplementor retrieveMatchingSessionFactory(final String sessionFactoryUuid) { + private static SessionFactoryImplementor retrieveMatchingSessionFactory(final String sessionFactoryUuid, final String sessionFactoryName) { Objects.requireNonNull( sessionFactoryUuid ); - final SessionFactoryImplementor sessionFactory = SessionFactoryRegistry.INSTANCE.getSessionFactory( sessionFactoryUuid ); + final SessionFactoryImplementor sessionFactory = SessionFactoryRegistry.INSTANCE.findSessionFactory( sessionFactoryUuid, sessionFactoryName ); if ( sessionFactory != null ) { return sessionFactory; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/MultipleSessionFactoriesProxyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/MultipleSessionFactoriesProxyTest.java new file mode 100644 index 0000000000..3d0dbf62e2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/proxy/MultipleSessionFactoriesProxyTest.java @@ -0,0 +1,98 @@ +/* + * 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.orm.test.proxy; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.math.BigDecimal; + +import org.hibernate.Hibernate; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.internal.util.SerializationHelper; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +/** + * This integration test verifies that serialized entities + * can be deserialized and re-connected to a SessionFactory + * by using the SessionFactory name rather than its UUID. + * This is relevant for clustering, as different SessionFactory + * instances on different nodes will have a different UUID + * but can be configured to match the name. + * + * @author Marcel Overdijk + */ +public class MultipleSessionFactoriesProxyTest extends BaseCoreFunctionalTestCase { + + @Override + public String[] getMappings() { + return new String[] { "proxy/DataPoint.hbm.xml" }; + } + + @Override + protected String getBaseForMappings() { + return "org/hibernate/orm/test/"; + } + + @Override + public void configure(Configuration cfg) { + super.configure( cfg ); + cfg.setProperty( Environment.SESSION_FACTORY_NAME, "sf-name" ); // explicitly define the session factory name + cfg.setProperty( Environment.SESSION_FACTORY_NAME_IS_JNDI, "false" ); // do not bind it to jndi + } + + @Test + @TestForIssue(jiraKey = "HHH-17172") + public void testProxySerializationWithMultipleSessionFactories() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + Container container = new Container( "container" ); + container.setOwner( new Owner( "owner" ) ); + container.setInfo( new Info( "blah blah blah" ) ); + container.getDataPoints().add( new DataPoint( new BigDecimal( 1 ), new BigDecimal( 1 ), "first data point" ) ); + container.getDataPoints().add( new DataPoint( new BigDecimal( 2 ), new BigDecimal( 2 ), "second data point" ) ); + s.persist( container ); + s.flush(); + s.clear(); + + container = s.load( Container.class, container.getId() ); + assertFalse( Hibernate.isInitialized( container ) ); + container.getId(); + assertFalse( Hibernate.isInitialized( container ) ); + container.getName(); + assertTrue( Hibernate.isInitialized( container ) ); + + t.commit(); + s.close(); + + // Serialize the container. + byte[] bytes = SerializationHelper.serialize( container ); + + // Now deserialize, which works at this stage, because the session factory UUID in the + // serialized object is the same as the current session factory. + SerializationHelper.deserialize( bytes ); + + // Now rebuild the session factory (it will get a new unique UUID). + // As configured for this test an explicit session factory name is used ("sf-name"), + // so we would expect the deserialization to work after rebuilding the session factory. + // Rebuilding the session factory simulates multiple JVM instances with different session + // factories (different UUID's, but same name!) trying to deserialize objects from a + // centralized cache (e.g. Hazelcast). + rebuildSessionFactory(); + + // And this should be possible now: even though we lack of a matching session factory by + // UUID, it should seek a match by using the session factory name as valid alternative. + SerializationHelper.deserialize( bytes ); + + } +}