HHH-9106 : Merging multiple representations of the same entity merged
(cherry picked from commit 70532259fa
)
Conflicts:
hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java
hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java
hibernate-core/src/main/java/org/hibernate/event/internal/EventCache.java
hibernate-core/src/test/java/org/hibernate/event/internal/EventCacheTest.java
hibernate-core/src/test/java/org/hibernate/test/ops/AbstractOperationTestCase.java
hibernate-core/src/test/java/org/hibernate/test/ops/Category.java
hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.hbm.xml
hibernate-core/src/test/java/org/hibernate/test/ops/Hoarder.java
hibernate-core/src/test/java/org/hibernate/test/ops/Item.java
hibernate-core/src/test/resources/log4j.properties
This commit is contained in:
parent
5da9da4585
commit
6fdc9bac13
|
@ -1322,6 +1322,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
collectionPersister,
|
||||
unmergedInstance
|
||||
);
|
||||
LOG.debugf(
|
||||
"Detached object being merged (corresponding with a managed entity) has a collection that [%s] the detached child.",
|
||||
( found ? "contains" : "does not contain" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1348,6 +1352,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
collectionPersister,
|
||||
mergeMap.get( proxy )
|
||||
);
|
||||
LOG.debugf(
|
||||
"Detached proxy being merged has a collection that [%s] the managed child.",
|
||||
(found ? "contains" : "does not contain")
|
||||
);
|
||||
if ( !found ) {
|
||||
found = isFoundInParent(
|
||||
propertyName,
|
||||
|
@ -1356,6 +1364,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
collectionPersister,
|
||||
mergeMap.get( proxy )
|
||||
);
|
||||
LOG.debugf(
|
||||
"Detached proxy being merged has a collection that [%s] the detached child being merged..",
|
||||
(found ? "contains" : "does not contain")
|
||||
);
|
||||
}
|
||||
if ( found ) {
|
||||
return proxy.getHibernateLazyInitializer().getIdentifier();
|
||||
|
@ -1402,10 +1414,14 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
Object index = getIndexInParent(property, childEntity, persister, cp, parent);
|
||||
|
||||
if (index==null && mergeMap!=null) {
|
||||
Object unmergedInstance = mergeMap.get(parent);
|
||||
Object unmergedChild = mergeMap.get(childEntity);
|
||||
if ( unmergedInstance!=null && unmergedChild!=null ) {
|
||||
index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
|
||||
final Object unMergedInstance = mergeMap.get( parent );
|
||||
final Object unMergedChild = mergeMap.get( childEntity );
|
||||
if ( unMergedInstance != null && unMergedChild != null ) {
|
||||
index = getIndexInParent( property, unMergedChild, persister, cp, unMergedInstance );
|
||||
LOG.debugf(
|
||||
"A detached object being merged (corresponding to a parent in parentsByChild) has an indexed collection that [%s] the detached child being merged. ",
|
||||
( index != null ? "contains" : "does not contain" )
|
||||
);
|
||||
}
|
||||
}
|
||||
if (index!=null) {
|
||||
|
@ -1421,15 +1437,18 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
for ( Entry<Object, EntityEntry> me : reentrantSafeEntityEntries() ) {
|
||||
EntityEntry ee = me.getValue();
|
||||
if ( persister.isSubclassEntityName( ee.getEntityName() ) ) {
|
||||
Object instance = me.getKey();
|
||||
final Object instance = me.getKey();
|
||||
|
||||
Object index = getIndexInParent(property, childEntity, persister, cp, instance);
|
||||
|
||||
if (index==null && mergeMap!=null) {
|
||||
Object unmergedInstance = mergeMap.get(instance);
|
||||
Object unmergedChild = mergeMap.get(childEntity);
|
||||
if ( unmergedInstance!=null && unmergedChild!=null ) {
|
||||
index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
|
||||
Object index = getIndexInParent( property, childEntity, persister, cp, instance );
|
||||
if ( index==null && mergeMap!=null ) {
|
||||
final Object unMergedInstance = mergeMap.get( instance );
|
||||
final Object unMergedChild = mergeMap.get( childEntity );
|
||||
if ( unMergedInstance != null && unMergedChild!=null ) {
|
||||
index = getIndexInParent( property, unMergedChild, persister, cp, unMergedInstance );
|
||||
LOG.debugf(
|
||||
"A detached object being merged (corresponding to a managed entity) has an indexed collection that [%s] the detached child being merged. ",
|
||||
(index != null ? "contains" : "does not contain" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.collections.IdentitySet;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.type.AssociationType;
|
||||
import org.hibernate.type.CollectionType;
|
||||
import org.hibernate.type.EntityType;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class DefaultEntityCopyObserver implements EntityCopyObserver {
|
||||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultEntityCopyObserver.class );
|
||||
|
||||
// managedToMergeEntitiesXref is only maintained for DEBUG logging so that a "nice" message
|
||||
// about multiple representations can be logged at the completion of the top-level merge.
|
||||
// if DEBUG logging is not enabled or no entity copies have been detected, managedToMergeEntitiesXref
|
||||
// will remain null;
|
||||
private Map<Object, Set<Object>> managedToMergeEntitiesXref = null;
|
||||
// key is the managed entity;
|
||||
// value is the set of representations being merged corresponding to the same managed result.
|
||||
|
||||
@Override
|
||||
public void entityCopyDetected(
|
||||
Object managedEntity,
|
||||
Object mergeEntity1,
|
||||
Object mergeEntity2,
|
||||
EventSource session) {
|
||||
LOG.trace(
|
||||
String.format(
|
||||
"More than one representation of the same persistent entity being merged for: %s",
|
||||
MessageHelper.infoString(
|
||||
session.getEntityName( managedEntity ),
|
||||
session.getIdentifier( managedEntity )
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if ( LOG.isDebugEnabled() ) {
|
||||
// managedToMergeEntitiesXref is only maintained for DEBUG logging
|
||||
Set<Object> detachedEntitiesForManaged = null;
|
||||
if ( managedToMergeEntitiesXref == null ) {
|
||||
// This is the first time multiple representations have been found;
|
||||
// instantiate managedToMergeEntitiesXref.
|
||||
managedToMergeEntitiesXref = new IdentityHashMap<Object, Set<Object>>();
|
||||
}
|
||||
else {
|
||||
// Get any existing representations that have already been found.
|
||||
detachedEntitiesForManaged = managedToMergeEntitiesXref.get( managedEntity );
|
||||
}
|
||||
if ( detachedEntitiesForManaged == null ) {
|
||||
// There were no existing representations; instantiate detachedEntitiesForManaged
|
||||
detachedEntitiesForManaged = new IdentitySet();
|
||||
managedToMergeEntitiesXref.put( managedEntity, detachedEntitiesForManaged );
|
||||
}
|
||||
// Now add the detached representation for the managed entity.
|
||||
detachedEntitiesForManaged.add( mergeEntity1 );
|
||||
detachedEntitiesForManaged.add( mergeEntity2 );
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if ( managedToMergeEntitiesXref != null ) {
|
||||
managedToMergeEntitiesXref.clear();
|
||||
managedToMergeEntitiesXref = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void topLevelMergeComplete(EventSource session) {
|
||||
if ( !LOG.isDebugEnabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( managedToMergeEntitiesXref != null && !managedToMergeEntitiesXref.isEmpty() ) {
|
||||
for ( Map.Entry<Object,Set<Object>> entry : managedToMergeEntitiesXref.entrySet() ) {
|
||||
Object managedEntity = entry.getKey();
|
||||
Set mergeEntities = entry.getValue();
|
||||
StringBuilder sb = new StringBuilder( "Found ")
|
||||
.append( mergeEntities.size() )
|
||||
.append( " entity representations of the same entity " )
|
||||
.append(
|
||||
MessageHelper.infoString(
|
||||
session.getEntityName( managedEntity ),
|
||||
session.getIdentifier( managedEntity )
|
||||
)
|
||||
)
|
||||
.append( " being merged: " );
|
||||
boolean first = true;
|
||||
for ( Object mergeEntity : mergeEntities ) {
|
||||
if ( first ) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
sb.append( ", " );
|
||||
}
|
||||
sb.append( getManagedOrDetachedEntityString( managedEntity, mergeEntity ) );
|
||||
}
|
||||
sb.append( "; resulting managed entity: [" ).append( managedEntity ).append( ']' );
|
||||
LOG.debug( sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getManagedOrDetachedEntityString(Object managedEntity, Object mergeEntity ) {
|
||||
if ( mergeEntity == managedEntity) {
|
||||
return "Managed: [" + mergeEntity + "]";
|
||||
}
|
||||
else {
|
||||
return "Detached: [" + mergeEntity + "]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
|
||||
* Copyright (c) 2008-2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
|
@ -61,8 +61,12 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
DefaultMergeEventListener.class.getName());
|
||||
|
||||
@Override
|
||||
protected Map getMergeMap(Object anything) {
|
||||
return ( ( EventCache ) anything ).invertMap();
|
||||
protected Map getMergeMap(Object anything) {
|
||||
return ( (MergeContext) anything ).invertMap();
|
||||
}
|
||||
|
||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
||||
return new DefaultEntityCopyObserver();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,10 +76,16 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
* @throws HibernateException
|
||||
*/
|
||||
public void onMerge(MergeEvent event) throws HibernateException {
|
||||
EventCache copyCache = new EventCache( event.getSession() );
|
||||
onMerge( event, copyCache );
|
||||
copyCache.clear();
|
||||
copyCache = null;
|
||||
final EntityCopyObserver entityCopyObserver = createDetachedEntityCopyObserver();
|
||||
final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
|
||||
try {
|
||||
onMerge( event, mergeContext );
|
||||
entityCopyObserver.topLevelMergeComplete( event.getSession() );
|
||||
}
|
||||
finally {
|
||||
entityCopyObserver.clear();
|
||||
mergeContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,7 +96,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
*/
|
||||
public void onMerge(MergeEvent event, Map copiedAlready) throws HibernateException {
|
||||
|
||||
final EventCache copyCache = ( EventCache ) copiedAlready;
|
||||
final MergeContext copyCache = (MergeContext) copiedAlready;
|
||||
final EventSource source = event.getSession();
|
||||
final Object original = event.getOriginal();
|
||||
|
||||
|
@ -178,7 +188,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
final EventSource source = event.getSession();
|
||||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||
|
||||
( ( EventCache ) copyCache ).put( entity, entity, true ); //before cascade!
|
||||
( (MergeContext) copyCache ).put( entity, entity, true ); //before cascade!
|
||||
|
||||
cascadeOnMerge(source, persister, entity, copyCache);
|
||||
copyValues(persister, entity, entity, source, copyCache);
|
||||
|
@ -203,7 +213,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
persister.setIdentifier( copyCache.get( entity ), id, source );
|
||||
}
|
||||
else {
|
||||
( ( EventCache ) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade!
|
||||
( (MergeContext) copyCache ).put( entity, source.instantiate( persister, id ), true ); //before cascade!
|
||||
}
|
||||
final Object copy = copyCache.get( entity );
|
||||
|
||||
|
@ -282,7 +292,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
|||
entityIsTransient(event, copyCache);
|
||||
}
|
||||
else {
|
||||
( ( EventCache ) copyCache ).put( entity, result, true ); //before cascade!
|
||||
( (MergeContext) copyCache ).put( entity, result, true ); //before cascade!
|
||||
|
||||
final Object target = source.getPersistenceContext().unproxy(result);
|
||||
if ( target == entity ) {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class EntityCopyNotAllowedObserver implements EntityCopyObserver {
|
||||
|
||||
@Override
|
||||
public void entityCopyDetected(
|
||||
Object managedEntity,
|
||||
Object mergeEntity1,
|
||||
Object mergeEntity2,
|
||||
EventSource session) {
|
||||
if ( mergeEntity1 == managedEntity && mergeEntity2 == managedEntity) {
|
||||
throw new AssertionFailure( "entity1 and entity2 are the same as managedEntity; must be different." );
|
||||
}
|
||||
final String managedEntityString = MessageHelper.infoString(
|
||||
session.getEntityName( managedEntity ),
|
||||
session.getIdentifier( managedEntity )
|
||||
);
|
||||
throw new IllegalStateException(
|
||||
"Multiple representations of the same entity " + managedEntityString + " are being merged. " +
|
||||
getManagedOrDetachedEntityString( managedEntity, mergeEntity1 ) + "; " +
|
||||
getManagedOrDetachedEntityString( managedEntity, mergeEntity2 )
|
||||
);
|
||||
}
|
||||
|
||||
private String getManagedOrDetachedEntityString(Object managedEntity, Object entity ) {
|
||||
if ( entity == managedEntity) {
|
||||
return "Managed: [" + entity + "]";
|
||||
}
|
||||
else {
|
||||
return "Detached: [" + entity + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void topLevelMergeComplete(EventSource session) {
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
|
||||
/**
|
||||
* An observer for detection of multiple entity representations for a persistent entity being merged.
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public interface EntityCopyObserver {
|
||||
|
||||
/**
|
||||
* Called when more than one representation of the same persistent entity is being merged.
|
||||
*
|
||||
* @param managedEntity The managed entity in the persistence context (the merge result).
|
||||
* @param mergeEntity1 A managed or detached entity being merged; must be non-null.
|
||||
* @param mergeEntity2 A different managed or detached entity being merged; must be non-null.
|
||||
* @param session The session.
|
||||
*/
|
||||
void entityCopyDetected(Object managedEntity, Object mergeEntity1, Object mergeEntity2, EventSource session);
|
||||
|
||||
/**
|
||||
* Called when the top-level merge operation is complete.
|
||||
*
|
||||
* @param session The session
|
||||
*/
|
||||
void topLevelMergeComplete(EventSource session);
|
||||
|
||||
/**
|
||||
* Called to clear any data stored in this EntityCopyObserver.
|
||||
*/
|
||||
void clear();
|
||||
}
|
|
@ -1,366 +0,0 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
|
||||
/**
|
||||
* EventCache is a Map implementation that can be used by an event
|
||||
* listener to keep track of entities involved in the operation
|
||||
* being performed. This implementation allows entities to be added
|
||||
* to the EventCache before the operation has cascaded to that
|
||||
* entity.
|
||||
* <p/>
|
||||
* There are some restriction;
|
||||
* <ul>
|
||||
* <li>the same value cannot be associated with more than one key</li>
|
||||
* <li>Methods that return collections (e.g., {@link #keySet()},
|
||||
* {@link #values()}, {@link #entrySet()}) return an
|
||||
* unnmodifiable view of the collection.</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* The following methods can be used by event listeners (and other
|
||||
* classes) in the same package to add entities to an EventCache
|
||||
* and indicate if the operation is being performed on the entity:<p/>
|
||||
* {@link EventCache#put(Object entity, Object copy, boolean isOperatedOn)}
|
||||
* <p/>
|
||||
* The following method can be used by event listeners (and other
|
||||
* classes) in the same package to indicate that the operation is being
|
||||
* performed on an entity already in the EventCache:
|
||||
* {@link EventCache#setOperatedOn(Object entity, boolean isOperatedOn)
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
class EventCache implements Map {
|
||||
private final EventSource session;
|
||||
|
||||
private Map<Object,Object> entityToCopyMap = new IdentityHashMap<Object,Object>(10);
|
||||
// key is an entity involved with the operation performed by the listener;
|
||||
// value can be either a copy of the entity or the entity itself
|
||||
|
||||
private Map<Object,Object> copyToEntityMap = new IdentityHashMap<Object,Object>( 10 );
|
||||
// maintains the inverse of the entityToCopyMap for performance reasons.
|
||||
|
||||
private Map<Object,Boolean> entityToOperatedOnFlagMap = new IdentityHashMap<Object,Boolean>( 10 );
|
||||
// key is an entity involved with the operation performed by the listener;
|
||||
// value is a flag indicating if the listener explicitly operates on the entity
|
||||
|
||||
EventCache(EventSource session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the EventCache.
|
||||
*/
|
||||
public void clear() {
|
||||
entityToCopyMap.clear();
|
||||
copyToEntityMap.clear();
|
||||
entityToOperatedOnFlagMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this EventCache contains a mapping for the specified entity.
|
||||
* @param entity must be non-null
|
||||
* @return true if this EventCache 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 EventCache maps an entity to the specified copy.
|
||||
* @param copy must be non-null
|
||||
* @return true if this EventCache maps an entity 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 copyToEntityMap.containsKey( copy );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set view of the entity-to-copy mappings contained in this EventCache.
|
||||
* @return an unmodifiable set view of the entity-to-copy mappings contained in this EventCache
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Set entrySet() {
|
||||
return Collections.unmodifiableSet( entityToCopyMap.entrySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the copy to which this EventCache maps the specified entity.
|
||||
* @param entity must be non-null
|
||||
* @return the copy to which this EventCache 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 EventCache contains no entity-copy mappings.
|
||||
* @return true if this EventCache contains no entity-copy mappings
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return entityToCopyMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set view of the entities contained in this EventCache
|
||||
* @return an unmodifiable set view of the entities contained in this EventCache
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Set keySet() {
|
||||
return Collections.unmodifiableSet( entityToCopyMap.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified entity with the specified copy in this EventCache;
|
||||
* @param entity must be non-null
|
||||
* @param copy must be non- null and must not be associated with any other entity in this EntityCache.
|
||||
* @return previous copy associated with specified entity, or null if
|
||||
* there was no mapping for entity.
|
||||
* @throws NullPointerException if entity or copy is null
|
||||
* @throws IllegalStateException if the specified copy is already associated with a different entity.
|
||||
*/
|
||||
public Object put(Object entity, Object copy) {
|
||||
return put( entity, copy, Boolean.FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified entity with the specified copy in this EventCache;
|
||||
* @param entity must be non-null
|
||||
* @param copy must be non- null and must not be associated with any other entity in this EntityCache.
|
||||
* @param isOperatedOn indicates if the operation is performed on the entity.
|
||||
*
|
||||
* @return previous copy associated with specified entity, or null if
|
||||
* there was no mapping for entity.
|
||||
* @throws NullPointerException if entity or copy is null
|
||||
* @throws IllegalStateException if the specified copy is already associated with a different entity.
|
||||
*/
|
||||
/* package-private */ Object put(Object entity, Object copy, boolean isOperatedOn) {
|
||||
if ( entity == null || copy == null ) {
|
||||
throw new NullPointerException( "null entities and copies are not supported by " + getClass().getName() );
|
||||
}
|
||||
|
||||
Object oldCopy = entityToCopyMap.put( entity, copy );
|
||||
Boolean oldOperatedOn = entityToOperatedOnFlagMap.put( entity, isOperatedOn );
|
||||
Object oldEntity = copyToEntityMap.put( copy, entity );
|
||||
|
||||
if ( oldCopy == null ) {
|
||||
if ( oldEntity != null ) {
|
||||
throw new IllegalStateException(
|
||||
"Error occurred while storing entity " + printEntity( entity ) + ". An entity copy " + printEntity( copy )
|
||||
+ " was already assigned to a different entity " + printEntity( oldEntity ) + "."
|
||||
);
|
||||
}
|
||||
if ( oldOperatedOn != null ) {
|
||||
throw new IllegalStateException(
|
||||
"EventCache#entityToOperatedOnFlagMap contains an entity " + printEntity( entity )
|
||||
+ ", but EventCache#entityToCopyMap does not."
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( oldCopy != copy ) {
|
||||
// Replaced an entity copy with a new copy; need to remove the oldCopy from copyToEntityMap
|
||||
// to synch things up.
|
||||
Object removedEntity = copyToEntityMap.remove( oldCopy );
|
||||
if ( removedEntity != entity ) {
|
||||
throw new IllegalStateException(
|
||||
"Error occurred while storing entity " + printEntity( entity ) + ". An unexpected entity " + printEntity( removedEntity )
|
||||
+ " was associated with the old entity copy " + printEntity( oldCopy ) + "."
|
||||
);
|
||||
}
|
||||
if ( oldEntity != null ) {
|
||||
throw new IllegalStateException(
|
||||
"Error occurred while storing entity " + printEntity( entity ) + ". A new entity copy " + printEntity( copy )
|
||||
+ " is already associated with a different entity " + printEntity( oldEntity ) + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Replaced an entity copy with the same copy in entityToCopyMap.
|
||||
// Make sure that copy is associated with the same entity in copyToEntityMap.
|
||||
if ( oldEntity != entity ) {
|
||||
throw new IllegalStateException(
|
||||
"An entity copy " + printEntity( copy ) + " was associated with a different entity "
|
||||
+ printEntity( oldEntity ) + " than provided " + printEntity( entity ) + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( oldOperatedOn == null ) {
|
||||
throw new IllegalStateException(
|
||||
"EventCache#entityToCopyMap contained an entity " + printEntity( entity )
|
||||
+ ", but EventCache#entityToOperatedOnFlagMap did not."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return oldCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified map to this EventCache
|
||||
* @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 ( Object o : map.entrySet() ) {
|
||||
Entry entry = (Entry) o;
|
||||
put( entry.getKey(), entry.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping for this entity from this EventCache 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() );
|
||||
}
|
||||
Boolean oldOperatedOn = entityToOperatedOnFlagMap.remove( entity );
|
||||
Object oldCopy = entityToCopyMap.remove( entity );
|
||||
Object oldEntity = oldCopy != null ? copyToEntityMap.remove( oldCopy ) : null;
|
||||
|
||||
if ( oldCopy == null ) {
|
||||
if ( oldOperatedOn != null ) {
|
||||
throw new IllegalStateException(
|
||||
"Removed entity " + printEntity( entity )
|
||||
+ " from EventCache#entityToOperatedOnFlagMap, but EventCache#entityToCopyMap did not contain the entity."
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ( oldEntity == null ) {
|
||||
throw new IllegalStateException(
|
||||
"Removed entity " + printEntity( entity )
|
||||
+ " from EventCache#entityToCopyMap, but EventCache#copyToEntityMap did not contain the entity."
|
||||
);
|
||||
}
|
||||
if ( oldOperatedOn == null ) {
|
||||
throw new IllegalStateException(
|
||||
"EventCache#entityToCopyMap contained an entity " + printEntity( entity )
|
||||
+ ", but EventCache#entityToOperatedOnFlagMap did not."
|
||||
);
|
||||
}
|
||||
if ( oldEntity != entity ) {
|
||||
throw new IllegalStateException(
|
||||
"An entity copy " + printEntity( oldCopy ) + " was associated with a different entity "
|
||||
+ printEntity( oldEntity ) + " than provided " + printEntity( entity ) + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return oldCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entity-copy mappings in this EventCache
|
||||
* @return the number of entity-copy mappings in this EventCache
|
||||
*/
|
||||
public int size() {
|
||||
return entityToCopyMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set view of the entity copies contained in this EventCache.
|
||||
* @return an unmodifiable set view of the entity copies contained in this EventCache
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Collection values() {
|
||||
return Collections.unmodifiableCollection( entityToCopyMap.values() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the listener is performing the operation on the specified entity.
|
||||
* @param entity must be non-null
|
||||
* @return true if the listener is performing the operation on the specified entity.
|
||||
* @throws NullPointerException if entity is null
|
||||
*/
|
||||
public boolean isOperatedOn(Object entity) {
|
||||
if ( entity == null ) {
|
||||
throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
return entityToOperatedOnFlagMap.get( entity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flag to indicate if the listener is performing the operation on the specified entity.
|
||||
* @param entity must be non-null and this EventCache must contain a mapping for this entity
|
||||
* @throws NullPointerException if entity is null
|
||||
* @throws AssertionFailure if this EventCache does not contain a mapping for the specified entity
|
||||
*/
|
||||
/* package-private */ void setOperatedOn(Object entity, boolean isOperatedOn) {
|
||||
if ( entity == null ) {
|
||||
throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
if ( ! entityToOperatedOnFlagMap.containsKey( entity ) ||
|
||||
! entityToCopyMap.containsKey( entity ) ) {
|
||||
throw new AssertionFailure( "called EventCache#setOperatedOn() for entity not found in EventCache" );
|
||||
}
|
||||
entityToOperatedOnFlagMap.put( entity, isOperatedOn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable map view of the copy-entity mappings
|
||||
* @return an unmodifiable map view of the copy-entity mappings
|
||||
*
|
||||
* @see {@link Collections#unmodifiableMap(java.util.Map)}
|
||||
*/
|
||||
public Map invertMap() {
|
||||
return Collections.unmodifiableMap( copyToEntityMap );
|
||||
}
|
||||
|
||||
private String printEntity(Object entity) {
|
||||
if ( session.getPersistenceContext().getEntry( entity ) != null ) {
|
||||
return MessageHelper.infoString( session.getEntityName( entity ), session.getIdentifier( entity ) );
|
||||
}
|
||||
// Entity was not found in current persistence context. Use Object#toString() method.
|
||||
return "[" + entity + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2008-2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
|
||||
/**
|
||||
* MergeContext is a Map implementation that is intended to be used by a merge
|
||||
* event listener to keep track of each entity being merged and their corresponding
|
||||
* managed result. Entities to be merged may to be added to the MergeContext before
|
||||
* the merge operation has cascaded to that entity.
|
||||
*
|
||||
* "Merge entity" and "mergeEntity" method parameter refer to an entity that is (or will be)
|
||||
* merged via {@link org.hibernate.event.spi.EventSource#merge(Object mergeEntity)}.
|
||||
*
|
||||
* "Managed entity" and "managedEntity" method parameter refer to the managed entity that is
|
||||
* the result of merging an entity.
|
||||
*
|
||||
* A merge entity can be transient, detached, or managed. If it is managed, then it must be
|
||||
* the same as its associated entity result.
|
||||
*
|
||||
* If {@link #put(Object mergeEntity, Object managedEntity)} is called, and this
|
||||
* MergeContext already contains an entry with a different entity as the key, but
|
||||
* with the same (managedEntity) value, this means that multiple entity representations
|
||||
* for the same persistent entity are being merged. If this happens,
|
||||
* {@link org.hibernate.event.internal.EntityCopyObserver#entityCopyDetected(
|
||||
* Object managedEntity, Object mergeEntity1, Object mergeEntity2, org.hibernate.event.spi.EventSource)}
|
||||
* will be called. It is up to that method to determine the property course of
|
||||
* action for this situation.
|
||||
*
|
||||
* There are several restrictions.
|
||||
* <ul>
|
||||
* <li>Methods that return collections (e.g., {@link #keySet()},
|
||||
* {@link #values()}, {@link #entrySet()}) return an
|
||||
* unnmodifiable view of the collection;</li>
|
||||
* <li>If {@link #put(Object mergeEntity, Object) managedEntity} or
|
||||
* {@link #put(Object mergeEntity, Object managedEntity, boolean isOperatedOn)}
|
||||
* is executed and this MergeMap already contains a cross-reference for
|
||||
* <code>mergeEntity</code>, then <code>managedEntity</code> must be the
|
||||
* same as what is already associated with <code>mergeEntity</code> in this
|
||||
* MergeContext.
|
||||
* </li>
|
||||
* <li>If {@link #putAll(Map map)} is executed, the previous restriction
|
||||
* applies to each entry in the Map;</li>
|
||||
* <li>The {@link #remove(Object)} operation is not supported;
|
||||
* The only way to remove data from a MergeContext is by calling
|
||||
* {@link #clear()};</li>
|
||||
* <li>the Map returned by {@link #invertMap()} will only contain the
|
||||
* managed-to-merge entity cross-reference to its "newest"
|
||||
* (most recently added) merge entity.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The following method is intended to be used by a merge event listener (and other
|
||||
* classes) in the same package to add a merge entity and its corresponding
|
||||
* managed entity to a MergeContext and indicate if the merge operation is
|
||||
* being performed on the merge entity yet.<p/>
|
||||
* {@link MergeContext#put(Object mergeEntity, Object managedEntity, boolean isOperatedOn)}
|
||||
* <p/>
|
||||
* The following method is intended to be used by a merge event listener (and other
|
||||
* classes) in the same package to indicate whether the merge operation is being
|
||||
* performed on a merge entity already in the MergeContext:
|
||||
* {@link MergeContext#setOperatedOn(Object mergeEntity, boolean isOperatedOn)
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
class MergeContext implements Map {
|
||||
private static final Logger LOG = Logger.getLogger( MergeContext.class );
|
||||
|
||||
private final EventSource session;
|
||||
private final EntityCopyObserver entityCopyObserver;
|
||||
|
||||
private Map<Object,Object> mergeToManagedEntityXref = new IdentityHashMap<Object,Object>(10);
|
||||
// key is an entity to be merged;
|
||||
// value is the associated managed entity (result) in the persistence context.
|
||||
|
||||
private Map<Object,Object> managedToMergeEntityXref = new IdentityHashMap<Object,Object>( 10 );
|
||||
// maintains the inverse of the mergeToManagedEntityXref for performance reasons.
|
||||
// key is the managed entity result in the persistence context.
|
||||
// value is the associated entity to be merged; if multiple
|
||||
// representations of the same persistent entity are added to the MergeContext,
|
||||
// value will be the most recently added merge entity that is
|
||||
// associated with the managed entity.
|
||||
|
||||
// TODO: merge mergeEntityToOperatedOnFlagMap into mergeToManagedEntityXref, since they have the same key.
|
||||
// need to check if this would hurt performance.
|
||||
private Map<Object,Boolean> mergeEntityToOperatedOnFlagMap = new IdentityHashMap<Object,Boolean>( 10 );
|
||||
// key is a merge entity;
|
||||
// value is a flag indicating if the merge entity is currently in the merge process.
|
||||
|
||||
MergeContext(EventSource session, EntityCopyObserver entityCopyObserver){
|
||||
this.session = session;
|
||||
this.entityCopyObserver = entityCopyObserver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the MergeContext.
|
||||
*/
|
||||
public void clear() {
|
||||
mergeToManagedEntityXref.clear();
|
||||
managedToMergeEntityXref.clear();
|
||||
mergeEntityToOperatedOnFlagMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this MergeContext contains a cross-reference for the specified merge entity
|
||||
* to a managed entity result.
|
||||
*
|
||||
* @param mergeEntity must be non-null
|
||||
* @return true if this MergeContext contains a cross-reference for the specified merge entity
|
||||
* @throws NullPointerException if mergeEntity is null
|
||||
*/
|
||||
public boolean containsKey(Object mergeEntity) {
|
||||
if ( mergeEntity == null ) {
|
||||
throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
return mergeToManagedEntityXref.containsKey( mergeEntity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this MergeContext contains a cross-reference from the specified managed entity
|
||||
* to a merge entity.
|
||||
* @param managedEntity must be non-null
|
||||
* @return true if this MergeContext contains a cross-reference from the specified managed entity
|
||||
* to a merge entity
|
||||
* @throws NullPointerException if managedEntity is null
|
||||
*/
|
||||
public boolean containsValue(Object managedEntity) {
|
||||
if ( managedEntity == null ) {
|
||||
throw new NullPointerException( "null copies are not supported by " + getClass().getName() );
|
||||
}
|
||||
return managedToMergeEntityXref.containsKey( managedEntity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set view of the merge-to-managed entity cross-references contained in this MergeContext.
|
||||
* @return an unmodifiable set view of the merge-to-managed entity cross-references contained in this MergeContext
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Set entrySet() {
|
||||
return Collections.unmodifiableSet( mergeToManagedEntityXref.entrySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the managed entity associated with the specified merge Entity.
|
||||
* @param mergeEntity the merge entity; must be non-null
|
||||
* @return the managed entity associated with the specified merge Entity
|
||||
* @throws NullPointerException if mergeEntity is null
|
||||
*/
|
||||
public Object get(Object mergeEntity) {
|
||||
if ( mergeEntity == null ) {
|
||||
throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
return mergeToManagedEntityXref.get( mergeEntity );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this MergeContext contains no merge-to-managed entity cross-references.
|
||||
* @return true if this MergeContext contains no merge-to-managed entity cross-references.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return mergeToManagedEntityXref.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set view of the merge entities contained in this MergeContext
|
||||
* @return an unmodifiable set view of the merge entities contained in this MergeContext
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Set keySet() {
|
||||
return Collections.unmodifiableSet( mergeToManagedEntityXref.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified merge entity with the specified managed entity result in this MergeContext.
|
||||
* If this MergeContext already contains a cross-reference for <code>mergeEntity</code> when this
|
||||
* method is called, then <code>managedEntity</code> must be the same as what is already associated
|
||||
* with <code>mergeEntity</code>.
|
||||
* <p/>
|
||||
* This method assumes that the merge process is not yet operating on <code>mergeEntity</code>.
|
||||
* Later when <code>mergeEntity</code> enters the merge process, {@link #setOperatedOn(Object, boolean)}
|
||||
* should be called.
|
||||
* <p/>
|
||||
* @param mergeEntity the merge entity; must be non-null
|
||||
* @param managedEntity the managed entity result; must be non-null
|
||||
* @return previous managed entity associated with specified merge entity, or null if
|
||||
* there was no mapping for mergeEntity.
|
||||
* @throws NullPointerException if mergeEntity or managedEntity is null
|
||||
* @throws IllegalArgumentException if <code>managedEntity</code> is not the same as the previous
|
||||
* managed entity associated with <code>merge entity</code>
|
||||
* @throws IllegalStateException if internal cross-references are out of sync,
|
||||
*/
|
||||
public Object put(Object mergeEntity, Object managedEntity) {
|
||||
return put( mergeEntity, managedEntity, Boolean.FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified merge entity with the specified managed entity in this MergeContext.
|
||||
* If this MergeContext already contains a cross-reference for <code>mergeEntity</code> when this
|
||||
* method is called, then <code>managedEntity</code> must be the same as what is already associated
|
||||
* with <code>mergeEntity</code>.
|
||||
*
|
||||
* @param mergeEntity the mergge entity; must be non-null
|
||||
* @param managedEntity the managed entity; must be non-null
|
||||
* @param isOperatedOn indicates if the merge operation is performed on the mergeEntity.
|
||||
*
|
||||
* @return previous managed entity associated with specified merge entity, or null if
|
||||
* there was no mapping for mergeEntity.
|
||||
* @throws NullPointerException if mergeEntity or managedEntity is null
|
||||
* @throws IllegalArgumentException if <code>managedEntity</code> is not the same as the previous
|
||||
* managed entity associated with <code>mergeEntity</code>
|
||||
* @throws IllegalStateException if internal cross-references are out of sync,
|
||||
*/
|
||||
/* package-private */ Object put(Object mergeEntity, Object managedEntity, boolean isOperatedOn) {
|
||||
if ( mergeEntity == null || managedEntity == null ) {
|
||||
throw new NullPointerException( "null merge and managed entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
|
||||
Object oldManagedEntity = mergeToManagedEntityXref.put( mergeEntity, managedEntity );
|
||||
Boolean oldOperatedOn = mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn );
|
||||
// If managedEntity already corresponds with a different merge entity, that means
|
||||
// that there are multiple entities being merged that correspond with managedEntity.
|
||||
// In the following, oldMergeEntity will be replaced with mergeEntity in managedToMergeEntityXref.
|
||||
Object oldMergeEntity = managedToMergeEntityXref.put( managedEntity, mergeEntity );
|
||||
|
||||
if ( oldManagedEntity == null ) {
|
||||
// this is a new mapping for mergeEntity in mergeToManagedEntityXref
|
||||
if ( oldMergeEntity != null ) {
|
||||
// oldMergeEntity was a different merge entity with the same corresponding managed entity;
|
||||
entityCopyObserver.entityCopyDetected(
|
||||
managedEntity,
|
||||
mergeEntity,
|
||||
oldMergeEntity,
|
||||
session
|
||||
);
|
||||
}
|
||||
if ( oldOperatedOn != null ) {
|
||||
throw new IllegalStateException(
|
||||
"MergeContext#mergeEntityToOperatedOnFlagMap contains an merge entity " + printEntity( mergeEntity )
|
||||
+ ", but MergeContext#mergeToManagedEntityXref does not."
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// mergeEntity was already mapped in mergeToManagedEntityXref
|
||||
if ( oldManagedEntity != managedEntity ) {
|
||||
throw new IllegalArgumentException(
|
||||
"Error occurred while storing a merge Entity " + printEntity( mergeEntity )
|
||||
+ ". It was previously associated with managed entity " + printEntity( oldManagedEntity )
|
||||
+ ". Attempted to replace managed entity with " + printEntity( managedEntity )
|
||||
);
|
||||
}
|
||||
if ( oldOperatedOn == null ) {
|
||||
throw new IllegalStateException(
|
||||
"MergeContext#mergeToManagedEntityXref contained an mergeEntity " + printEntity( mergeEntity )
|
||||
+ ", but MergeContext#mergeEntityToOperatedOnFlagMap did not."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return oldManagedEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the mappings from the specified Map to this MergeContext.
|
||||
* The key and value for each entry in <code>map</code> is subject to the same
|
||||
* restrictions as {@link #put(Object mergeEntity, Object managedEntity)}.
|
||||
*
|
||||
* This method assumes that the merge process is not yet operating on any merge entity
|
||||
*
|
||||
* @param map keys and values must be non-null
|
||||
* @throws NullPointerException if any key or value is null
|
||||
* @throws IllegalArgumentException if a key in <code>map</code> was already in this MergeContext
|
||||
* but associated value in <code>map</code> is different from the previous value in this MergeContext.
|
||||
* @throws IllegalStateException if internal cross-references are out of sync,
|
||||
*/
|
||||
public void putAll(Map map) {
|
||||
for ( Object o : map.entrySet() ) {
|
||||
Entry entry = (Entry) o;
|
||||
put( entry.getKey(), entry.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The remove operation is not supported.
|
||||
* @param mergeEntity the merge entity.
|
||||
* @throws UnsupportedOperationException if called.
|
||||
*/
|
||||
public Object remove(Object mergeEntity) {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format( "Operation not supported: %s.remove()", getClass().getName() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of merge-to-managed entity cross-references in this MergeContext
|
||||
* @return the number of merge-to-managed entity cross-references in this MergeContext
|
||||
*/
|
||||
public int size() {
|
||||
return mergeToManagedEntityXref.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Set view of managed entities contained in this MergeContext.
|
||||
* @return an unmodifiable Set view of managed entities contained in this MergeContext
|
||||
*
|
||||
* @see {@link Collections#unmodifiableSet(java.util.Set)}
|
||||
*/
|
||||
public Collection values() {
|
||||
return Collections.unmodifiableSet( managedToMergeEntityXref.keySet() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the listener is performing the merge operation on the specified merge entity.
|
||||
* @param mergeEntity the merge entity; must be non-null
|
||||
* @return true if the listener is performing the merge operation on the specified merge entity;
|
||||
* false, if there is no mapping for mergeEntity.
|
||||
* @throws NullPointerException if mergeEntity is null
|
||||
*/
|
||||
public boolean isOperatedOn(Object mergeEntity) {
|
||||
if ( mergeEntity == null ) {
|
||||
throw new NullPointerException( "null merge entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
final Boolean isOperatedOn = mergeEntityToOperatedOnFlagMap.get( mergeEntity );
|
||||
return isOperatedOn == null ? false : isOperatedOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flag to indicate if the listener is performing the merge operation on the specified merge entity.
|
||||
* @param mergeEntity must be non-null and this MergeContext must contain a cross-reference for mergeEntity
|
||||
* to a managed entity
|
||||
* @throws NullPointerException if mergeEntity is null
|
||||
* @throws IllegalStateException if this MergeContext does not contain a a cross-reference for mergeEntity
|
||||
*/
|
||||
/* package-private */ void setOperatedOn(Object mergeEntity, boolean isOperatedOn) {
|
||||
if ( mergeEntity == null ) {
|
||||
throw new NullPointerException( "null entities are not supported by " + getClass().getName() );
|
||||
}
|
||||
if ( ! mergeEntityToOperatedOnFlagMap.containsKey( mergeEntity ) ||
|
||||
! mergeToManagedEntityXref.containsKey( mergeEntity ) ) {
|
||||
throw new IllegalStateException( "called MergeContext#setOperatedOn() for mergeEntity not found in MergeContext" );
|
||||
}
|
||||
mergeEntityToOperatedOnFlagMap.put( mergeEntity, isOperatedOn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable map view of the managed-to-merge entity
|
||||
* cross-references.
|
||||
*
|
||||
* The returned Map will contain a cross-reference from each managed entity
|
||||
* to the most recently associated merge entity that was most recently put in the MergeContext.
|
||||
*
|
||||
* @return an unmodifiable map view of the managed-to-merge entity cross-references.
|
||||
*
|
||||
* @see {@link Collections#unmodifiableMap(java.util.Map)}
|
||||
*/
|
||||
public Map invertMap() {
|
||||
return Collections.unmodifiableMap( managedToMergeEntityXref );
|
||||
}
|
||||
|
||||
private String printEntity(Object entity) {
|
||||
if ( session.getPersistenceContext().getEntry( entity ) != null ) {
|
||||
return MessageHelper.infoString( session.getEntityName( entity ), session.getIdentifier( entity ) );
|
||||
}
|
||||
// Entity was not found in current persistence context. Use Object#toString() method.
|
||||
return "[" + entity + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.hibernate.event.internal;
|
||||
|
||||
/**
|
||||
* A {@link org.hibernate.event.spi.MergeEventListener} that does not allow merging
|
||||
* multiple representations of the same persistent entity.
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class NoEntityCopiesMergeEventListener extends DefaultMergeEventListener {
|
||||
|
||||
@Override
|
||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
||||
return new EntityCopyNotAllowedObserver();
|
||||
}
|
||||
}
|
|
@ -1,460 +0,0 @@
|
|||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* 2011/10/20 Unit test for code added in EventCache for performance improvement.
|
||||
*
|
||||
* @author Wim Ockerman @ CISCO
|
||||
*/
|
||||
public class EventCacheTest extends BaseCoreFunctionalTestCase {
|
||||
private Session session = null;
|
||||
private EventCache cache = null;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Simple.class };
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
session = openSession();
|
||||
cache = new EventCache( ( EventSource) session );
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
cache = null;
|
||||
session.close();
|
||||
session = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByCopyToEntityMapping() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put(entity, copy);
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
assertTrue( cache.containsKey( entity ) );
|
||||
assertFalse( cache.containsKey( copy ) );
|
||||
assertTrue( cache.containsValue( copy ) );
|
||||
|
||||
assertTrue( cache.invertMap().containsKey( copy ) );
|
||||
assertFalse( cache.invertMap().containsKey( entity ) );
|
||||
assertTrue( cache.invertMap().containsValue( entity ) );
|
||||
|
||||
cache.clear();
|
||||
|
||||
checkCacheConsistency( cache, 0 );
|
||||
|
||||
assertFalse(cache.containsKey(entity));
|
||||
assertFalse(cache.invertMap().containsKey(copy));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByCopyToEntityMappingOnRemove() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put(entity, copy);
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
assertTrue(cache.containsKey(entity));
|
||||
assertFalse( cache.containsKey( copy ) );
|
||||
|
||||
assertTrue( cache.invertMap().containsKey( copy ) );
|
||||
assertFalse( cache.invertMap().containsKey( entity ) );
|
||||
|
||||
cache.remove( entity );
|
||||
|
||||
checkCacheConsistency( cache, 0 );
|
||||
|
||||
assertFalse(cache.containsKey(entity));
|
||||
assertFalse(cache.invertMap().containsKey(copy));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByCopyToEntityUsingPutAll() {
|
||||
Map<Object,Object> input = new HashMap<Object,Object>();
|
||||
Object entity1 = new Simple( 1 );
|
||||
//
|
||||
Object copy1 = new Integer( 2 );
|
||||
input.put(entity1, copy1);
|
||||
Object entity2 = new Simple( 3 );
|
||||
Object copy2 = new Integer( 2 );
|
||||
input.put(entity2, copy2);
|
||||
cache.putAll(input);
|
||||
|
||||
checkCacheConsistency( cache, 2 );
|
||||
|
||||
assertTrue(cache.containsKey(entity1));
|
||||
assertFalse(cache.containsKey(copy1));
|
||||
assertTrue(cache.containsKey(entity2));
|
||||
assertFalse(cache.containsKey(copy2));
|
||||
|
||||
assertTrue(cache.invertMap().containsKey(copy1));
|
||||
assertFalse(cache.invertMap().containsKey(entity1));
|
||||
|
||||
assertTrue(cache.invertMap().containsKey(copy2));
|
||||
assertFalse(cache.invertMap().containsKey(entity2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByCopyToEntityMappingUsingPutWithSetOperatedOnArg() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put(entity, copy, true);
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
assertTrue(cache.containsKey(entity));
|
||||
assertFalse( cache.containsKey( copy ) );
|
||||
|
||||
assertTrue( cache.invertMap().containsKey( copy ) );
|
||||
assertFalse( cache.invertMap().containsKey( entity ) );
|
||||
|
||||
cache.clear();
|
||||
|
||||
checkCacheConsistency( cache, 0 );
|
||||
|
||||
cache.put(entity, copy, false);
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
assertTrue(cache.containsKey(entity));
|
||||
assertFalse(cache.containsKey(copy));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByIterateEntrySet() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put( entity, copy, true );
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
Iterator it = cache.entrySet().iterator();
|
||||
assertTrue( it.hasNext() );
|
||||
Map.Entry entry = ( Map.Entry ) it.next();
|
||||
assertSame( entity, entry.getKey() );
|
||||
assertSame( copy, entry.getValue() );
|
||||
assertFalse( it.hasNext() );
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByModifyEntrySet() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put( entity, copy, true );
|
||||
|
||||
Iterator it = cache.entrySet().iterator();
|
||||
try {
|
||||
it.remove();
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
Map.Entry entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||
try {
|
||||
cache.entrySet().remove( entry );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
Map.Entry anotherEntry = new Map.Entry() {
|
||||
private Object key = new Simple( 3 );
|
||||
private Object value = 4;
|
||||
@Override
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(Object value) {
|
||||
Object oldValue = this.value;
|
||||
this.value = value;
|
||||
return oldValue;
|
||||
}
|
||||
};
|
||||
try {
|
||||
cache.entrySet().add( anotherEntry );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByModifyKeys() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put( entity, copy, true );
|
||||
|
||||
Iterator it = cache.keySet().iterator();
|
||||
try {
|
||||
it.remove();
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
cache.keySet().remove( entity );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
Object newCopy = new Simple( 3 );
|
||||
try {
|
||||
cache.keySet().add( newCopy );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByModifyValues() {
|
||||
Object entity = new Simple( 1 );
|
||||
Object copy = new Simple( 2 );
|
||||
|
||||
cache.put( entity, copy, true );
|
||||
|
||||
Iterator it = cache.values().iterator();
|
||||
try {
|
||||
it.remove();
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
cache.values().remove( copy );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
|
||||
Object newCopy = new Simple( 3 );
|
||||
try {
|
||||
cache.values().add( newCopy );
|
||||
fail( "should have thrown UnsupportedOperationException" );
|
||||
}
|
||||
catch ( UnsupportedOperationException ex ) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByModifyKeyOfEntrySetElement() {
|
||||
Simple entity = new Simple( 1 );
|
||||
Simple copy = new Simple( 0 );
|
||||
cache.put(entity, copy, true);
|
||||
|
||||
Map.Entry entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||
( ( Simple ) entry.getKey() ).setValue( 2 );
|
||||
assertEquals( 2, entity.getValue() );
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||
assertSame( entity, entry.getKey() );
|
||||
assertSame( copy, entry.getValue() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityToCopyFillFollowedByModifyValueOfEntrySetElement() {
|
||||
Simple entity = new Simple( 1 );
|
||||
Simple copy = new Simple( 0 );
|
||||
cache.put(entity, copy, true);
|
||||
|
||||
Map.Entry entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||
( ( Simple ) entry.getValue() ).setValue( 2 );
|
||||
assertEquals( 2, copy.getValue() );
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||
assertSame( entity, entry.getKey() );
|
||||
assertSame( copy, entry.getValue() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplaceEntityCopy() {
|
||||
Simple entity = new Simple( 1 );
|
||||
Simple copy = new Simple( 0 );
|
||||
cache.put(entity, copy);
|
||||
|
||||
Simple copyNew = new Simple( 0 );
|
||||
assertSame( copy, cache.put( entity, copyNew ) );
|
||||
assertSame( copyNew, cache.get( entity ) );
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
|
||||
copy = copyNew;
|
||||
copyNew = new Simple( 1 );
|
||||
assertSame( copy, cache.put( entity, copyNew ) );
|
||||
assertSame( copyNew, cache.get( entity ) );
|
||||
|
||||
checkCacheConsistency( cache, 1 );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyAssociatedWithNewAndExistingEntity() {
|
||||
session.getTransaction().begin();
|
||||
Simple entity = new Simple( 1 );
|
||||
Simple copy = new Simple( 0 );
|
||||
session.persist( entity );
|
||||
cache.put(entity, copy);
|
||||
session.flush();
|
||||
|
||||
try {
|
||||
cache.put( new Simple( 1 ), copy );
|
||||
fail( "should have thrown IllegalStateException");
|
||||
}
|
||||
catch( IllegalStateException ex ) {
|
||||
// expected
|
||||
assertTrue( ex.getMessage().startsWith( "Error occurred while storing entity [org.hibernate.event.internal.EventCacheTest$Simple@" ) );
|
||||
}
|
||||
session.getTransaction().rollback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyAssociatedWith2ExistingEntities() {
|
||||
session.getTransaction().begin();
|
||||
Simple entity1 = new Simple( 1 );
|
||||
session.persist( entity1 );
|
||||
Simple copy1 = new Simple( 1 );
|
||||
cache.put(entity1, copy1);
|
||||
Simple entity2 = new Simple( 2 );
|
||||
session.persist( entity2 );
|
||||
Simple copy2 = new Simple( 2 );
|
||||
cache.put( entity2, copy2 );
|
||||
session.flush();
|
||||
|
||||
try {
|
||||
cache.put( entity1, copy2 );
|
||||
fail( "should have thrown IllegalStateException");
|
||||
}
|
||||
catch( IllegalStateException ex ) {
|
||||
// expected
|
||||
assertTrue( ex.getMessage().startsWith( "Error occurred while storing entity [org.hibernate.event.internal.EventCacheTest$Simple#1]." ) );
|
||||
}
|
||||
session.getTransaction().rollback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveNonExistingEntity() {
|
||||
assertNull( cache.remove( new Simple( 1 ) ) );
|
||||
}
|
||||
|
||||
private void checkCacheConsistency(EventCache cache, int expectedSize) {
|
||||
Set entrySet = cache.entrySet();
|
||||
Set cacheKeys = cache.keySet();
|
||||
Collection cacheValues = cache.values();
|
||||
Map invertedMap = cache.invertMap();
|
||||
|
||||
assertEquals( expectedSize, entrySet.size() );
|
||||
assertEquals( expectedSize, cache.size() );
|
||||
assertEquals( expectedSize, cacheKeys.size() );
|
||||
assertEquals( expectedSize, cacheValues.size() );
|
||||
assertEquals( expectedSize, invertedMap.size() );
|
||||
|
||||
for ( Object entry : cache.entrySet() ) {
|
||||
Map.Entry mapEntry = ( Map.Entry ) entry;
|
||||
assertSame( cache.get( mapEntry.getKey() ), mapEntry.getValue() );
|
||||
assertTrue( cacheKeys.contains( mapEntry.getKey() ) );
|
||||
assertTrue( cacheValues.contains( mapEntry.getValue() ) );
|
||||
assertSame( mapEntry.getKey(), invertedMap.get( mapEntry.getValue() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity
|
||||
private static class Simple {
|
||||
@Id
|
||||
private int value;
|
||||
|
||||
public Simple(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class Category {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Item exampleItem;
|
||||
private int version;
|
||||
private Set<SubCategory> subCategories = new HashSet<SubCategory>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Item getExampleItem() {
|
||||
return exampleItem;
|
||||
}
|
||||
|
||||
public void setExampleItem(Item exampleItem) {
|
||||
this.exampleItem = exampleItem;
|
||||
}
|
||||
|
||||
public Set<SubCategory> getSubCategories() {
|
||||
return subCategories;
|
||||
}
|
||||
|
||||
public void setSubCategories(Set<SubCategory> subCategories) {
|
||||
this.subCategories = subCategories;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Category{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", version=" + version +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
-->
|
||||
|
||||
<hibernate-mapping package="org.hibernate.test.ops">
|
||||
|
||||
<class name="Hoarder">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name"/>
|
||||
<many-to-one name="favoriteItem" cascade="persist,merge,save-update" />
|
||||
<set name="items" cascade="persist,merge,save-update">
|
||||
<key column="HOARDER_ID"/>
|
||||
<one-to-many class="Item" />
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Item">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<version name="version"/>
|
||||
<property name="name" not-null="true" />
|
||||
<many-to-one name="category" cascade="persist,merge,save-update"/>
|
||||
<set name="colors">
|
||||
<key column="itemId"/>
|
||||
<element type="string" not-null="true"/>
|
||||
</set>
|
||||
<list name="subItemsBackref" lazy="true" cascade="persist,merge,save-update">
|
||||
<key column="parentItem" not-null="true"/>
|
||||
<index column="index"/>
|
||||
<one-to-many class="SubItem"/>
|
||||
</list>
|
||||
</class>
|
||||
|
||||
<class name="SubItem">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name" not-null="true" />
|
||||
</class>
|
||||
|
||||
|
||||
<class name="Category">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<version name="version" />
|
||||
<property name="name" not-null="true" />
|
||||
<many-to-one name="exampleItem" cascade="persist,merge,save-update" />
|
||||
<set name="subCategories" lazy="true" cascade="persist,merge,save-update">
|
||||
<key column="parentCategory" not-null="false"/>
|
||||
<one-to-many class="SubCategory"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="SubCategory">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name" not-null="true" />
|
||||
</class>
|
||||
|
||||
|
||||
</hibernate-mapping>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class Hoarder {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Item favoriteItem;
|
||||
private Set<Item> items = new HashSet<Item>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Item getFavoriteItem() {
|
||||
return favoriteItem;
|
||||
}
|
||||
|
||||
public void setFavoriteItem(Item favoriteItem) {
|
||||
this.favoriteItem = favoriteItem;
|
||||
}
|
||||
|
||||
public Set<Item> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(Set<Item> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Hoarder{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
", favoriteItem=" + favoriteItem +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
-->
|
||||
|
||||
<hibernate-mapping package="org.hibernate.test.ops">
|
||||
|
||||
<class name="Hoarder">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name"/>
|
||||
<many-to-one name="favoriteItem" cascade="persist,merge,save-update" />
|
||||
<set name="items" cascade="persist,merge,save-update">
|
||||
<key column="HOARDER_ID"/>
|
||||
<one-to-many class="Item" />
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="Item">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<version name="version"/>
|
||||
<property name="name" not-null="true" />
|
||||
<many-to-one name="category" cascade="persist,merge,save-update"/>
|
||||
<set name="colors">
|
||||
<key column="itemId"/>
|
||||
<element type="string" not-null="true"/>
|
||||
</set>
|
||||
<list name="subItemsBackref" lazy="true" cascade="persist,merge,save-update,delete-orphan">
|
||||
<key column="parentItem" not-null="true"/>
|
||||
<index column="index"/>
|
||||
<one-to-many class="SubItem"/>
|
||||
</list>
|
||||
</class>
|
||||
|
||||
<class name="SubItem">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name" not-null="true" />
|
||||
</class>
|
||||
|
||||
|
||||
<class name="Category">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<version name="version" />
|
||||
<property name="name" not-null="true" />
|
||||
<many-to-one name="exampleItem" cascade="persist,merge,save-update" />
|
||||
<set name="subCategories" lazy="true" cascade="persist,merge,save-update,delete-orphan">
|
||||
<key column="parentCategory" not-null="false"/>
|
||||
<one-to-many class="SubCategory"/>
|
||||
</set>
|
||||
</class>
|
||||
|
||||
<class name="SubCategory">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="name" not-null="true" />
|
||||
</class>
|
||||
|
||||
|
||||
</hibernate-mapping>
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class Item {
|
||||
private Long id;
|
||||
private int version;
|
||||
private String name;
|
||||
private Category category;
|
||||
private List<SubItem> subItemsBackref = new ArrayList<SubItem>();
|
||||
private Set<String> colors = new HashSet<String>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Category getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(Category category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public Set<String> getColors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
public void setColors(Set<String> colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public List<SubItem> getSubItemsBackref() {
|
||||
return subItemsBackref;
|
||||
}
|
||||
|
||||
public void setSubItemsBackref(List<SubItem> subItemsBackref) {
|
||||
this.subItemsBackref = subItemsBackref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Item item = (Item) o;
|
||||
|
||||
if ( !name.equals( item.name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Item{" +
|
||||
"id=" + id +
|
||||
", version=" + version +
|
||||
", name='" + name + '\'' +
|
||||
//", category=" + category +
|
||||
//", subItems=" + subItems +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.event.internal.NoEntityCopiesMergeEventListener;
|
||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests merging multiple detached representations of the same entity using
|
||||
* a MergeEventListener that does not allow this.
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-9106")
|
||||
public class MergeMultipleEntityRepresentationsNotAllowedTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
public String[] getMappings() {
|
||||
return new String[] {
|
||||
"ops/Hoarder.hbm.xml"
|
||||
};
|
||||
}
|
||||
|
||||
protected void afterSessionFactoryBuilt() {
|
||||
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
||||
registry.setListeners( EventType.MERGE, new NoEntityCopiesMergeEventListener() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCascadeFromDetachedToNonDirtyRepresentations() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1" );
|
||||
|
||||
Hoarder hoarder = new Hoarder();
|
||||
hoarder.setName( "joe" );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
s.persist( item1 );
|
||||
s.persist( hoarder );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// Get another representation of the same Item from a different session.
|
||||
|
||||
s = openSession();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
s.close();
|
||||
|
||||
// item1_1 and item1_2 are unmodified representations of the same persistent entity.
|
||||
assertFalse( item1 == item1_1 );
|
||||
assertTrue( item1.equals( item1_1 ) );
|
||||
|
||||
// Update hoarder (detached) to references both representations.
|
||||
hoarder.getItems().add( item1 );
|
||||
hoarder.setFavoriteItem( item1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
try {
|
||||
hoarder = (Hoarder) s.merge( hoarder );
|
||||
fail( "should have failed due IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
//expected
|
||||
}
|
||||
finally {
|
||||
tx.rollback();
|
||||
s.close();
|
||||
}
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTopLevelManyToOneManagedNestedIsDetached() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1 name" );
|
||||
Category category = new Category();
|
||||
category.setName( "category" );
|
||||
item1.setCategory( category );
|
||||
category.setExampleItem( item1 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( item1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of item1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1Merged = (Item) s.merge( item1 );
|
||||
|
||||
item1Merged.setCategory( category );
|
||||
category.setExampleItem( item1_1 );
|
||||
|
||||
// now item1Merged is managed and it has a nested detached item
|
||||
try {
|
||||
s.merge( item1Merged );
|
||||
fail( "should have failed due IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
//expected
|
||||
}
|
||||
finally {
|
||||
tx.rollback();
|
||||
s.close();
|
||||
}
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
private void cleanup() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
s.createQuery( "delete from SubItem" ).executeUpdate();
|
||||
for ( Hoarder hoarder : (List<Hoarder>) s.createQuery( "from Hoarder" ).list() ) {
|
||||
hoarder.getItems().clear();
|
||||
s.delete( hoarder );
|
||||
}
|
||||
|
||||
for ( Category category : (List<Category>) s.createQuery( "from Category" ).list() ) {
|
||||
if ( category.getExampleItem() != null ) {
|
||||
category.setExampleItem( null );
|
||||
s.delete( category );
|
||||
}
|
||||
}
|
||||
|
||||
for ( Item item : (List<Item>) s.createQuery( "from Item" ).list() ) {
|
||||
item.setCategory( null );
|
||||
s.delete( item );
|
||||
}
|
||||
|
||||
s.createQuery( "delete from Item" ).executeUpdate();
|
||||
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests merging multiple detached representations of the same entity.
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class MergeMultipleEntityRepresentationsOrphanDeleteTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
public String[] getMappings() {
|
||||
return new String[] {
|
||||
"ops/HoarderOrphanDelete.hbm.xml"
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9106" )
|
||||
public void testTopLevelUnidirOneToManyBackrefWithNewElement() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1 name" );
|
||||
SubItem subItem1 = new SubItem();
|
||||
subItem1.setName( "subItem1 name" );
|
||||
item1.getSubItemsBackref().add( subItem1 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( item1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of item1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( item1_1.getSubItemsBackref() ) );
|
||||
|
||||
Category category = new Category();
|
||||
category.setName( "category" );
|
||||
|
||||
SubItem subItem2 = new SubItem();
|
||||
subItem2.setName( "subItem2 name" );
|
||||
item1.getSubItemsBackref().add( subItem2 );
|
||||
|
||||
item1.setCategory( category );
|
||||
category.setExampleItem( item1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
// The following will fail due to PropertyValueException because item1 will
|
||||
// be removed from the inverted merge map when the operation cascades to item1_1.
|
||||
Item item1Merged = (Item) s.merge( item1 );
|
||||
// top-level collection should win
|
||||
assertEquals( 2, item1.getSubItemsBackref().size() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
item1 = (Item) s.get( Item.class, item1.getId() );
|
||||
assertEquals( 2, item1.getSubItemsBackref().size() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-9171" )
|
||||
public void testNestedUnidirOneToManyBackrefWithNewElement() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1 name" );
|
||||
SubItem subItem1 = new SubItem();
|
||||
subItem1.setName( "subItem1 name" );
|
||||
item1.getSubItemsBackref().add( subItem1 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( item1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of item1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
Hibernate.initialize( item1_1.getSubItemsBackref() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
Category category = new Category();
|
||||
category.setName( "category" );
|
||||
item1.setCategory( category );
|
||||
|
||||
// Add a new SubItem to the Item representation that will be in a nested association.
|
||||
SubItem subItem2 = new SubItem();
|
||||
subItem2.setName( "subItem2 name" );
|
||||
item1_1.getSubItemsBackref().add( subItem2 );
|
||||
|
||||
category.setExampleItem( item1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1Merged = (Item) s.merge( item1 );
|
||||
// The new SubItem was persisted because it is added to the nested item's collection.
|
||||
// item1.subItems overwrites the collection (which should remove the new SubItem),
|
||||
// Even though cascade includes "delete-orphan", the new SubItem is not
|
||||
// deleted. This is because JPA spec says,"If the entity being orphaned is a detached,
|
||||
// new, or removed entity, the semantics of orphanRemoval do not apply."
|
||||
|
||||
// Because the collection key is non-nullable, SubItem still has a backref to the
|
||||
// same collection owner.
|
||||
// gb: Shouldn't the collection key be updated to null causing a ConstraintViolationException???
|
||||
|
||||
// Note that the collection resulting from the merge looks OK here.
|
||||
assertEquals( 1, item1Merged.getSubItemsBackref().size() );
|
||||
assertEquals( "subItem1 name", item1Merged.getSubItemsBackref().get( 0 ).getName() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
item1 = (Item) s.get( Item.class, item1.getId() );
|
||||
// The following fails because that new SubItem from the previous transaction gets loaded
|
||||
// into the collection because it still has the collection key set. <== gb: bug?
|
||||
assertEquals( 1, item1.getSubItemsBackref().size() );
|
||||
assertEquals( "subItem1 name", item1.getSubItemsBackref().get( 0 ).getName() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9106" )
|
||||
public void testTopLevelUnidirOneToManyBackrefWithRemovedElement() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1 name" );
|
||||
SubItem subItem1 = new SubItem();
|
||||
subItem1.setName( "subItem1 name" );
|
||||
item1.getSubItemsBackref().add( subItem1 );
|
||||
SubItem subItem2 = new SubItem();
|
||||
subItem2.setName( "subItem2 name" );
|
||||
item1.getSubItemsBackref().add( subItem2 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( item1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of item1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( item1_1.getSubItemsBackref() ) );
|
||||
|
||||
Category category = new Category();
|
||||
category.setName( "category" );
|
||||
|
||||
item1.setCategory( category );
|
||||
category.setExampleItem( item1_1 );
|
||||
|
||||
// remove subItem1 from top-level Item
|
||||
item1.getSubItemsBackref().remove( subItem1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1Merged = (Item) s.merge( item1 );
|
||||
// top-level collection should win
|
||||
assertEquals( 1, item1Merged.getSubItemsBackref().size() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
item1 = (Item) s.get( Item.class, item1.getId() );
|
||||
assertEquals( 1, item1.getSubItemsBackref().size() );
|
||||
subItem1 = (SubItem) s.get( SubItem.class, subItem1.getId() );
|
||||
assertNull( subItem1 );
|
||||
tx.commit();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9171" )
|
||||
public void testNestedUnidirOneToManyBackrefWithRemovedElement() {
|
||||
Item item1 = new Item();
|
||||
item1.setName( "item1 name" );
|
||||
SubItem subItem1 = new SubItem();
|
||||
subItem1.setName( "subItem1 name" );
|
||||
item1.getSubItemsBackref().add( subItem1 );
|
||||
SubItem subItem2 = new SubItem();
|
||||
subItem2.setName( "subItem2 name" );
|
||||
item1.getSubItemsBackref().add( subItem2 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( item1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of item1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1_1 = (Item) s.get( Item.class, item1.getId() );
|
||||
Hibernate.initialize( item1_1.getSubItemsBackref() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// remove subItem1 from the nested Item
|
||||
item1_1.getSubItemsBackref().remove( subItem1 );
|
||||
|
||||
Category category = new Category();
|
||||
category.setName( "category" );
|
||||
item1.setCategory( category );
|
||||
category.setExampleItem( item1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Item item1Merged = (Item) s.merge( item1 );
|
||||
// collection from top-level Item should win
|
||||
assertEquals( 2, item1Merged.getSubItemsBackref().size() );
|
||||
assertTrue( item1Merged.getSubItemsBackref().contains( subItem1 ) );
|
||||
assertTrue( item1Merged.getSubItemsBackref().contains( subItem2 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
item1 = (Item) s.get( Item.class, item1.getId() );
|
||||
assertEquals( 2, item1.getSubItemsBackref().size() );
|
||||
assertTrue( item1.getSubItemsBackref().contains( subItem1 ) );
|
||||
assertTrue( item1.getSubItemsBackref().contains( subItem2 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9106" )
|
||||
public void testTopLevelUnidirOneToManyNoBackrefWithNewElement() {
|
||||
Category category1 = new Category();
|
||||
category1.setName( "category1 name" );
|
||||
SubCategory subCategory1 = new SubCategory();
|
||||
subCategory1.setName( "subCategory1 name" );
|
||||
category1.getSubCategories().add( subCategory1 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( category1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of category1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1_1 = (Category) s.get( Category.class, category1.getId() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( category1_1.getSubCategories() ) );
|
||||
|
||||
SubCategory subCategory2 = new SubCategory();
|
||||
subCategory2.setName( "subCategory2 name" );
|
||||
category1.getSubCategories().add( subCategory2 );
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "item" );
|
||||
category1.setExampleItem( item );
|
||||
item.setCategory( category1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1Merged = (Category) s.merge( category1 );
|
||||
assertEquals( 2, category1Merged.getSubCategories().size() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
category1 = (Category) s.get( Category.class, category1.getId() );
|
||||
assertEquals( 2, category1.getSubCategories().size() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedUnidirOneToManyNoBackrefWithNewElement() {
|
||||
Category category1 = new Category();
|
||||
category1.setName( "category1 name" );
|
||||
SubCategory subCategory1 = new SubCategory();
|
||||
subCategory1.setName( "subCategory1 name" );
|
||||
category1.getSubCategories().add( subCategory1 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( category1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of category1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1_1 = (Category) s.get( Category.class, category1.getId() );
|
||||
Hibernate.initialize( category1_1.getSubCategories() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
SubCategory subCategory2 = new SubCategory();
|
||||
subCategory2.setName( "subCategory2 name" );
|
||||
category1_1.getSubCategories().add( subCategory2 );
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "item" );
|
||||
category1.setExampleItem( item );
|
||||
item.setCategory( category1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
// top-level collection should still have just 1 element.
|
||||
// copy of subcategory2 should still be persisted even though cascade includes delete-orphan.
|
||||
// This is because in Section 2.9, Entity Relationships, the JPA 2.1 spec says:
|
||||
// "If the entity being orphaned is a detached, new, or removed entity, the semantics of orphanRemoval do not apply.".
|
||||
Category category1Merged = (Category) s.merge( category1 );
|
||||
assertEquals( 1, category1Merged.getSubCategories().size() );
|
||||
assertTrue( category1Merged.getSubCategories().contains( subCategory1 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
category1 = (Category) s.get( Category.class, category1.getId() );
|
||||
assertEquals( 1, category1.getSubCategories().size() );
|
||||
assertTrue( category1.getSubCategories().contains( subCategory1 ) );
|
||||
subCategory2 = (SubCategory) s.createQuery( "from SubCategory sc where sc.name = 'subCategory2 name'" ).uniqueResult();
|
||||
assertNotNull( subCategory2 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9106" )
|
||||
public void testTopLevelUnidirOneToManyNoBackrefWithRemovedElement() {
|
||||
Category category1 = new Category();
|
||||
category1.setName( "category1 name" );
|
||||
SubCategory subCategory1 = new SubCategory();
|
||||
subCategory1.setName( "subCategory1 name" );
|
||||
category1.getSubCategories().add( subCategory1 );
|
||||
SubCategory subCategory2 = new SubCategory();
|
||||
subCategory2.setName( "subCategory2 name" );
|
||||
category1.getSubCategories().add( subCategory2 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( category1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of category1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1_1 = (Category) s.get( Category.class, category1.getId() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( category1_1.getSubCategories() ) );
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "item" );
|
||||
category1.setExampleItem( item );
|
||||
item.setCategory( category1_1 );
|
||||
|
||||
category1.getSubCategories().remove( subCategory1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1Merged = (Category) s.merge( category1 );
|
||||
assertEquals( 1, category1Merged.getSubCategories().size() );
|
||||
assertTrue( category1Merged.getSubCategories().contains( subCategory2 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
category1 = (Category) s.get( Category.class, category1.getId() );
|
||||
assertEquals( 1, category1.getSubCategories().size() );
|
||||
assertTrue( category1.getSubCategories().contains( subCategory2 ) );
|
||||
subCategory1 = (SubCategory) s.get( SubCategory.class, subCategory1.getId() );
|
||||
assertNull( subCategory1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
//@FailureExpected( jiraKey = "HHH-9171" )
|
||||
public void testNestedUnidirOneToManyNoBackrefWithRemovedElement() {
|
||||
Category category1 = new Category();
|
||||
category1.setName( "category1 name" );
|
||||
SubCategory subCategory1 = new SubCategory();
|
||||
subCategory1.setName( "subCategory1 name" );
|
||||
category1.getSubCategories().add( subCategory1 );
|
||||
SubCategory subCategory2 = new SubCategory();
|
||||
subCategory2.setName( "subCategory2 name" );
|
||||
category1.getSubCategories().add( subCategory2 );
|
||||
|
||||
Session s = openSession();
|
||||
Transaction tx = s.beginTransaction();
|
||||
s.persist( category1 );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
// get another representation of category1
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
Category category1_1 = (Category) s.get( Category.class, category1.getId() );
|
||||
Hibernate.initialize( category1_1.getSubCategories() );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
category1_1.getSubCategories().remove( subCategory2 );
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "item" );
|
||||
category1.setExampleItem( item );
|
||||
item.setCategory( category1_1 );
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
// top-level collection should still have 2 elements.
|
||||
Category category1Merged = (Category) s.merge( category1 );
|
||||
assertEquals( 2, category1Merged.getSubCategories().size() );
|
||||
assertTrue( category1Merged.getSubCategories().contains( subCategory1 ) );
|
||||
assertTrue( category1Merged.getSubCategories().contains( subCategory2 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
tx = s.beginTransaction();
|
||||
category1 = (Category) s.get( Category.class, category1.getId() );
|
||||
assertEquals( 2, category1.getSubCategories().size() );
|
||||
assertTrue( category1.getSubCategories().contains( subCategory1 ) );
|
||||
assertTrue( category1.getSubCategories().contains( subCategory2 ) );
|
||||
tx.commit();
|
||||
s.close();
|
||||
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
private void cleanup() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
s.createQuery( "delete from SubItem" ).executeUpdate();
|
||||
for ( Hoarder hoarder : (List<Hoarder>) s.createQuery( "from Hoarder" ).list() ) {
|
||||
hoarder.getItems().clear();
|
||||
s.delete( hoarder );
|
||||
}
|
||||
|
||||
for ( Category category : (List<Category>) s.createQuery( "from Category" ).list() ) {
|
||||
if ( category.getExampleItem() != null ) {
|
||||
category.setExampleItem( null );
|
||||
s.delete( category );
|
||||
}
|
||||
}
|
||||
|
||||
for ( Item item : (List<Item>) s.createQuery( "from Item" ).list() ) {
|
||||
item.setCategory( null );
|
||||
s.delete( item );
|
||||
}
|
||||
|
||||
s.createQuery( "delete from Item" ).executeUpdate();
|
||||
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -745,11 +745,11 @@ public class MergeTest extends AbstractOperationTestCase {
|
|||
Employer jboss = new Employer();
|
||||
Employee gavin = new Employee();
|
||||
jboss.setEmployees( new ArrayList() );
|
||||
jboss.getEmployees().add(gavin);
|
||||
s.merge(jboss);
|
||||
jboss.getEmployees().add( gavin );
|
||||
s.merge( jboss );
|
||||
s.flush();
|
||||
jboss = (Employer) s.createQuery("from Employer e join fetch e.employees").uniqueResult();
|
||||
assertTrue( Hibernate.isInitialized( jboss.getEmployees() ) );
|
||||
assertTrue( Hibernate.isInitialized( jboss.getEmployees() ) );
|
||||
assertEquals( 1, jboss.getEmployees().size() );
|
||||
s.clear();
|
||||
s.merge( jboss.getEmployees().iterator().next() );
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class SubCategory {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SubCategory item = (SubCategory) o;
|
||||
|
||||
if ( !name.equals( item.name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SubItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.ops;
|
||||
|
||||
/**
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class SubItem {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SubItem item = (SubItem) o;
|
||||
|
||||
if ( !name.equals( item.name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SubItem{" +
|
||||
"id=" + id +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -17,3 +17,6 @@ log4j.logger.org.hibernate.hql.internal.ast=debug
|
|||
log4j.logger.org.hibernate.sql.ordering.antlr=debug
|
||||
|
||||
log4j.logger.org.hibernate.engine.internal.LoggingSessionEventListener=info
|
||||
|
||||
### enable the following line to log multiple entity representations being merged for a persistent entity.
|
||||
#log4j.logger.org.hibernate.event.internal.DefaultEntityCopyObserver=debug
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.jpa.event.internal.core;
|
||||
|
||||
import org.hibernate.event.internal.EntityCopyNotAllowedObserver;
|
||||
import org.hibernate.event.internal.EntityCopyObserver;
|
||||
|
||||
/**
|
||||
* Overrides {@link JpaMergeEventListener} to disallow merging multiple representations
|
||||
* of the same persistent entity.
|
||||
*
|
||||
* @author Gail Badner
|
||||
*/
|
||||
public class JpaNoEntityCopiesMergeEventListener extends JpaMergeEventListener {
|
||||
|
||||
@Override
|
||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
||||
return new EntityCopyNotAllowedObserver();
|
||||
}
|
||||
|
||||
}
|
|
@ -37,3 +37,6 @@ log4j.logger.org.hibernate.tool.hbm2ddl=debug
|
|||
### enable the following line if you want to track down connection ###
|
||||
### leakages when using DriverManagerConnectionProvider ###
|
||||
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
|
||||
|
||||
### enable the following line to log multiple entity representations being merged for a persistent entity.
|
||||
#log4j.logger.org.hibernate.event.internal.DefaultEntityCopyObserver=debug
|
||||
|
|
Loading…
Reference in New Issue