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,
|
collectionPersister,
|
||||||
unmergedInstance
|
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,
|
collectionPersister,
|
||||||
mergeMap.get( proxy )
|
mergeMap.get( proxy )
|
||||||
);
|
);
|
||||||
|
LOG.debugf(
|
||||||
|
"Detached proxy being merged has a collection that [%s] the managed child.",
|
||||||
|
(found ? "contains" : "does not contain")
|
||||||
|
);
|
||||||
if ( !found ) {
|
if ( !found ) {
|
||||||
found = isFoundInParent(
|
found = isFoundInParent(
|
||||||
propertyName,
|
propertyName,
|
||||||
|
@ -1356,6 +1364,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
collectionPersister,
|
collectionPersister,
|
||||||
mergeMap.get( proxy )
|
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 ) {
|
if ( found ) {
|
||||||
return proxy.getHibernateLazyInitializer().getIdentifier();
|
return proxy.getHibernateLazyInitializer().getIdentifier();
|
||||||
|
@ -1402,10 +1414,14 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
Object index = getIndexInParent(property, childEntity, persister, cp, parent);
|
Object index = getIndexInParent(property, childEntity, persister, cp, parent);
|
||||||
|
|
||||||
if (index==null && mergeMap!=null) {
|
if (index==null && mergeMap!=null) {
|
||||||
Object unmergedInstance = mergeMap.get(parent);
|
final Object unMergedInstance = mergeMap.get( parent );
|
||||||
Object unmergedChild = mergeMap.get(childEntity);
|
final Object unMergedChild = mergeMap.get( childEntity );
|
||||||
if ( unmergedInstance!=null && unmergedChild!=null ) {
|
if ( unMergedInstance != null && unMergedChild != null ) {
|
||||||
index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance);
|
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) {
|
if (index!=null) {
|
||||||
|
@ -1421,15 +1437,18 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
||||||
for ( Entry<Object, EntityEntry> me : reentrantSafeEntityEntries() ) {
|
for ( Entry<Object, EntityEntry> me : reentrantSafeEntityEntries() ) {
|
||||||
EntityEntry ee = me.getValue();
|
EntityEntry ee = me.getValue();
|
||||||
if ( persister.isSubclassEntityName( ee.getEntityName() ) ) {
|
if ( persister.isSubclassEntityName( ee.getEntityName() ) ) {
|
||||||
Object instance = me.getKey();
|
final Object instance = me.getKey();
|
||||||
|
|
||||||
Object index = getIndexInParent(property, childEntity, persister, cp, instance);
|
Object index = getIndexInParent( property, childEntity, persister, cp, instance );
|
||||||
|
if ( index==null && mergeMap!=null ) {
|
||||||
if (index==null && mergeMap!=null) {
|
final Object unMergedInstance = mergeMap.get( instance );
|
||||||
Object unmergedInstance = mergeMap.get(instance);
|
final Object unMergedChild = mergeMap.get( childEntity );
|
||||||
Object unmergedChild = mergeMap.get(childEntity);
|
if ( unMergedInstance != null && unMergedChild!=null ) {
|
||||||
if ( unmergedInstance!=null && unmergedChild!=null ) {
|
index = getIndexInParent( property, unMergedChild, persister, cp, unMergedInstance );
|
||||||
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
|
* 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
|
* indicated by the @author tags or express copyright attribution
|
||||||
* statements applied by the authors. All third-party contributions are
|
* statements applied by the authors. All third-party contributions are
|
||||||
* distributed under license by Red Hat Inc.
|
* distributed under license by Red Hat Inc.
|
||||||
|
@ -62,7 +62,11 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map getMergeMap(Object anything) {
|
protected Map getMergeMap(Object anything) {
|
||||||
return ( ( EventCache ) anything ).invertMap();
|
return ( (MergeContext) anything ).invertMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
||||||
|
return new DefaultEntityCopyObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,10 +76,16 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
* @throws HibernateException
|
* @throws HibernateException
|
||||||
*/
|
*/
|
||||||
public void onMerge(MergeEvent event) throws HibernateException {
|
public void onMerge(MergeEvent event) throws HibernateException {
|
||||||
EventCache copyCache = new EventCache( event.getSession() );
|
final EntityCopyObserver entityCopyObserver = createDetachedEntityCopyObserver();
|
||||||
onMerge( event, copyCache );
|
final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
|
||||||
copyCache.clear();
|
try {
|
||||||
copyCache = null;
|
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 {
|
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 EventSource source = event.getSession();
|
||||||
final Object original = event.getOriginal();
|
final Object original = event.getOriginal();
|
||||||
|
|
||||||
|
@ -178,7 +188,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
final EventSource source = event.getSession();
|
final EventSource source = event.getSession();
|
||||||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
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);
|
cascadeOnMerge(source, persister, entity, copyCache);
|
||||||
copyValues(persister, entity, entity, source, copyCache);
|
copyValues(persister, entity, entity, source, copyCache);
|
||||||
|
@ -203,7 +213,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
persister.setIdentifier( copyCache.get( entity ), id, source );
|
persister.setIdentifier( copyCache.get( entity ), id, source );
|
||||||
}
|
}
|
||||||
else {
|
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 );
|
final Object copy = copyCache.get( entity );
|
||||||
|
|
||||||
|
@ -282,7 +292,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
entityIsTransient(event, copyCache);
|
entityIsTransient(event, copyCache);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
( ( EventCache ) copyCache ).put( entity, result, true ); //before cascade!
|
( (MergeContext) copyCache ).put( entity, result, true ); //before cascade!
|
||||||
|
|
||||||
final Object target = source.getPersistenceContext().unproxy(result);
|
final Object target = source.getPersistenceContext().unproxy(result);
|
||||||
if ( target == entity ) {
|
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,8 +745,8 @@ public class MergeTest extends AbstractOperationTestCase {
|
||||||
Employer jboss = new Employer();
|
Employer jboss = new Employer();
|
||||||
Employee gavin = new Employee();
|
Employee gavin = new Employee();
|
||||||
jboss.setEmployees( new ArrayList() );
|
jboss.setEmployees( new ArrayList() );
|
||||||
jboss.getEmployees().add(gavin);
|
jboss.getEmployees().add( gavin );
|
||||||
s.merge(jboss);
|
s.merge( jboss );
|
||||||
s.flush();
|
s.flush();
|
||||||
jboss = (Employer) s.createQuery("from Employer e join fetch e.employees").uniqueResult();
|
jboss = (Employer) s.createQuery("from Employer e join fetch e.employees").uniqueResult();
|
||||||
assertTrue( Hibernate.isInitialized( jboss.getEmployees() ) );
|
assertTrue( Hibernate.isInitialized( jboss.getEmployees() ) );
|
||||||
|
|
|
@ -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.sql.ordering.antlr=debug
|
||||||
|
|
||||||
log4j.logger.org.hibernate.engine.internal.LoggingSessionEventListener=info
|
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 ###
|
### enable the following line if you want to track down connection ###
|
||||||
### leakages when using DriverManagerConnectionProvider ###
|
### leakages when using DriverManagerConnectionProvider ###
|
||||||
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
|
#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