[hibernate-commits] Hibernate SVN: r14656 - in core/trunk: core/src/main/java/org/hibernate/type and 1 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Fri May 9 19:23:30 EDT 2008


Author: gbadner
Date: 2008-05-09 19:23:29 -0400 (Fri, 09 May 2008)
New Revision: 14656

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


Modified: core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java	2008-05-09 17:07:31 UTC (rev 14655)
+++ core/trunk/core/src/main/java/org/hibernate/event/def/DefaultMergeEventListener.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -1,24 +1,28 @@
-//$Id: DefaultMergeEventListener.java 10784 2006-11-11 05:13:01Z steve.ebersole at jboss.com $
+//$Id: DefaultMergeEventListener.java 14513 2008-04-17 23:05:11Z gbadner $
 package org.hibernate.event.def;
 
 import java.io.Serializable;
+import java.util.Iterator;
 import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 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,14 +105,13 @@
 				entity = original;
 			}
 			
-			if ( copyCache.containsKey(entity) ) {
+			if ( copyCache.containsKey(entity) &&
+					source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
 				log.trace("already merged");
 				event.setResult(entity);
 			}
 			else {
-
 				event.setEntity( entity );
-
 				int entityState = -1;
 
 				// Check the persistence context for an entry relating to this
@@ -116,7 +138,7 @@
 				if ( entityState == -1 ) {
 					entityState = getEntityState( entity, event.getEntityName(), entry, source );
 				}
-
+				
 				switch (entityState) {
 					case DETACHED:
 						entityIsDetached(event, copyCache);
@@ -128,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 )
@@ -170,11 +192,16 @@
 		final Serializable id = persister.hasIdentifierProperty() ?
 				persister.getIdentifier( entity, source.getEntityMode() ) :
 		        null;
+		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 );
 
-		final Object copy = persister.instantiate( id, source.getEntityMode() );  //TODO: should this be Session.instantiate(Persister, ...)?
-		copyCache.put(entity, copy); //before cascade!
-		
-		// cascade first, so that all unsaved objects get their 
+		// cascade first, so that all unsaved objects get their
 		// copy created before we actually copy
 		//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
 		super.cascadeBeforeSave(source, persister, entity, copyCache);
@@ -330,7 +357,7 @@
 			return entry.isExistsInDatabase();
 		}
 	}
-
+	
 	protected void copyValues(
 		final EntityPersister persister, 
 		final Object entity, 
@@ -353,8 +380,8 @@
 
 	protected void copyValues(
 			final EntityPersister persister,
-			final Object entity,
-			final Object target,
+			final Object entity, 
+			final Object target, 
 			final SessionImplementor source,
 			final Map copyCache,
 			final ForeignKeyDirection foreignKeyDirection) {

Modified: core/trunk/core/src/main/java/org/hibernate/type/EntityType.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/type/EntityType.java	2008-05-09 17:07:31 UTC (rev 14655)
+++ core/trunk/core/src/main/java/org/hibernate/type/EntityType.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -1,4 +1,4 @@
-//$Id: EntityType.java 10777 2006-11-08 22:02:28Z steve.ebersole at jboss.com $
+//$Id: EntityType.java 14513 2008-04-17 23:05:11Z gbadner $
 package org.hibernate.type;
 
 import java.io.Serializable;
@@ -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 );
+			}
 		}
 	}
 
@@ -451,7 +461,7 @@
 		if ( value == null ) {
 			return "null";
 		}
-
+		
 		EntityPersister persister = factory.getEntityPersister( associatedEntityName );
 		StringBuffer result = new StringBuffer().append( associatedEntityName );
 
@@ -467,11 +477,11 @@
 			else {
 				id = getIdentifier( value, persister, entityMode );
 			}
-
+			
 			result.append( '#' )
 				.append( persister.getIdentifierType().toLoggableString( id, factory ) );
 		}
-
+		
 		return result.toString();
 	}
 
@@ -537,7 +547,7 @@
 			return uniqueKeyPropertyName;
 		}
 	}
-
+	
 	protected abstract boolean isNullable();
 
 	/**
@@ -584,9 +594,9 @@
 	 * @throws HibernateException generally indicates problems performing the load.
 	 */
 	public Object loadByUniqueKey(
-			String entityName,
-			String uniqueKeyPropertyName,
-			Object key,
+			String entityName, 
+			String uniqueKeyPropertyName, 
+			Object key, 
 			SessionImplementor session) throws HibernateException {
 		final SessionFactoryImplementor factory = session.getFactory();
 		UniqueKeyLoadable persister = ( UniqueKeyLoadable ) factory.getEntityPersister( entityName );
@@ -594,11 +604,11 @@
 		//TODO: implement caching?! proxies?!
 
 		EntityUniqueKey euk = new EntityUniqueKey(
-				entityName,
-				uniqueKeyPropertyName,
-				key,
+				entityName, 
+				uniqueKeyPropertyName, 
+				key, 
 				getIdentifierOrUniqueKeyType( factory ),
-				session.getEntityMode(),
+				session.getEntityMode(), 
 				session.getFactory()
 		);
 

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -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/trunk/testsuite/src/test/java/org/hibernate/test/cascade/A.java
___________________________________________________________________
Name: svn:executable
   + *

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/CascadeSuite.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/CascadeSuite.java	2008-05-09 17:07:31 UTC (rev 14655)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/CascadeSuite.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -12,6 +12,7 @@
 		TestSuite suite = new TestSuite( "Cascade tests" );
 		suite.addTest( BidirectionalOneToManyCascadeTest.suite() );
 		suite.addTest( RefreshTest.suite() );
+		suite.addTest( MultiPathCascadeTest.suite() );
 		return suite;
 	}
 }

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -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/trunk/testsuite/src/test/java/org/hibernate/test/cascade/G.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -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/trunk/testsuite/src/test/java/org/hibernate/test/cascade/H.java
___________________________________________________________________
Name: svn:executable
   + *

Added: core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascade.hbm.xml
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascade.hbm.xml	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascade.hbm.xml	2008-05-09 23:23:29 UTC (rev 14656)
@@ -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/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java	                        (rev 0)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/cascade/MultiPathCascadeTest.java	2008-05-09 23:23:29 UTC (rev 14656)
@@ -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