From db475ed716045b4c2a7eebccfbf51daf7e2a3c6e Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 11 Jun 2015 11:56:04 -0700 Subject: [PATCH] HHH-9518 : Exception handling around collection session access needs to be improved (cherry picked from commit f4f04901e266f00451ec13b86b90eeefb803d7a5) Conflicts: hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java HHH-9518 : Corrections to test to work for pre-5.0 (cherry picked from commit 0854a0473f444dce4c9241a87e7c4542e690451f) Conflicts: hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java --- .../AbstractPersistentCollection.java | 73 +- .../hibernate/internal/CoreMessageLogger.java | 8 + .../MultipleSessionCollectionTest.java | 694 ++++++++++++++++++ 3 files changed, 761 insertions(+), 14 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 1983dbee6f..65a65f95b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -34,6 +34,8 @@ import java.util.ListIterator; import javax.naming.NamingException; +import org.jboss.logging.Logger; + import org.hibernate.AssertionFailure; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -47,6 +49,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.internal.util.MarkerObject; import org.hibernate.internal.util.collections.EmptyIterator; @@ -56,7 +59,6 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.type.ComponentType; import org.hibernate.type.Type; -import org.jboss.logging.Logger; /** * Base class implementing {@link org.hibernate.collection.spi.PersistentCollection} @@ -64,7 +66,9 @@ import org.jboss.logging.Logger; * @author Gavin King */ public abstract class AbstractPersistentCollection implements Serializable, PersistentCollection { - private static final Logger log = Logger.getLogger( AbstractPersistentCollection.class ); + private static final CoreMessageLogger LOG = Logger.getMessageLogger( + CoreMessageLogger.class, AbstractPersistentCollection.class.getName() + ); private transient SessionImplementor session; private boolean initialized; @@ -243,7 +247,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers ( (Session) session ).close(); } catch (Exception e) { - log.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); + LOG.warn( "Unable to close temporary session used to load lazy collection associated to no session" ); } session = originalSession; } @@ -602,6 +606,9 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers return true; } else { + if ( this.session != null ) { + LOG.logCannotUnsetUnexpectedSessionInCollection( generateUnexpectedSessionStateMessage( currentSession ) ); + } return false; } } @@ -630,26 +637,23 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers * @throws HibernateException if the collection was already associated * with another open session */ + @Override public final boolean setCurrentSession(SessionImplementor session) throws HibernateException { if ( session == this.session ) { return false; } else { - if ( isConnectedToSession() ) { - CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this ); - if ( ce == null ) { + if ( this.session != null ) { + final String msg = generateUnexpectedSessionStateMessage( session ); + if ( isConnectedToSession() ) { throw new HibernateException( - "Illegal attempt to associate a collection with two open sessions" + "Illegal attempt to associate a collection with two open sessions. " + msg ); } else { - throw new HibernateException( - "Illegal attempt to associate a collection with two open sessions: " + - MessageHelper.collectionInfoString( - ce.getLoadedPersister(), this, - ce.getLoadedKey(), session - ) - ); + LOG.logUnexpectedSessionInCollectionNotConnected( msg ); + this.session = session; + return true; } } else { @@ -659,9 +663,50 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers } } + private String generateUnexpectedSessionStateMessage(SessionImplementor session) { + // NOTE: If this.session != null, this.session may be operating on this collection + // (e.g., by changing this.role, this.key, or even this.session) in a different thread. + + // Grab the current role and key (it can still get changed by this.session...) + // If this collection is connected to this.session, then this.role and this.key should + // be consistent with the CollectionEntry in this.session (as long as this.session doesn't + // change it). Don't access the CollectionEntry in this.session because that could result + // in multi-threaded access to this.session. + final String roleCurrent = role; + final Serializable keyCurrent = key; + + final StringBuilder sb = new StringBuilder( "Collection : " ); + if ( roleCurrent != null ) { + sb.append( MessageHelper.collectionInfoString( roleCurrent, keyCurrent ) ); + } + else { + final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( this ); + if ( ce != null ) { + sb.append( + MessageHelper.collectionInfoString( + ce.getLoadedPersister(), + this, + ce.getLoadedKey(), + session + ) + ); + } + else { + sb.append( "" ); + } + } + // only include the collection contents if debug logging + if ( LOG.isDebugEnabled() ) { + final String collectionContents = wasInitialized() ? toString() : ""; + sb.append( "\nCollection contents: [" ).append( collectionContents ).append( "]" ); + } + return sb.toString(); + } + /** * Do we need to completely recreate this collection when it changes? */ + @Override public boolean needsRecreate(CollectionPersister persister) { // Workaround for situations like HHH-7072. If the collection element is a component that consists entirely // of nullable properties, we currently have to forcefully recreate the entire collection. See the use diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 4b510873a8..c1586ba917 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1652,4 +1652,12 @@ public interface CoreMessageLogger extends BasicLogger { @LogMessage(level = DEBUG) @Message(value = "Creating pooled optimizer (lo) with [incrementSize=%s; returnClass=%s]", id = 467) void creatingPooledLoOptimizer(int incrementSize, String name); + + @LogMessage(level = WARN) + @Message(value = "An unexpected session is defined for a collection, but the collection is not connected to that session. A persistent collection may only be associated with one session at a time. Overwriting session. %s", id = 470) + void logUnexpectedSessionInCollectionNotConnected(String msg); + + @LogMessage(level = WARN) + @Message(value = "Cannot unset session in a collection because an unexpected session is defined. A persistent collection may only be associated with one session at a time. %s", id = 471 ) + void logCannotUnsetUnexpectedSessionInCollection(String msg); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionTest.java new file mode 100644 index 0000000000..5ffa0d4bdf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionTest.java @@ -0,0 +1,694 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.collection.multisession; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.jboss.logging.Logger; +import org.junit.Test; + +import org.hibernate.Hibernate; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.collection.internal.AbstractPersistentCollection; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Gail Badner + */ +public class MultipleSessionCollectionTest extends BaseCoreFunctionalTestCase { + private static final Logger log = Logger.getLogger( MultipleSessionCollectionTest.class ); + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testSaveOrUpdateOwnerWithCollectionInNewSessionBeforeFlush() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.saveOrUpdate( p ); + + // try to save the same entity in a new session before flushing the first session + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testSaveOrUpdateOwnerWithCollectionInNewSessionAfterFlush() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.saveOrUpdate( p ); + s1.flush(); + + // try to save the same entity in a new session after flushing the first session + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testSaveOrUpdateOwnerWithUninitializedCollectionInNewSession() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( p ); + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.getTransaction().begin(); + p = (Parent) s1.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( p.children ) ); + + // try to save the same entity (with an uninitialized collection) in a new session + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to initialize collection, modify and commit in first session + assertFalse( Hibernate.isInitialized( p.children ) ); + Hibernate.initialize( p.children ); + p.children.add( new Child() ); + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( 2, pGet.children.size()); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testSaveOrUpdateOwnerWithInitializedCollectionInNewSession() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( p ); + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.getTransaction().begin(); + p = (Parent) s1.get( Parent.class, p.id ); + Hibernate.initialize( p.children ); + + // try to save the same entity (with an initialized collection) in a new session + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + // + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testCopyPersistentCollectionReferenceBeforeFlush() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.persist( p ); + + // Copy p.children into a different Parent before flush and try to save in new session. + + Parent pWithSameChildren = new Parent(); + pWithSameChildren.children = p.children; + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( pWithSameChildren ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testCopyPersistentCollectionReferenceAfterFlush() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.persist( p ); + s1.flush(); + + // Copy p.children into a different Parent after flush and try to save in new session. + + Parent pWithSameChildren = new Parent(); + pWithSameChildren.children = p.children; + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( pWithSameChildren ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testCopyUninitializedCollectionReferenceAfterGet() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( p ); + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.getTransaction().begin(); + p = (Parent) s1.get( Parent.class, p.id ); + assertFalse( Hibernate.isInitialized( p.children ) ); + + // Copy p.children (uninitialized) into a different Parent and try to save in new session. + + Parent pWithSameChildren = new Parent(); + pWithSameChildren.children = p.children; + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( pWithSameChildren ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testCopyInitializedCollectionReferenceAfterGet() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( p ); + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.getTransaction().begin(); + p = (Parent) s1.get( Parent.class, p.id ); + Hibernate.initialize( p.children ); + + // Copy p.children (initialized) into a different Parent.children and try to save in new session. + + Parent pWithSameChildren = new Parent(); + pWithSameChildren.children = p.children; + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( pWithSameChildren ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testCopyInitializedCollectionReferenceToNewEntityCollectionRoleAfterGet() { + Parent p = new Parent(); + Child c = new Child(); + p.children.add( c ); + + Session s = openSession(); + s.getTransaction().begin(); + s.persist( p ); + s.getTransaction().commit(); + s.close(); + + Session s1 = openSession(); + s1.getTransaction().begin(); + p = (Parent) s1.get( Parent.class, p.id ); + Hibernate.initialize( p.children ); + + // Copy p.children (initialized) into a different Parent.oldChildren (note different collection role) + // and try to save in new session. + + Parent pWithSameChildren = new Parent(); + pWithSameChildren.oldChildren = p.children; + + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( pWithSameChildren ); + s2.getTransaction().commit(); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit in first session + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + Parent pGet = (Parent) s1.get( Parent.class, p.id ); + assertEquals( c.id, pGet.children.iterator().next().id ); + session.delete( pGet ); + s1.getTransaction().commit(); + session.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testDeleteCommitCopyToNewOwnerInNewSession() { + Parent p1 = new Parent(); + p1.nickNames.add( "nick" ); + Parent p2 = new Parent(); + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.save( p1 ); + s1.save( p2 ); + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + s1.delete( p1 ); + s1.flush(); + s1.getTransaction().commit(); + + // need to commit after flushing; otherwise, will get lock failure when try to move the collection below + + assertNull( ( (SessionImplementor) s1 ).getPersistenceContext().getEntry( p1 ) ); + CollectionEntry ceChildren = ( (SessionImplementor) s1 ).getPersistenceContext().getCollectionEntry( (PersistentCollection) p1.children ); + CollectionEntry ceNickNames = ( (SessionImplementor) s1 ).getPersistenceContext().getCollectionEntry( (PersistentCollection) p1.nickNames ); + assertNull( ceChildren ); + assertNull( ceNickNames ); + assertNull( ( ( AbstractPersistentCollection) p1.children ).getSession() ); + assertNull( ( ( AbstractPersistentCollection) p1.oldChildren ).getSession() ); + assertNull( ( ( AbstractPersistentCollection) p1.nickNames ).getSession() ); + assertNull( ( (AbstractPersistentCollection) p1.oldNickNames ).getSession() ); + + // Assign the deleted collection to a different entity with same collection role (p2.nickNames) + + p2.nickNames = p1.nickNames; + Session s2 = openSession(); + s2.getTransaction().begin(); + s2.saveOrUpdate( p2 ); + s2.getTransaction().commit(); + s2.close(); + + s1.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testDeleteCommitCopyToNewOwnerNewCollectionRoleInNewSession() { + Parent p1 = new Parent(); + p1.nickNames.add( "nick" ); + Parent p2 = new Parent(); + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.save( p1 ); + s1.save( p2 ); + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + s1.delete( p1 ); + s1.flush(); + s1.getTransaction().commit(); + + // need to commit after flushing; otherwise, will get lock failure when try to move the collection below + + assertNull( ( (SessionImplementor) s1 ).getPersistenceContext().getEntry( p1 ) ); + CollectionEntry ceChildren = ( (SessionImplementor) s1 ).getPersistenceContext().getCollectionEntry( (PersistentCollection) p1.children ); + CollectionEntry ceNickNames = ( (SessionImplementor) s1 ).getPersistenceContext().getCollectionEntry( (PersistentCollection) p1.nickNames ); + assertNull( ceChildren ); + assertNull( ceNickNames ); + assertNull( ( ( AbstractPersistentCollection) p1.children ).getSession() ); + assertNull( ( ( AbstractPersistentCollection) p1.oldChildren ).getSession() ); + assertNull( ( ( AbstractPersistentCollection) p1.nickNames ).getSession() ); + assertNull( ( (AbstractPersistentCollection) p1.oldNickNames ).getSession() ); + + // Assign the deleted collection to a different entity with different collection role (p2.oldNickNames) + + p2.oldNickNames = p1.nickNames; + Session s2 = openSession(); + s2.getTransaction().begin(); + s2.saveOrUpdate( p2 ); + s2.getTransaction().commit(); + s2.close(); + + s1.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testDeleteCopyToNewOwnerInNewSessionBeforeFlush() { + Parent p1 = new Parent(); + p1.nickNames.add( "nick" ); + Parent p2 = new Parent(); + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.save( p1 ); + s1.save( p2 ); + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + s1.delete( p1 ); + + // Assign the deleted collection to a different entity with same collection role (p2.nickNames) + // before committing delete. + + p2.nickNames = p1.nickNames; + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p2 ); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit the original delete + s1.getTransaction().commit(); + s1.close(); + } + + @Test + @TestForIssue( jiraKey = "HHH-9518" ) + public void testDeleteCopyToNewOwnerNewCollectionRoleInNewSessionBeforeFlush() { + Parent p1 = new Parent(); + p1.nickNames.add( "nick" ); + Parent p2 = new Parent(); + Session s1 = openSession(); + s1.getTransaction().begin(); + s1.save( p1 ); + s1.save( p2 ); + s1.getTransaction().commit(); + s1.close(); + + s1 = openSession(); + s1.getTransaction().begin(); + s1.delete( p1 ); + + // Assign the deleted collection to a different entity with different collection role (p2.oldNickNames) + // before committing delete. + + p2.oldNickNames = p1.nickNames; + Session s2 = openSession(); + s2.getTransaction().begin(); + try { + s2.saveOrUpdate( p2 ); + fail( "should have thrown HibernateException" ); + } + catch (HibernateException ex) { + log.error( ex ); + s2.getTransaction().rollback(); + } + finally { + s2.close(); + } + + // should still be able to commit the original delete + s1.getTransaction().commit(); + s1.close(); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + Child.class + }; + } + + @Entity + @Table(name="Parent") + public static class Parent { + @Id + @GeneratedValue + private Long id; + + @ElementCollection + private Set nickNames = new HashSet(); + + @ElementCollection + private Set oldNickNames = new HashSet(); + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn + private Set children = new HashSet(); + + @OneToMany(cascade = CascadeType.ALL) + @JoinColumn + private Set oldChildren = new HashSet(); + + } + + @Entity + @Table(name="Child") + public static class Child { + @Id + @GeneratedValue + private Long id; + + } +}