HHH-3810 : Transient entities can be inserted twice on merge
git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_2@16561 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
956e4d1e2a
commit
4d57eb957d
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -28,6 +28,8 @@ package org.hibernate.event.def;
|
|||
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.ObjectDeletedException;
|
|||
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.HibernateProxy;
|
|||
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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
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,24 +81,83 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
* @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();
|
||||
}
|
||||
EntityEntry entry = event.getSession().getPersistenceContext().getEntry( entity );
|
||||
if ( entry == null ) {
|
||||
// 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(
|
||||
"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
|
||||
"one or more objects is an unsaved transient instance - save transient instance(s) before merging: " +
|
||||
transientEntityNames );
|
||||
}
|
||||
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() );
|
||||
}
|
||||
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 is an unsaved transient instance - save the transient instance before merging: " +
|
||||
event.getSession().guessEntityName( copy )
|
||||
);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,8 +168,9 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
* @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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
default: //DELETED
|
||||
throw new ObjectDeletedException(
|
||||
"deleted instance passed to merge",
|
||||
null,
|
||||
null,
|
||||
getLoggableName( event.getEntityName(), entity )
|
||||
);
|
||||
}
|
||||
|
@ -194,15 +261,15 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
final Object entity = event.getEntity();
|
||||
final EventSource source = event.getSession();
|
||||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||
|
||||
copyCache.put(entity, entity); //before cascade!
|
||||
|
||||
( ( CopyCache ) copyCache ).put( entity, entity, true ); //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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
|
||||
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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
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,24 +306,53 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
//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;
|
||||
|
||||
}
|
||||
|
||||
|
@ -260,7 +365,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
|
||||
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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
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 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
return entry.isExistsInDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void copyValues(
|
||||
final EntityPersister persister,
|
||||
final Object entity,
|
||||
|
@ -389,7 +494,6 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener
|
|||
final SessionImplementor source,
|
||||
final Map copyCache
|
||||
) {
|
||||
|
||||
final Object[] copiedValues = TypeFactory.replace(
|
||||
persister.getPropertyValues( entity, source.getEntityMode() ),
|
||||
persister.getPropertyValues( target, source.getEntityMode() ),
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.hibernate.test.cache.CacheSuite;
|
|||
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 @@ public class AllTests {
|
|||
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() );
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -187,7 +187,7 @@ public class MergeTest extends AbstractOperationTestCase {
|
|||
// 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 );
|
||||
|
|
Loading…
Reference in New Issue