[hibernate-commits] Hibernate SVN: r14903 - in core/branches/Branch_3_2_4_SP1_CP: src/org/hibernate/type and 2 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Jul 9 03:16:31 EDT 2008


Author: gbadner
Date: 2008-07-09 03:16:31 -0400 (Wed, 09 Jul 2008)
New Revision: 14903

Added:
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascade.hbm.xml
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
Modified:
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/type/EntityType.java
   core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/AllTests.java
Log:
JBPAPP-915,HHH-3229 : Cascade merge transient entities regardless of property traversal order


Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java	2008-07-09 05:14:03 UTC (rev 14902)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/event/def/DefaultMergeEventListener.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -2,23 +2,27 @@
 package org.hibernate.event.def;
 
 import java.io.Serializable;
+import java.util.Iterator;
 import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.hibernate.AssertionFailure;
 import org.hibernate.HibernateException;
 import org.hibernate.ObjectDeletedException;
 import org.hibernate.StaleObjectStateException;
+import org.hibernate.TransientObjectException;
 import org.hibernate.WrongClassException;
 import org.hibernate.engine.Cascade;
 import org.hibernate.engine.CascadingAction;
+import org.hibernate.engine.EntityEntry;
+import org.hibernate.engine.EntityKey;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.engine.Status;
 import org.hibernate.event.EventSource;
 import org.hibernate.event.MergeEvent;
 import org.hibernate.event.MergeEventListener;
-import org.hibernate.engine.SessionImplementor;
-import org.hibernate.engine.EntityEntry;
-import org.hibernate.engine.EntityKey;
 import org.hibernate.intercept.FieldInterceptionHelper;
 import org.hibernate.intercept.FieldInterceptor;
 import org.hibernate.persister.entity.EntityPersister;
@@ -43,14 +47,33 @@
 		return IdentityMap.invert( (Map) anything );
 	}
 
-	/** 
+	/**
 	 * Handle the given merge event.
 	 *
 	 * @param event The merge event to be handled.
 	 * @throws HibernateException
 	 */
 	public void onMerge(MergeEvent event) throws HibernateException {
-		onMerge( event, IdentityMap.instantiate(10) );
+		Map copyCache = IdentityMap.instantiate(10);
+		onMerge( event, copyCache );
+		for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
+			Object entity = it.next();
+			if ( entity instanceof HibernateProxy ) {
+				entity = ( (HibernateProxy) entity ).getHibernateLazyInitializer().getImplementation();
+			}
+			EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
+			if ( entry == null ) {
+				throw new TransientObjectException(
+						"object references an unsaved transient instance - save the transient instance before merging: " +
+						event.getSession().guessEntityName( entity )
+				);
+				// TODO: cache the entity name somewhere so that it is available to this exception
+				// entity name will not be available for non-POJO entities
+			}
+			if ( entry.getStatus() != Status.MANAGED ) {
+				throw new AssertionFailure( "Merged entity does not have status set to MANAGED; "+entry+" status="+entry.getStatus() );
+			}
+		}
 	}
 
 	/** 
@@ -82,7 +105,8 @@
 				entity = original;
 			}
 			
-			if ( copyCache.containsKey(entity) ) {
+			if ( copyCache.containsKey(entity) &&
+					source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
 				log.trace("already merged");
 				event.setResult(entity);
 			}
@@ -126,7 +150,7 @@
 						entityIsPersistent(event, copyCache);
 						break;
 					default: //DELETED
-						throw new ObjectDeletedException( 
+						throw new ObjectDeletedException(
 								"deleted instance passed to merge", 
 								null, 
 								getLoggableName( event.getEntityName(), entity )
@@ -137,7 +161,7 @@
 		}
 		
 	}
-	
+
 	protected void entityIsPersistent(MergeEvent event, Map copyCache) {
 		log.trace("ignoring persistent instance");
 		
@@ -168,10 +192,15 @@
 		final Serializable id = persister.hasIdentifierProperty() ?
 				persister.getIdentifier( entity, source.getEntityMode() ) :
 		        null;
-		
-		final Object copy = persister.instantiate( id, source.getEntityMode() );  //TODO: should this be Session.instantiate(Persister, ...)?
-		copyCache.put(entity, copy); //before cascade!
-		
+		if ( copyCache.containsKey( entity ) ) {
+			persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
+		}
+		else {
+			copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before cascade!
+			//TODO: should this be Session.instantiate(Persister, ...)?
+		}
+		final Object copy = copyCache.get( entity );
+
 		// cascade first, so that all unsaved objects get their
 		// copy created before we actually copy
 		//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/type/EntityType.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/type/EntityType.java	2008-07-09 05:14:03 UTC (rev 14902)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/type/EntityType.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -250,13 +250,23 @@
 			if ( original == target ) {
 				return target;
 			}
-			Object id = getIdentifier( original, session );
-			if ( id == null ) {
-				throw new AssertionFailure("cannot copy a reference to an object with a null id");
+			if ( session.getContextEntityIdentifier( original ) == null  &&
+					ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
+				final Object copy = session.getFactory().getEntityPersister( associatedEntityName )
+						.instantiate( null, session.getEntityMode() );
+				//TODO: should this be Session.instantiate(Persister, ...)?
+				copyCache.put( original, copy );
+				return copy;
 			}
-			id = getIdentifierOrUniqueKeyType( session.getFactory() )
-					.replace(id, null, session, owner, copyCache);
-			return resolve( id, session, owner );
+			else {
+				Object id = getIdentifier( original, session );
+				if ( id == null ) {
+					throw new AssertionFailure("non-transient entity has a null id");
+				}
+				id = getIdentifierOrUniqueKeyType( session.getFactory() )
+						.replace(id, null, session, owner, copyCache);
+				return resolve( id, session, owner );
+			}
 		}
 	}
 

Modified: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/AllTests.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/AllTests.java	2008-07-09 05:14:03 UTC (rev 14902)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/AllTests.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -20,7 +20,7 @@
 import org.hibernate.test.bidi.AuctionTest2;
 import org.hibernate.test.bytecode.BytecodeSuite;
 import org.hibernate.test.cache.CacheSuite;
-import org.hibernate.test.cascade.RefreshTest;
+import org.hibernate.test.cascade.CascadeSuite;
 import org.hibernate.test.cid.CompositeIdTest;
 import org.hibernate.test.collection.CollectionSuite;
 import org.hibernate.test.component.ComponentSuite;
@@ -259,7 +259,7 @@
 			suite.addTest( SortTest.suite() );
 			suite.addTest( WhereTest.suite() );
 			suite.addTest( IterateTest.suite() );
-			suite.addTest( RefreshTest.suite() );
+			suite.addTest( CascadeSuite.suite() );
 			suite.addTest( CollectionEventSuite.suite() );
 			suite.addTest( ExtraLazyTest.suite() );
 			suite.addTest( StatsTest.suite() );

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,106 @@
+// $Id$
+
+package org.hibernate.test.cascade;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * @author <a href="mailto:ovidiu at feodorov.com">Ovidiu Feodorov</a>
+ *
+ * Copyright 2008 Ovidiu Feodorov
+ *
+ */
+public class A
+{
+    // Constants -----------------------------------------------------------------------------------
+
+    // Static --------------------------------------------------------------------------------------
+
+    // Attributes ----------------------------------------------------------------------------------
+
+    private long id;
+
+    private String data;
+
+    // A 1 - * H
+    private Set hs;
+
+    // A 1 - 1 G
+    private G g;
+
+
+    // Constructors --------------------------------------------------------------------------------
+
+    public A()
+    {
+        hs = new HashSet();
+    }
+
+    public A(String data)
+    {
+        this();
+        this.data = data;
+    }
+
+    // Public --------------------------------------------------------------------------------------
+
+    public long getId()
+    {
+        return id;
+    }
+
+    public void setId(long id)
+    {
+        this.id = id;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setHs(Set hs)
+    {
+        this.hs = hs;
+    }
+
+    public Set getHs()
+    {
+        return hs;
+    }
+
+    public void setG(G g)
+    {
+        this.g = g;
+    }
+
+    public G getG()
+    {
+        return g;
+    }
+
+    public void addH(H h)
+    {
+        hs.add(h);
+        h.setA(this);
+    }
+
+    public String toString()
+    {
+        return "A[" + id + ", " + data + "]";
+    }
+
+    // Package protected ---------------------------------------------------------------------------
+
+    // Protected -----------------------------------------------------------------------------------
+
+    // Private -------------------------------------------------------------------------------------
+
+    // Inner classes -------------------------------------------------------------------------------
+}


Property changes on: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/A.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/CascadeSuite.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,17 @@
+package org.hibernate.test.cascade;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Implementation of CascadeSuite.
+ */
+public class CascadeSuite {
+
+	public static Test suite() {
+		TestSuite suite = new TestSuite( "Cascade tests" );
+		suite.addTest( RefreshTest.suite() );
+		suite.addTest( MultiPathCascadeTest.suite() );
+		return suite;
+	}
+}

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,95 @@
+package org.hibernate.test.cascade;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * @author <a href="mailto:ovidiu at feodorov.com">Ovidiu Feodorov</a>
+ *
+ * Copyright 2008 Ovidiu Feodorov
+ *
+ * @version <tt>$Revision$</tt>
+ *
+ * $Id$
+ */
+public class G
+{
+    // Constants -----------------------------------------------------------------------------------
+
+    // Static --------------------------------------------------------------------------------------
+
+    // Attributes ----------------------------------------------------------------------------------
+
+    private long id;
+
+    private String data;
+
+    // A 1 <-> 1 G
+    private A a;
+
+    // G * <-> * H
+    private Set hs;
+
+    // Constructors --------------------------------------------------------------------------------
+
+    public G()
+    {
+        this(null);
+    }
+
+    public G(String data)
+    {
+        this.data = data;
+        hs = new HashSet();
+    }
+
+    // Public --------------------------------------------------------------------------------------
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public A getA()
+    {
+        return a;
+    }
+
+    public void setA(A a)
+    {
+        this.a = a;
+    }
+
+    public Set getHs()
+    {
+        return hs;
+    }
+
+    public void setHs(Set s)
+    {
+        hs = s;
+    }
+
+    // Package protected ---------------------------------------------------------------------------
+
+    long getId()
+    {
+        return id;
+    }
+
+    // Protected -----------------------------------------------------------------------------------
+
+    // Private -------------------------------------------------------------------------------------
+
+    private void setId(long id)
+    {
+        this.id = id;
+    }
+
+    // Inner classes -------------------------------------------------------------------------------
+}


Property changes on: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/G.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,94 @@
+// $Id$
+
+
+package org.hibernate.test.cascade;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * @author <a href="mailto:ovidiu at feodorov.com">Ovidiu Feodorov</a>
+ *
+ * Copyright 2008 Ovidiu Feodorov
+ *
+ */
+public class H
+{
+    // Constants -----------------------------------------------------------------------------------
+
+    // Static --------------------------------------------------------------------------------------
+
+    // Attributes ----------------------------------------------------------------------------------
+
+    private long id;
+
+    private String data;
+
+    private A a;
+
+    // G * <-> * H
+    private Set gs;
+
+    // Constructors --------------------------------------------------------------------------------
+
+    public H()
+    {
+        this(null);
+    }
+
+    public H(String data)
+    {
+        this.data = data;
+        gs = new HashSet();
+    }
+
+    // Public --------------------------------------------------------------------------------------
+
+    public long getId()
+    {
+        return id;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public A getA()
+    {
+        return a;
+    }
+
+    public void setA(A a)
+    {
+        this.a = a;
+    }
+
+    public Set getGs()
+    {
+        return gs;
+    }
+
+    public void setGs(Set gs)
+    {
+        this.gs = gs;
+    }
+
+    // Package protected ---------------------------------------------------------------------------
+
+    // Protected -----------------------------------------------------------------------------------
+
+    // Private -------------------------------------------------------------------------------------
+
+    private void setId(long id)
+    {
+        this.id = id;
+    }
+
+    // Inner classes -------------------------------------------------------------------------------
+}


Property changes on: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/H.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascade.hbm.xml
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascade.hbm.xml	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascade.hbm.xml	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade">
+
+    <class name="A" table="HB_A">
+
+        <id name="id" type="long"><generator class="native"/></id>
+
+        <property name="data" type="string" not-null="true"/>
+
+        <!--
+             Associations
+        -->
+
+        <set name="hs" inverse="true" cascade="all">
+            <key column="a_fk"/>
+            <one-to-many class="H"/>
+        </set>
+        <one-to-one name="g" class="G" property-ref="a" cascade="all"/>
+
+    </class>
+
+    <class name="G" table="HB_G">
+
+        <id name="id" type="long"><generator class="native"/></id>
+
+        <property name="data" type="string" not-null="true"/>
+
+        <!--
+             Associations
+        -->
+
+        <set name="hs" inverse="true" table="HB_G_H" cascade="all">
+            <key column="g_fk"/>
+            <many-to-many class="H" column="h_fk"/>
+        </set>
+
+        <many-to-one name="a"
+            column="aId"
+            unique="true"
+            not-null="false"/>
+
+    </class>
+
+    <class name="H" table="HB_H">
+
+        <id name="id" type="long"><generator class="native"/></id>
+
+        <property name="data" type="string" not-null="true"/>
+
+        <!--
+            Associations
+        -->
+
+        <!-- *NOT* cascaded -->
+        <set name="gs" table="HB_G_H">
+            <key column="h_fk"/>
+            <many-to-many class="G" column="g_fk"/>
+        </set>
+
+        <many-to-one name="a" column="a_fk" class="A"/>
+
+    </class>
+
+
+</hibernate-mapping>
\ No newline at end of file

Added: core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/test/org/hibernate/test/cascade/MultiPathCascadeTest.java	2008-07-09 07:16:31 UTC (rev 14903)
@@ -0,0 +1,337 @@
+//$Id: $
+
+package org.hibernate.test.cascade;
+
+import junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.TransientObjectException;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+import org.hibernate.proxy.HibernateProxy;
+
+/**
+ * @author <a href="mailto:ovidiu at feodorov.com">Ovidiu Feodorov</a>
+ * @author Gail Badner
+ *
+ */
+
+public class MultiPathCascadeTest extends FunctionalTestCase {
+
+	public MultiPathCascadeTest(String name) {
+		super( name );
+	}
+
+	public String[] getMappings() {
+		return new String[] {
+				"cascade/MultiPathCascade.hbm.xml"
+		};
+	}
+
+	public static Test suite() {
+		return new FunctionalTestClassTestSuite( MultiPathCascadeTest.class );
+	}
+
+	protected void cleanupTest() {
+		Session s = openSession();
+		s.beginTransaction();
+		s.createQuery( "delete from A" );
+		s.createQuery( "delete from G" );
+		s.createQuery( "delete from H" );
+	}
+
+	public void testMultiPathMergeModifiedDetached() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		a = ( A ) s.merge( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+	}
+
+	public void testMultiPathMergeModifiedDetachedIntoProxy() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		A aLoaded = ( A ) s.load( A.class, new Long( a.getId() ) );
+		assertTrue( aLoaded instanceof HibernateProxy );
+		assertSame( aLoaded, s.merge( a ) );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+	}
+
+	public void testMultiPathUpdateModifiedDetached() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		s.update( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+	}
+
+	public void testMultiPathGetAndModify() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+		// retrieve the previously saved instance from the database, and update it
+		a = ( A ) s.get( A.class, new Long( a.getId() ) );
+		modifyEntity( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+	}
+
+	public void testMultiPathMergeNonCascadedTransientEntityInCollection() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		a = ( A ) s.merge( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+
+		// add a new (transient) G to collection in h
+		// there is no cascade from H to the collection, so this should fail when merged
+		assertEquals( 1, a.getHs().size() );
+		H h = ( H ) a.getHs().iterator().next();
+		G gNew = new G();
+		gNew.setData( "Gail" );
+		gNew.getHs().add( h );
+		h.getGs().add( gNew );
+
+		s = openSession();
+		s.beginTransaction();
+		try {
+			s.merge( a );
+			s.merge( h );
+			fail( "should have thrown TransientObjectException" );
+		}
+		catch ( TransientObjectException ex ) {
+			// expected
+		}
+		finally {
+			s.getTransaction().rollback();
+		}
+		s.close();
+	}
+
+	public void testMultiPathMergeNonCascadedTransientEntityInOneToOne() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		a = ( A ) s.merge( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+
+		// change the one-to-one association from g to be a new (transient) A
+		// there is no cascade from G to A, so this should fail when merged
+		G g = a.getG();
+		a.setG( null );
+		A aNew = new A();
+		aNew.setData( "Alice" );
+		g.setA( aNew );
+		aNew.setG( g );
+
+		s = openSession();
+		s.beginTransaction();
+		try {
+			s.merge( a );
+			s.merge( g );
+			fail( "should have thrown TransientObjectException" );
+		}
+		catch ( TransientObjectException ex ) {
+			// expected
+		}
+		finally {
+			s.getTransaction().rollback();
+		}
+		s.close();
+	}
+
+	public void testMultiPathMergeNonCascadedTransientEntityInManyToOne() throws Exception
+	{
+		// persist a simple A in the database
+
+		Session s = openSession();
+		s.beginTransaction();
+		A a = new A();
+		a.setData( "Anna" );
+		s.save( a );
+		s.getTransaction().commit();
+		s.close();
+
+		// modify detached entity
+		modifyEntity( a );
+
+		s = openSession();
+		s.beginTransaction();
+		a = ( A ) s.merge( a );
+		s.getTransaction().commit();
+		s.close();
+
+		verifyModifications( a.getId() );
+
+		// change the many-to-one association from h to be a new (transient) A
+		// there is no cascade from H to A, so this should fail when merged
+		assertEquals( 1, a.getHs().size() );
+		H h = ( H ) a.getHs().iterator().next();
+		a.getHs().remove( h );
+		A aNew = new A();
+		aNew.setData( "Alice" );
+		aNew.addH( h );
+
+		s = openSession();
+		s.beginTransaction();
+		try {
+			s.merge( a );
+			s.merge( h );
+			fail( "should have thrown TransientObjectException" );
+		}
+		catch ( TransientObjectException ex ) {
+			// expected
+		}
+		finally {
+			s.getTransaction().rollback();
+		}
+		s.close();
+	}
+
+	private void modifyEntity(A a) {
+		// create a *circular* graph in detached entity
+		a.setData("Anthony");
+
+		G g = new G();
+		g.setData( "Giovanni" );
+
+		H h = new H();
+		h.setData( "Hellen" );
+
+		a.setG( g );
+		g.setA( a );
+
+		a.getHs().add( h );
+		h.setA( a );
+
+		g.getHs().add( h );
+		h.getGs().add( g );
+	}
+
+	private void verifyModifications(long aId) {
+		Session s = openSession();
+		s.beginTransaction();
+
+		// retrieve the A object and check it
+		A a = ( A ) s.get( A.class, new Long( aId ) );
+		assertEquals( aId, a.getId() );
+		assertEquals( "Anthony", a.getData() );
+		assertNotNull( a.getG() );
+		assertNotNull( a.getHs() );
+		assertEquals( 1, a.getHs().size() );
+
+		G gFromA = a.getG();
+		H hFromA = ( H ) a.getHs().iterator().next();
+
+		// check the G object
+		assertEquals( "Giovanni", gFromA.getData() );
+		assertSame( a, gFromA.getA() );
+		assertNotNull( gFromA.getHs() );
+		assertEquals( a.getHs(), gFromA.getHs() );
+		assertSame( hFromA, gFromA.getHs().iterator().next() );
+
+		// check the H object
+		assertEquals( "Hellen", hFromA.getData() );
+		assertSame( a, hFromA.getA() );
+		assertNotNull( hFromA.getGs() );
+		assertEquals( 1, hFromA.getGs().size() );
+		assertSame( gFromA, hFromA.getGs().iterator().next() );
+
+		s.getTransaction().commit();
+		s.close();
+	}
+
+}




More information about the hibernate-commits mailing list