Author: gbadner
Date: 2009-05-13 18:17:08 -0400 (Wed, 13 May 2009)
New Revision: 16561
Added:
core/branches/Branch_3_2/src/org/hibernate/event/def/CopyCache.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Node.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Route.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Tour.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Transport.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Vehicle.java
Modified:
core/branches/Branch_3_2/src/org/hibernate/event/def/DefaultMergeEventListener.java
core/branches/Branch_3_2/test/org/hibernate/test/AllTests.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/A.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/G.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/H.java
core/branches/Branch_3_2/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
core/branches/Branch_3_2/test/org/hibernate/test/ops/MergeTest.java
Log:
HHH-3810 : Transient entities can be inserted twice on merge
Added: core/branches/Branch_3_2/src/org/hibernate/event/def/CopyCache.java
===================================================================
--- core/branches/Branch_3_2/src/org/hibernate/event/def/CopyCache.java
(rev 0)
+++ core/branches/Branch_3_2/src/org/hibernate/event/def/CopyCache.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,244 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.event.def;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.hibernate.util.IdentityMap;
+import org.hibernate.AssertionFailure;
+
+/**
+ * CopyCache is intended to be the Map implementation used by
+ * {@link DefaultMergeEventListener} to keep track of entities and their copies
+ * being merged into the session. This implementation also tracks whether a
+ * an entity in the CopyCache is included in the merge. This allows a
+ * an entity and its copy to be added to a CopyCache before merge has cascaded
+ * to that entity.
+ *
+ * @author Gail Badner
+ */
+class CopyCache implements Map {
+ private Map entityToCopyMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value can be either a copy of the entity or the entity itself
+
+ private Map entityToIncludeInMergeFlagMap = IdentityMap.instantiate(10);
+ // key is an entity involved with the merge;
+ // value is a flag indicating if the entity is included in the merge
+
+ /**
+ * Clears the CopyCache.
+ */
+ public void clear() {
+ entityToCopyMap.clear();
+ entityToIncludeInMergeFlagMap.clear();
+ }
+
+ /**
+ * Returns true if this CopyCache contains a mapping for the specified entity.
+ * @param entity must be non-null
+ * @return true if this CopyCache contains a mapping for the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public boolean containsKey(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " +
getClass().getName() );
+ }
+ return entityToCopyMap.containsKey( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache maps one or more entities to the specified copy.
+ * @param copy must be non-null
+ * @return true if this CopyCache maps one or more entities to the specified copy
+ * @throws NullPointerException if copy is null
+ */
+ public boolean containsValue(Object copy) {
+ if ( copy == null ) {
+ throw new NullPointerException( "null copies are not supported by " +
getClass().getName() );
+ }
+ return entityToCopyMap.containsValue( copy );
+ }
+
+ /**
+ * Returns a set view of the entity-to-copy mappings contained in this CopyCache.
+ * @return set view of the entity-to-copy mappings contained in this CopyCache
+ */
+ public Set entrySet() {
+ return entityToCopyMap.entrySet();
+ }
+
+ /**
+ * Returns the copy to which this CopyCache maps the specified entity.
+ * @param entity must be non-null
+ * @return the copy to which this CopyCache maps the specified entity
+ * @throws NullPointerException if entity is null
+ */
+ public Object get(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " +
getClass().getName() );
+ }
+ return entityToCopyMap.get( entity );
+ }
+
+ /**
+ * Returns true if this CopyCache contains no entity-copy mappings.
+ * @return true if this CopyCache contains no entity-copy mappings
+ */
+ public boolean isEmpty() {
+ return entityToCopyMap.isEmpty();
+ }
+
+ /**
+ * Returns a set view of the entities contained in this CopyCache
+ * @return a set view of the entities contained in this CopyCache
+ */
+ public Set keySet() {
+ return entityToCopyMap.keySet();
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ public Object put(Object entity, Object copy) {
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by
" + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.FALSE );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Associates the specified entity with the specified copy in this CopyCache;
+ * @param entity must be non-null
+ * @param copy must be non- null
+ * @param isIncludedInMerge indicates if the entity is included in merge
+ *
+ * @return previous copy associated with specified entity, or null if
+ * there was no mapping for entity.
+ * @throws NullPointerException if entity or copy is null
+ */
+ /* package-private */ Object put(Object entity, Object copy, boolean isIncludedInMerge)
{
+ if ( entity == null || copy == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by
" + getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ return entityToCopyMap.put( entity, copy );
+ }
+
+ /**
+ * Copies all of the mappings from the specified map to this CopyCache
+ * @param map keys and values must be non-null
+ * @throws NullPointerException if any map keys or values are null
+ */
+ public void putAll(Map map) {
+ for ( Iterator it=map.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry entry = ( Map.Entry ) it.next();
+ if ( entry.getKey() == null || entry.getValue() == null ) {
+ throw new NullPointerException( "null entities and copies are not supported by
" + getClass().getName() );
+ }
+ entityToCopyMap.put( entry.getKey(), entry.getValue() );
+ entityToIncludeInMergeFlagMap.put( entry.getKey(), Boolean.FALSE );
+ }
+ }
+
+ /**
+ * Removes the mapping for this entity from this CopyCache if it is present
+ * @param entity must be non-null
+ * @return previous value associated with specified entity, or null if there was no
mapping for entity.
+ * @throws NullPointerException if entity is null
+ */
+ public Object remove(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " +
getClass().getName() );
+ }
+ entityToIncludeInMergeFlagMap.remove( entity );
+ return entityToCopyMap.remove( entity );
+ }
+
+ /**
+ * Returns the number of entity-copy mappings in this CopyCache
+ * @return the number of entity-copy mappings in this CopyCache
+ */
+ public int size() {
+ return entityToCopyMap.size();
+ }
+
+ /**
+ * Returns a collection view of the entity copies contained in this CopyCache.
+ * @return a collection view of the entity copies contained in this CopyCache
+ */
+ public Collection values() {
+ return entityToCopyMap.values();
+ }
+
+ /**
+ * Returns true if the specified entity is included in the merge.
+ * @param entity must be non-null
+ * @return true if the specified entity is included in the merge.
+ * @throws NullPointerException if entity is null
+ */
+ public boolean isIncludedInMerge(Object entity) {
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " +
getClass().getName() );
+ }
+ return ( ( Boolean ) entityToIncludeInMergeFlagMap.get( entity ) ).booleanValue();
+ }
+
+ /**
+ * Set flag to indicate if an entity is included in the merge.
+ * @param entity must be non-null and this CopyCache must contain a mapping for this
entity
+ * @return true if the specified entity is included in the merge
+ * @throws NullPointerException if entity is null
+ * @throws AssertionFailure if this CopyCache does not contain a mapping for the
specified entity
+ */
+ /* package-private */ void setIncludedInMerge(Object entity, boolean isIncludedInMerge)
{
+ if ( entity == null ) {
+ throw new NullPointerException( "null entities are not supported by " +
getClass().getName() );
+ }
+ if ( ! entityToIncludeInMergeFlagMap.containsKey( entity ) ||
+ ! entityToCopyMap.containsKey( entity ) ) {
+ throw new AssertionFailure( "called CopyCache.setInMergeProcess() for entity not
found in CopyCache" );
+ }
+ entityToIncludeInMergeFlagMap.put( entity, Boolean.valueOf( isIncludedInMerge ) );
+ }
+
+ /**
+ * Returns the copy-entity mappings
+ * @return the copy-entity mappings
+ */
+ public Map getMergeMap() {
+ return IdentityMap.invert( entityToCopyMap );
+ }
+}
\ No newline at end of file
Modified:
core/branches/Branch_3_2/src/org/hibernate/event/def/DefaultMergeEventListener.java
===================================================================
---
core/branches/Branch_3_2/src/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-13
13:39:03 UTC (rev 16560)
+++
core/branches/Branch_3_2/src/org/hibernate/event/def/DefaultMergeEventListener.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -28,6 +28,8 @@
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -38,6 +40,7 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.TransientObjectException;
import org.hibernate.WrongClassException;
+import org.hibernate.PropertyValueException;
import org.hibernate.engine.Cascade;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
@@ -54,7 +57,7 @@
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeFactory;
-import org.hibernate.util.IdentityMap;
+import org.hibernate.type.Type;
/**
* Defines the default copy event listener used by hibernate for copying entities
@@ -66,9 +69,9 @@
implements MergeEventListener {
private static final Log log = LogFactory.getLog(DefaultMergeEventListener.class);
-
+
protected Map getMergeMap(Object anything) {
- return IdentityMap.invert( (Map) anything );
+ return ( ( CopyCache ) anything ).getMergeMap();
}
/**
@@ -78,36 +81,96 @@
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
- Map copyCache = IdentityMap.instantiate(10);
+ CopyCache copyCache = new CopyCache();
onMerge( event, copyCache );
- for ( Iterator it=copyCache.values().iterator(); it.hasNext(); ) {
- Object entity = it.next();
- if ( entity instanceof HibernateProxy ) {
- entity = ( (HibernateProxy) entity
).getHibernateLazyInitializer().getImplementation();
+ // TODO: iteratively get transient entities and retry merge until one of the following
conditions:
+ // 1) transientCopyCache.size() == 0
+ // 2) transientCopyCache.size() is not decreasing and copyCache.size() is not
increasing
+ // TODO: find out if retrying can add entities to copyCache (don't think it
can...)
+ // For now, just retry once; throw TransientObjectException if there are still any
transient entities
+ Map transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ retryMergeTransientEntities( event, transientCopyCache, copyCache );
+ // find any entities that are still transient after retry
+ transientCopyCache = getTransientCopyCache(event, copyCache );
+ if ( transientCopyCache.size() > 0 ) {
+ Set transientEntityNames = new HashSet();
+ for( Iterator it=transientCopyCache.keySet().iterator(); it.hasNext(); ) {
+ Object transientEntity = it.next();
+ String transientEntityName = event.getSession().guessEntityName( transientEntity );
+ transientEntityNames.add( transientEntityName );
+ log.trace( "transient instance could not be processed by merge: " +
+ transientEntityName + "[" + transientEntity + "]" );
+ }
+ throw new TransientObjectException(
+ "one or more objects is an unsaved transient instance - save transient
instance(s) before merging: " +
+ transientEntityNames );
}
- EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
- if ( entry == null ) {
+ }
+ copyCache.clear();
+ copyCache = null;
+ }
+
+ protected CopyCache getTransientCopyCache(MergeEvent event, CopyCache copyCache) {
+ CopyCache transientCopyCache = new CopyCache();
+ for ( Iterator it=copyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = mapEntry.getValue();
+ if ( copy instanceof HibernateProxy ) {
+ copy = ( (HibernateProxy) copy ).getHibernateLazyInitializer().getImplementation();
+ }
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( copyEntry == null ) {
+ // entity name will not be available for non-POJO entities
+ // TODO: cache the entity name somewhere so that it is available to this exception
+ log.trace( "transient instance could not be processed by merge: " +
+ event.getSession().guessEntityName( copy ) + "[" + entity + "]"
);
throw new TransientObjectException(
- "object references an unsaved transient instance - save the transient instance
before merging: " +
- event.getSession().guessEntityName( entity )
+ "object is an unsaved transient instance - save the transient instance before
merging: " +
+ event.getSession().guessEntityName( copy )
);
- // 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 && entry.getStatus() !=
Status.READ_ONLY ) {
- throw new AssertionFailure( "Merged entity does not have status set to MANAGED
or READ_ONLY; "+entry+" status="+entry.getStatus() );
+ else if ( copyEntry.getStatus() == Status.SAVING ) {
+ transientCopyCache.put( entity, copy, copyCache.isIncludedInMerge( entity ) );
}
+ else if ( copyEntry.getStatus() != Status.MANAGED && copyEntry.getStatus() !=
Status.READ_ONLY ) {
+ throw new AssertionFailure( "Merged entity does not have status set to MANAGED
or READ_ONLY; "+copy+" status="+copyEntry.getStatus() );
+ }
}
+ return transientCopyCache;
}
+ protected void retryMergeTransientEntities(MergeEvent event, Map transientCopyCache,
CopyCache copyCache) {
+ // TODO: The order in which entities are saved may matter (e.g., a particular transient
entity
+ // may need to be saved before other transient entities can be saved;
+ // Keep retrying the batch of transient entities until either:
+ // 1) there are no transient entities left in transientCopyCache
+ // or 2) no transient entities were saved in the last batch
+ // For now, just run through the transient entities and retry the merge
+ for ( Iterator it=transientCopyCache.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry mapEntry = ( Map.Entry ) it.next();
+ Object entity = mapEntry.getKey();
+ Object copy = transientCopyCache.get( entity );
+ EntityEntry copyEntry = event.getSession().getPersistenceContext().getEntry( copy );
+ if ( entity == event.getEntity() ) {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), event.getRequestedId(),
event.getSession(), copyCache);
+ }
+ else {
+ mergeTransientEntity( entity, copyEntry.getEntityName(), copyEntry.getId(),
event.getSession(), copyCache);
+ }
+ }
+ }
+
/**
* Handle the given merge event.
*
* @param event The merge event to be handled.
* @throws HibernateException
*/
- public void onMerge(MergeEvent event, Map copyCache) throws HibernateException {
+ public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
+ final CopyCache copyCache = ( CopyCache ) copiedAlready;
final EventSource source = event.getSession();
final Object original = event.getOriginal();
@@ -128,13 +191,17 @@
else {
entity = original;
}
-
- if ( copyCache.containsKey(entity) &&
- source.getContextEntityIdentifier( copyCache.get( entity ) ) != null ) {
- log.trace("already merged");
- event.setResult(entity);
+
+ if ( copyCache.containsKey( entity ) &&
+ ( copyCache.isIncludedInMerge( entity ) ) ) {
+ log.trace("already in merge process");
+ event.setResult( entity );
}
else {
+ if ( copyCache.containsKey( entity ) ) {
+ log.trace("already in copyCache; setting in merge process");
+ copyCache.setIncludedInMerge( entity, true );
+ }
event.setEntity( entity );
int entityState = -1;
@@ -176,7 +243,7 @@
default: //DELETED
throw new ObjectDeletedException(
"deleted instance passed to merge",
- null,
+ null,
getLoggableName( event.getEntityName(), entity )
);
}
@@ -194,15 +261,15 @@
final Object entity = event.getEntity();
final EventSource source = event.getSession();
final EntityPersister persister = source.getEntityPersister( event.getEntityName(),
entity );
+
+ ( ( CopyCache ) copyCache ).put( entity, entity, true ); //before cascade!
- copyCache.put(entity, entity); //before cascade!
-
cascadeOnMerge(source, persister, entity, copyCache);
copyValues(persister, entity, entity, source, copyCache);
event.setResult(entity);
}
-
+
protected void entityIsTransient(MergeEvent event, Map copyCache) {
log.trace("merging transient instance");
@@ -212,7 +279,16 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(),
entity );
final String entityName = persister.getEntityName();
-
+
+ event.setResult( mergeTransientEntity( entity, entityName, event.getRequestedId(),
source, copyCache ) );
+ }
+
+ protected Object mergeTransientEntity(Object entity, String entityName, Serializable
requestedId, EventSource source, Map copyCache) {
+
+ log.trace("merging transient instance");
+
+ final EntityPersister persister = source.getEntityPersister( entityName, entity );
+
final Serializable id = persister.hasIdentifierProperty() ?
persister.getIdentifier( entity, source.getEntityMode() ) :
null;
@@ -220,7 +296,7 @@
persister.setIdentifier( copyCache.get( entity ), id, source.getEntityMode() );
}
else {
- copyCache.put(entity, persister.instantiate( id, source.getEntityMode() ) ); //before
cascade!
+ ( ( CopyCache ) copyCache ).put( entity, persister.instantiate( id,
source.getEntityMode() ), true ); //before cascade!
//TODO: should this be Session.instantiate(Persister, ...)?
}
final Object copy = copyCache.get( entity );
@@ -230,25 +306,54 @@
//cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE);
super.cascadeBeforeSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache,
ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT);
-
- //this bit is only *really* absolutely necessary for handling
- //requestedId, but is also good if we merge multiple object
- //graphs, since it helps ensure uniqueness
- final Serializable requestedId = event.getRequestedId();
- if (requestedId==null) {
- saveWithGeneratedId( copy, entityName, copyCache, source, false );
+
+ try {
+ //this bit is only *really* absolutely necessary for handling
+ //requestedId, but is also good if we merge multiple object
+ //graphs, since it helps ensure uniqueness
+ if (requestedId==null) {
+ saveWithGeneratedId( copy, entityName, copyCache, source, false );
+ }
+ else {
+ saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ }
}
- else {
- saveWithRequestedId( copy, requestedId, entityName, copyCache, source );
+ catch (PropertyValueException ex) {
+ String propertyName = ex.getPropertyName();
+ Object propertyFromCopy = persister.getPropertyValue( copy, propertyName,
source.getEntityMode() );
+ Object propertyFromEntity = persister.getPropertyValue( entity, propertyName,
source.getEntityMode() );
+ Type propertyType = persister.getPropertyType( propertyName );
+ EntityEntry copyEntry = source.getPersistenceContext().getEntry( copy );
+ if ( propertyFromCopy == null || ! propertyType.isEntityType() ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." +
propertyName +
+ "' is null or not an entity; " + propertyName + "
=["+propertyFromCopy+"]");
+ throw ex;
+ }
+ if ( ! copyCache.containsKey( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." +
propertyName +
+ "' from original entity is not in copyCache; " + propertyName +
" =["+propertyFromEntity+"]");
+ throw ex;
+ }
+ if ( ( ( CopyCache ) copyCache ).isIncludedInMerge( propertyFromEntity ) ) {
+ log.trace( "property '" + copyEntry.getEntityName() + "." +
propertyName +
+ "' from original entity is in copyCache and is in the process of being
merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ else {
+ log.trace( "property '" + copyEntry.getEntityName() + "." +
propertyName +
+ "' from original entity is in copyCache and is not in the process of being
merged; " +
+ propertyName + " =["+propertyFromEntity+"]");
+ }
+ // continue...; we'll find out if it ends up not getting saved later
}
-
- // cascade first, so that all unsaved objects get their
+
+ // cascade first, so that all unsaved objects get their
// copy created before we actually copy
super.cascadeAfterSave(source, persister, entity, copyCache);
copyValues(persister, entity, copy, source, copyCache,
ForeignKeyDirection.FOREIGN_KEY_TO_PARENT);
-
- event.setResult(copy);
+ return copy;
+
}
protected void entityIsDetached(MergeEvent event, Map copyCache) {
@@ -260,7 +365,7 @@
final EntityPersister persister = source.getEntityPersister( event.getEntityName(),
entity );
final String entityName = persister.getEntityName();
-
+
Serializable id = event.getRequestedId();
if ( id == null ) {
id = persister.getIdentifier( entity, source.getEntityMode() );
@@ -293,7 +398,7 @@
entityIsTransient(event, copyCache);
}
else {
- copyCache.put(entity, result); //before cascade!
+ ( ( CopyCache ) copyCache ).put( entity, result, true ); //before cascade!
final Object target = source.getPersistenceContext().unproxy(result);
if ( target == entity ) {
@@ -381,7 +486,7 @@
return entry.isExistsInDatabase();
}
}
-
+
protected void copyValues(
final EntityPersister persister,
final Object entity,
@@ -389,7 +494,6 @@
final SessionImplementor source,
final Map copyCache
) {
-
final Object[] copiedValues = TypeFactory.replace(
persister.getPropertyValues( entity, source.getEntityMode() ),
persister.getPropertyValues( target, source.getEntityMode() ),
Modified: core/branches/Branch_3_2/test/org/hibernate/test/AllTests.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/AllTests.java 2009-05-13 13:39:03 UTC
(rev 16560)
+++ core/branches/Branch_3_2/test/org/hibernate/test/AllTests.java 2009-05-13 22:17:08 UTC
(rev 16561)
@@ -22,6 +22,8 @@
import org.hibernate.test.cascade.BidirectionalOneToManyCascadeTest;
import org.hibernate.test.cascade.MultiPathCascadeTest;
import org.hibernate.test.cascade.RefreshTest;
+import org.hibernate.test.cascade.circle.CascadeMergeToChildBeforeParentTest;
+import org.hibernate.test.cascade.circle.MultiPathCircleCascadeTest;
import org.hibernate.test.cfg.ListenerTest;
import org.hibernate.test.cid.CompositeIdTest;
import org.hibernate.test.collection.bag.PersistentBagTest;
@@ -404,6 +406,8 @@
suite.addTest( BidirectionalOneToManyCascadeTest.suite() );
suite.addTest( RefreshTest.suite() );
suite.addTest( MultiPathCascadeTest.suite() );
+ suite.addTest( CascadeMergeToChildBeforeParentTest.suite() );
+ suite.addTest( MultiPathCircleCascadeTest.suite() );
suite.addTest( ListenerTest.suite() );
suite.addTest( BrokenCollectionEventTest.suite() );
suite.addTest( BidirectionalManyToManyBagToSetCollectionEventTest.suite() );
Modified: core/branches/Branch_3_2/test/org/hibernate/test/cascade/A.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/A.java 2009-05-13 13:39:03
UTC (rev 16560)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/A.java 2009-05-13 22:17:08
UTC (rev 16561)
@@ -1,4 +1,28 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade;
Modified: core/branches/Branch_3_2/test/org/hibernate/test/cascade/G.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/G.java 2009-05-13 13:39:03
UTC (rev 16560)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/G.java 2009-05-13 22:17:08
UTC (rev 16561)
@@ -1,3 +1,29 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade;
import java.util.Set;
Modified: core/branches/Branch_3_2/test/org/hibernate/test/cascade/H.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/H.java 2009-05-13 13:39:03
UTC (rev 16560)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/H.java 2009-05-13 22:17:08
UTC (rev 16561)
@@ -1,6 +1,29 @@
-// $Id$
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade;
import java.util.Set;
Modified:
core/branches/Branch_3_2/test/org/hibernate/test/cascade/MultiPathCascadeTest.java
===================================================================
---
core/branches/Branch_3_2/test/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-13
13:39:03 UTC (rev 16560)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/MultiPathCascadeTest.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -1,4 +1,28 @@
//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade;
Added:
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
===================================================================
---
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator
class="native"/></id>
+ <version name="version" column="VERS"
type="long" />
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="nodes" inverse="true"
cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ <set name="vehicles" inverse="true"
cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Vehicle"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator
class="native"/></id>
+ <version name="version" column="VERS"
type="long" />
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true"
cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator
class="native"/></id>
+ <version name="version" column="VERS"
type="long" />
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="vehicle"
+ column="vehicleID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+ <class name="Vehicle" table="HB_Vehicle">
+ <id name="vehicleID" type="long"><generator
class="native"/></id>
+ <version name="version" column="VERS"
type="long" />
+
+ <property name="name"/>
+ <set name="transports" inverse="false"
lazy="true" cascade="merge,refresh">
+ <key column="vehicleID"/>
+ <one-to-many class="Transport"
not-found="exception"/>
+ </set>
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+ </class>
+
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator
class="native"/></id>
+ <version name="version" column="VERS"
type="long" />
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true"
lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true"
lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added:
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
===================================================================
---
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/CascadeMergeToChildBeforeParentTest.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,288 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * The test case uses the following model:
+ *
+ * <- ->
+ * -- (N : 0,1) -- Tour
+ * | <- ->
+ * | -- (1 : N) -- (pickup) ----
+ * -> | | |
+ * Route -- (1 : N) - Node Transport
+ * | | <- -> | |
+ * | -- (1 : N) -- (delivery) -- |
+ * | |
+ * | -> -> |
+ * -------- (1 : N) ---- Vehicle--(1 : N)------------
+ *
+ * Arrows indicate the direction of cascade-merge.
+ *
+ * I believe it reproduces the following issue:
+ *
http://opensource.atlassian.com/projects/hibernate/browse/HHH-3544
+ *
+ * @author Gail Badner (based on original model provided by Pavol Zibrita)
+ */
+public class CascadeMergeToChildBeforeParentTest extends FunctionalTestCase {
+
+ public CascadeMergeToChildBeforeParentTest(String string) {
+ super(string);
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/CascadeMergeToChildBeforeParent.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( CascadeMergeToChildBeforeParentTest.class );
+ }
+
+ protected void cleanupTest() {
+ Session s = openSession();
+ s.beginTransaction();
+ s.createQuery( "delete from Transport" );
+ s.createQuery( "delete from Tour" );
+ s.createQuery( "delete from Node" );
+ s.createQuery( "delete from Route" );
+ s.createQuery( "delete from Vehicle" );
+ }
+
+ public void testMerge()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ // This test fails because the merge algorithm tries to save a
+ // transient child (transport) before cascade-merge gets its
+ // transient parent (vehicle); merge does not cascade from the
+ // child to the parent.
+ public void testMergeTransientChildBeforeTransientParent()
+ {
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Transport transport = new Transport();
+ transport.setName("transportB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.getPickupTransports().add(transport);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.getDeliveryTransports().add(transport);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+ route.getVehicles().add(vehicle);
+
+ transport.setPickupNode(pickupNode);
+ transport.setDeliveryNode(deliveryNode);
+ transport.setVehicle( vehicle );
+ transport.setTransientField("aaaaaaaaaaaaaa");
+
+ vehicle.getTransports().add(transport);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeData3Nodes()
+ {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Transport transport1 = new Transport();
+ transport1.setName("TRANSPORT1");
+
+ Transport transport2 = new Transport();
+ transport2.setName("TRANSPORT2");
+
+ Node node1 = new Node();
+ node1.setName("NODE1");
+
+ Node node2 = new Node();
+ node2.setName("NODE2");
+
+ Node node3 = new Node();
+ node3.setName("NODE3");
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setName("vehicleB");
+
+ node1.setRoute(route);
+ node1.setTour(tour);
+ node1.getPickupTransports().add(transport1);
+ node1.setTransientField("node 1");
+
+ node2.setRoute(route);
+ node2.setTour(tour);
+ node2.getDeliveryTransports().add(transport1);
+ node2.getPickupTransports().add(transport2);
+ node2.setTransientField("node 2");
+
+ node3.setRoute(route);
+ node3.setTour(tour);
+ node3.getDeliveryTransports().add(transport2);
+ node3.setTransientField("node 3");
+
+ tour.getNodes().add(node1);
+ tour.getNodes().add(node2);
+ tour.getNodes().add(node3);
+
+ route.getNodes().add(node1);
+ route.getNodes().add(node2);
+ route.getNodes().add(node3);
+ route.getVehicles().add(vehicle);
+
+ transport1.setPickupNode(node1);
+ transport1.setDeliveryNode(node2);
+ transport1.setVehicle( vehicle );
+ transport1.setTransientField("aaaaaaaaaaaaaa");
+
+ transport2.setPickupNode(node2);
+ transport2.setDeliveryNode(node3);
+ transport2.setVehicle( vehicle );
+ transport2.setTransientField("bbbbbbbbbbbbb");
+
+ vehicle.getTransports().add(transport1);
+ vehicle.getTransports().add(transport2);
+ vehicle.setTransientField( "anewvalue" );
+ vehicle.setRoute( route );
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+ }
+
+}
Added:
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
===================================================================
---
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascade.hbm.xml 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.cascade.circle">
+
+ <class name="Route" table="HB_Route">
+
+ <id name="routeID" type="long"><generator
class="native"/></id>
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="nodes" inverse="true"
cascade="persist,merge,refresh">
+ <key column="routeID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Tour" table="HB_Tour">
+
+ <id name="tourID" type="long"><generator
class="native"/></id>
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="nodes" inverse="true" lazy="true"
cascade="merge,refresh">
+ <key column="tourID"/>
+ <one-to-many class="Node"/>
+ </set>
+ </class>
+
+ <class name="Transport" table="HB_Transport">
+
+ <id name="transportID" type="long"><generator
class="native"/></id>
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <many-to-one name="pickupNode"
+ column="pickupNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+
+ <many-to-one name="deliveryNode"
+ column="deliveryNodeID"
+ unique="true"
+ not-null="true"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+ <class name="Node" table="HB_Node">
+
+ <id name="nodeID" type="long"><generator
class="native"/></id>
+
+ <property name="name" type="string"
not-null="true"/>
+
+ <set name="deliveryTransports" inverse="true"
lazy="true" cascade="merge,refresh">
+ <key column="deliveryNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <set name="pickupTransports" inverse="true"
lazy="true" cascade="merge,refresh">
+ <key column="pickupNodeID"/>
+ <one-to-many class="Transport"/>
+ </set>
+
+ <many-to-one name="route"
+ column="routeID"
+ unique="false"
+ not-null="true"
+ cascade="none"
+ lazy="false"/>
+
+ <many-to-one name="tour"
+ column="tourID"
+ unique="false"
+ not-null="false"
+ cascade="merge,refresh"
+ lazy="false"/>
+ </class>
+
+</hibernate-mapping>
Added:
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
===================================================================
---
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/MultiPathCircleCascadeTest.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,459 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import java.util.Iterator;
+
+import junit.framework.Test;
+
+import org.hibernate.Session;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.cfg.Environment;
+import org.hibernate.junit.functional.FunctionalTestCase;
+import org.hibernate.junit.functional.FunctionalTestClassTestSuite;
+
+/**
+ * The test case uses the following model:
+ *
+ * <- ->
+ * -- (N : 0,1) -- Tour
+ * | <- ->
+ * | -- (1 : N) -- (pickup) ----
+ * -> | | |
+ * Route -- (1 : N) -- Node Transport
+ * | <- -> |
+ * -- (1 : N) -- (delivery) --
+ *
+ * Arrows indicate the direction of cascade-merge.
+ *
+ * It reproduced the following issues:
+ *
http://opensource.atlassian.com/projects/hibernate/browse/HHH-3046
+ *
http://opensource.atlassian.com/projects/hibernate/browse/HHH-3810
+ *
+ * This tests that merge is cascaded properly from each entity.
+ *
+ * @author Pavol Zibrita, Gail Badner
+ */
+public class MultiPathCircleCascadeTest extends FunctionalTestCase {
+
+ public MultiPathCircleCascadeTest(String string) {
+ super(string);
+ }
+
+ public void configure(Configuration cfg) {
+ cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
+ cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
+ }
+
+ public String[] getMappings() {
+ return new String[] {
+ "cascade/circle/MultiPathCircleCascade.hbm.xml"
+ };
+ }
+
+ public static Test suite() {
+ return new FunctionalTestClassTestSuite( MultiPathCircleCascadeTest.class );
+ }
+
+ protected void cleanupTest() {
+ Session s = openSession();
+ s.beginTransaction();
+ s.createQuery( "delete from Transport" );
+ s.createQuery( "delete from Tour" );
+ s.createQuery( "delete from Node" );
+ s.createQuery( "delete from Route" );
+ }
+
+ public void testMergeRoute()
+ {
+
+ Route route = getUpdatedDetachedEntity();
+
+ clearCounts();
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 4 );
+ assertUpdateCount( 1 );
+
+ s = openSession();
+ s.beginTransaction();
+ route = ( Route ) s.get( Route.class, route.getRouteID() );
+ checkResults( route, true );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergePickupNode()
+ {
+
+ Route route = getUpdatedDetachedEntity();
+
+ clearCounts();
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Iterator it=route.getNodes().iterator();
+ Node node = ( Node ) it.next();
+ Node pickupNode;
+ if ( node.getName().equals( "pickupNodeB") ) {
+ pickupNode = node;
+ }
+ else {
+ node = ( Node ) it.next();
+ assertEquals( "pickupNodeB", node.getName() );
+ pickupNode = node;
+ }
+
+ pickupNode = ( Node ) s.merge( pickupNode );
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 4 );
+ assertUpdateCount( 0 );
+
+ s = openSession();
+ s.beginTransaction();
+ route = ( Route ) s.get( Route.class, route.getRouteID() );
+ checkResults( route, false );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeDeliveryNode()
+ {
+
+ Route route = getUpdatedDetachedEntity();
+
+ clearCounts();
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Iterator it=route.getNodes().iterator();
+ Node node = ( Node ) it.next();
+ Node deliveryNode;
+ if ( node.getName().equals( "deliveryNodeB") ) {
+ deliveryNode = node;
+ }
+ else {
+ node = ( Node ) it.next();
+ assertEquals( "deliveryNodeB", node.getName() );
+ deliveryNode = node;
+ }
+
+ deliveryNode = ( Node ) s.merge( deliveryNode );
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 4 );
+ assertUpdateCount( 0 );
+
+ s = openSession();
+ s.beginTransaction();
+ route = ( Route ) s.get( Route.class, route.getRouteID() );
+ checkResults( route, false );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeTour()
+ {
+
+ Route route = getUpdatedDetachedEntity();
+
+ clearCounts();
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Tour tour = ( Tour ) s.merge( ( ( Node ) route.getNodes().toArray()[0]).getTour() );
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 4 );
+ assertUpdateCount( 0 );
+
+ s = openSession();
+ s.beginTransaction();
+ route = ( Route ) s.get( Route.class, route.getRouteID() );
+ checkResults( route, false );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ public void testMergeTransport()
+ {
+
+ Route route = getUpdatedDetachedEntity();
+
+ clearCounts();
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Node node = ( ( Node ) route.getNodes().toArray()[0]);
+ Transport transport;
+ if ( node.getPickupTransports().size() == 1 ) {
+ transport = ( Transport ) node.getPickupTransports().toArray()[0];
+ }
+ else {
+ transport = ( Transport ) node.getDeliveryTransports().toArray()[0];
+ }
+
+ transport = ( Transport ) s.merge( transport );
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 4 );
+ assertUpdateCount( 0 );
+
+ s = openSession();
+ s.beginTransaction();
+ route = ( Route ) s.get( Route.class, route.getRouteID() );
+ checkResults( route, false );
+ s.getTransaction().commit();
+ s.close();
+ }
+
+ private Route getUpdatedDetachedEntity() {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ route.setName( "new routeA" );
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Transport transport = new Transport();
+ transport.setName("transportB");
+
+ Node pickupNode = new Node();
+ pickupNode.setName("pickupNodeB");
+
+ Node deliveryNode = new Node();
+ deliveryNode.setName("deliveryNodeB");
+
+ pickupNode.setRoute(route);
+ pickupNode.setTour(tour);
+ pickupNode.getPickupTransports().add(transport);
+ pickupNode.setTransientField("pickup node aaaaaaaaaaa");
+
+ deliveryNode.setRoute(route);
+ deliveryNode.setTour(tour);
+ deliveryNode.getDeliveryTransports().add(transport);
+ deliveryNode.setTransientField("delivery node aaaaaaaaa");
+
+ tour.getNodes().add(pickupNode);
+ tour.getNodes().add(deliveryNode);
+
+ route.getNodes().add(pickupNode);
+ route.getNodes().add(deliveryNode);
+
+ transport.setPickupNode(pickupNode);
+ transport.setDeliveryNode(deliveryNode);
+ transport.setTransientField("aaaaaaaaaaaaaa");
+
+ return route;
+ }
+
+ private void checkResults(Route route, boolean isRouteUpdated) {
+ // since merge is not cascaded to route, this method needs to
+ // know whether route is expected to be updated
+ if ( isRouteUpdated ) {
+ assertEquals( "new routeA", route.getName() );
+ }
+ assertEquals( 2, route.getNodes().size() );
+ Node deliveryNode = null;
+ Node pickupNode = null;
+ for( Iterator it=route.getNodes().iterator(); it.hasNext(); ) {
+ Node node = ( Node ) it.next();
+ if( "deliveryNodeB".equals( node.getName( ) ) ) {
+ deliveryNode = node;
+ }
+ else if( "pickupNodeB".equals( node.getName() ) ) {
+ pickupNode = node;
+ }
+ else {
+ fail( "unknown node");
+ }
+ }
+ assertNotNull( deliveryNode );
+ assertSame( route, deliveryNode.getRoute() );
+ assertEquals( 1, deliveryNode.getDeliveryTransports().size() );
+ assertEquals( 0, deliveryNode.getPickupTransports().size() );
+ assertNotNull( deliveryNode.getTour() );
+ assertEquals( "node original value", deliveryNode.getTransientField() );
+
+ assertNotNull( pickupNode );
+ assertSame( route, pickupNode.getRoute() );
+ assertEquals( 0, pickupNode.getDeliveryTransports().size() );
+ assertEquals( 1, pickupNode.getPickupTransports().size() );
+ assertNotNull( pickupNode.getTour() );
+ assertEquals( "node original value", pickupNode.getTransientField() );
+
+ assertTrue( ! deliveryNode.getNodeID().equals( pickupNode.getNodeID() ) );
+ assertSame( deliveryNode.getTour(), pickupNode.getTour() );
+ assertSame( deliveryNode.getDeliveryTransports().iterator().next(),
+ pickupNode.getPickupTransports().iterator().next() );
+
+ Tour tour = deliveryNode.getTour();
+ Transport transport = ( Transport )
deliveryNode.getDeliveryTransports().iterator().next();
+
+ assertEquals( "tourB", tour.getName() );
+ assertEquals( 2, tour.getNodes().size() );
+ assertTrue( tour.getNodes().contains( deliveryNode ) );
+ assertTrue( tour.getNodes().contains( pickupNode ) );
+
+ assertEquals( "transportB", transport.getName() );
+ assertSame( deliveryNode, transport.getDeliveryNode() );
+ assertSame( pickupNode, transport.getPickupNode() );
+ assertEquals( "transport original value", transport.getTransientField() );
+ }
+
+ public void testMergeData3Nodes()
+ {
+
+ Session s = openSession();
+ s.beginTransaction();
+
+ Route route = new Route();
+ route.setName("routeA");
+
+ s.save( route );
+ s.getTransaction().commit();
+ s.close();
+
+ clearCounts();
+
+ s = openSession();
+ s.beginTransaction();
+
+ route = (Route) s.get(Route.class, new Long(1));
+ //System.out.println(route);
+ route.setName( "new routA" );
+
+ route.setTransientField(new String("sfnaouisrbn"));
+
+ Tour tour = new Tour();
+ tour.setName("tourB");
+
+ Transport transport1 = new Transport();
+ transport1.setName("TRANSPORT1");
+
+ Transport transport2 = new Transport();
+ transport2.setName("TRANSPORT2");
+
+ Node node1 = new Node();
+ node1.setName("NODE1");
+
+ Node node2 = new Node();
+ node2.setName("NODE2");
+
+ Node node3 = new Node();
+ node3.setName("NODE3");
+
+ node1.setRoute(route);
+ node1.setTour(tour);
+ node1.getPickupTransports().add(transport1);
+ node1.setTransientField("node 1");
+
+ node2.setRoute(route);
+ node2.setTour(tour);
+ node2.getDeliveryTransports().add(transport1);
+ node2.getPickupTransports().add(transport2);
+ node2.setTransientField("node 2");
+
+ node3.setRoute(route);
+ node3.setTour(tour);
+ node3.getDeliveryTransports().add(transport2);
+ node3.setTransientField("node 3");
+
+ tour.getNodes().add(node1);
+ tour.getNodes().add(node2);
+ tour.getNodes().add(node3);
+
+ route.getNodes().add(node1);
+ route.getNodes().add(node2);
+ route.getNodes().add(node3);
+
+ transport1.setPickupNode(node1);
+ transport1.setDeliveryNode(node2);
+ transport1.setTransientField("aaaaaaaaaaaaaa");
+
+ transport2.setPickupNode(node2);
+ transport2.setDeliveryNode(node3);
+ transport2.setTransientField("bbbbbbbbbbbbb");
+
+ Route mergedRoute = (Route) s.merge(route);
+
+ s.getTransaction().commit();
+ s.close();
+
+ assertInsertCount( 6 );
+ assertUpdateCount( 1 );
+ }
+
+ protected void clearCounts() {
+ getSessions().getStatistics().clear();
+ }
+
+ protected void assertInsertCount(int expected) {
+ int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
+ assertEquals( "unexpected insert count", expected, inserts );
+ }
+
+ protected void assertUpdateCount(int expected) {
+ int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
+ assertEquals( "unexpected update counts", expected, updates );
+ }
+
+ protected void assertDeleteCount(int expected) {
+ int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
+ assertEquals( "unexpected delete counts", expected, deletes );
+ }
+}
Added: core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Node.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Node.java
(rev 0)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Node.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,154 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+public class Node {
+
+// @Id
+// @SequenceGenerator(name="NODE_SEQ", sequenceName="NODE_SEQ",
initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="NODE_SEQ")
+ private Long nodeID;
+
+ private long version;
+
+ private String name;
+
+ /** the list of orders that are delivered at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.REFRESH},
mappedBy="deliveryNode")
+ private Set deliveryTransports = new HashSet();
+
+ /** the list of orders that are picked up at this node */
+// @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL,
mappedBy="pickupNode")
+ private Set pickupTransports = new HashSet();
+
+ /** the route to which this node belongs */
+// @ManyToOne(targetEntity=Route.class, optional=false, fetch=FetchType.EAGER)
+// @JoinColumn(name="ROUTEID", nullable=false, insertable=true,
updatable=true)
+ private Route route = null;
+
+ /** the tour this node belongs to, null if this node does not belong to a tour (e.g
first node of a route) */
+// @ManyToOne(targetEntity=Tour.class, cascade={CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH}, optional=true, fetch=FetchType.LAZY)
+// @JoinColumn(name="TOURID", nullable=true, insertable=true, updatable=true)
+ private Tour tour;
+
+// @Transient
+ private String transientField = "node original value";
+
+ public Set getDeliveryTransports() {
+ return deliveryTransports;
+ }
+
+ public void setDeliveryTransports(Set deliveryTransports) {
+ this.deliveryTransports = deliveryTransports;
+ }
+
+ public Set getPickupTransports() {
+ return pickupTransports;
+ }
+
+ public void setPickupTransports(Set pickupTransports) {
+ this.pickupTransports = pickupTransports;
+ }
+
+ public Long getNodeID() {
+ return nodeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public Tour getTour() {
+ return tour;
+ }
+
+ public void setTour(Tour tour) {
+ this.tour = tour;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append( name + " id: " + nodeID );
+ if ( route != null ) {
+ buffer.append( " route name: " ).append( route.getName() ).append( "
tour name: " ).append( tour.getName() );
+ }
+ if ( pickupTransports != null ) {
+ for (Iterator it = pickupTransports.iterator(); it.hasNext();) {
+ buffer.append("Pickup transports: " + it.next());
+ }
+ }
+
+ if ( deliveryTransports != null ) {
+ for (Iterator it = deliveryTransports.iterator(); it.hasNext();) {
+ buffer.append("Delviery transports: " + it.next());
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+
+ protected void setNodeID(Long nodeID) {
+ this.nodeID = nodeID;
+ }
+
+}
Added: core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Route.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Route.java
(rev 0)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Route.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,119 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Route {
+
+// @Id
+// @SequenceGenerator(name="ROUTE_SEQ", sequenceName="ROUTE_SEQ",
initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ROUTE_SEQ")
+ private Long routeID;
+
+ private long version;
+
+ /** A List of nodes contained in this route. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL,
mappedBy="route")
+ private Set nodes = new HashSet();
+
+ private Set vehicles = new HashSet();
+
+ private String name;
+
+// @Transient
+ private String transientField = null;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ protected Set getNodes() {
+ return nodes;
+ }
+
+ protected void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ protected Set getVehicles() {
+ return vehicles;
+ }
+
+ protected void setVehicles(Set vehicles) {
+ this.vehicles = vehicles;
+ }
+
+ protected void setRouteID(Long routeID) {
+ this.routeID = routeID;
+ }
+
+ public Long getRouteID() {
+ return routeID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("Route name: " + name + " id: " + routeID + "
transientField: " + transientField + "\n");
+ for (Iterator it = nodes.iterator(); it.hasNext();) {
+ buffer.append("Node: " + (Node)it.next());
+ }
+
+ for (Iterator it = vehicles.iterator(); it.hasNext();) {
+ buffer.append("Vehicle: " + (Vehicle)it.next());
+ }
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Tour.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Tour.java
(rev 0)
+++ core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Tour.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,80 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Tour {
+
+// @Id
+// @SequenceGenerator(name="TOUR_SEQ", sequenceName="TOUR_SEQ",
initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TOUR_SEQ")
+ private Long tourID;
+
+ private long version;
+
+ private String name;
+
+ /** A List of nodes contained in this tour. */
+// @OneToMany(targetEntity=Node.class, fetch=FetchType.LAZY, cascade={CascadeType.MERGE,
CascadeType.REFRESH}, mappedBy="tour")
+ private Set nodes = new HashSet(0);
+
+ public String getName() {
+ return name;
+ }
+
+ protected void setTourID(Long tourID) {
+ this.tourID = tourID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Set nodes) {
+ this.nodes = nodes;
+ }
+
+ public Long getTourID() {
+ return tourID;
+ }
+}
Added: core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Transport.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Transport.java
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Transport.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,120 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+
+public class Transport {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ",
sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="TRANSPORT_SEQ")
+ private Long transportID;
+
+ private long version;
+
+ private String name;
+
+ /** node value object at which the order is picked up */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="PICKUPNODEID", /*nullable=false,*/insertable=true,
updatable=true)
+ private Node pickupNode = null;
+
+ /** node value object at which the order is delivered */
+// @ManyToOne(optional=false, cascade={CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH}, fetch=FetchType.EAGER)
+// @JoinColumn(name="DELIVERYNODEID", /*nullable=false,*/ insertable=true,
updatable=true)
+ private Node deliveryNode = null;
+
+ private Vehicle vehicle;
+
+// @Transient
+ private String transientField = "transport original value";
+
+ public Node getDeliveryNode() {
+ return deliveryNode;
+ }
+
+ public void setDeliveryNode(Node deliveryNode) {
+ this.deliveryNode = deliveryNode;
+ }
+
+ public Node getPickupNode() {
+ return pickupNode;
+ }
+
+ protected void setTransportID(Long transportID) {
+ this.transportID = transportID;
+ }
+
+ public void setPickupNode(Node pickupNode) {
+ this.pickupNode = pickupNode;
+ }
+
+ public Vehicle getVehicle() {
+ return vehicle;
+ }
+
+ public void setVehicle(Vehicle vehicle) {
+ this.vehicle = vehicle;
+ }
+
+ public Long getTransportID() {
+ return transportID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + transportID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
Added: core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Vehicle.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Vehicle.java
(rev 0)
+++
core/branches/Branch_3_2/test/org/hibernate/test/cascade/circle/Vehicle.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -0,0 +1,106 @@
+//$Id: $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2008, Red Hat Middleware LLC 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 Middleware LLC.
+ *
+ * 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.cascade.circle;
+
+import java.util.Set;
+import java.util.HashSet;
+
+
+public class Vehicle {
+
+// @Id
+// @SequenceGenerator(name="TRANSPORT_SEQ",
sequenceName="TRANSPORT_SEQ", initialValue=1, allocationSize=1)
+// @GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="TRANSPORT_SEQ")
+ private Long vehicleID;
+
+ private long version;
+
+ private String name;
+
+ private Set transports = new HashSet();
+
+ private Route route;
+
+ private String transientField = "vehicle original value";
+
+ protected void setVehicleID(Long vehicleID) {
+ this.vehicleID = vehicleID;
+ }
+
+ public Long getVehicleID() {
+ return vehicleID;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ protected void setVersion(long version) {
+ this.version = version;
+ }
+
+ public Set getTransports() {
+ return transports;
+ }
+
+ public void setTransports(Set transports) {
+ this.transports = transports;
+ }
+
+ public Route getRoute() {
+ return route;
+ }
+
+ public void setRoute(Route route) {
+ this.route = route;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(name + " id: " + vehicleID + "\n");
+
+ return buffer.toString();
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+}
\ No newline at end of file
Modified: core/branches/Branch_3_2/test/org/hibernate/test/ops/MergeTest.java
===================================================================
--- core/branches/Branch_3_2/test/org/hibernate/test/ops/MergeTest.java 2009-05-13
13:39:03 UTC (rev 16560)
+++ core/branches/Branch_3_2/test/org/hibernate/test/ops/MergeTest.java 2009-05-13
22:17:08 UTC (rev 16561)
@@ -187,7 +187,7 @@
// as a control measure, now update the node while it is detached and
// make sure we get an update as a result...
( ( Node ) parent.getChildren().iterator().next() ).setDescription( "child's
new description" );
- parent.getChildren().add( new Node( "second child" ) );
+ parent.addChild( new Node( "second child" ) );
s = openSession();
s.beginTransaction();
parent = ( Node ) s.merge( parent );