HHH-9106 : Merging multiple representations of the same entity
This commit is contained in:
parent
c7c5b015c1
commit
2daaf9a196
|
@ -64,8 +64,8 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
return ( (MergeContext) anything ).invertMap();
|
return ( (MergeContext) anything ).invertMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
protected EntityCopyObserver createEntityCopyObserver() {
|
||||||
return new DefaultEntityCopyObserver();
|
return new EntityCopyNotAllowedObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +76,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
|
||||||
* @throws HibernateException
|
* @throws HibernateException
|
||||||
*/
|
*/
|
||||||
public void onMerge(MergeEvent event) throws HibernateException {
|
public void onMerge(MergeEvent event) throws HibernateException {
|
||||||
final EntityCopyObserver entityCopyObserver = createDetachedEntityCopyObserver();
|
final EntityCopyObserver entityCopyObserver = createEntityCopyObserver();
|
||||||
final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
|
final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
|
||||||
try {
|
try {
|
||||||
onMerge( event, mergeContext );
|
onMerge( event, mergeContext );
|
||||||
|
|
|
@ -26,73 +26,98 @@ package org.hibernate.event.internal;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
|
||||||
import org.hibernate.event.spi.EventSource;
|
import org.hibernate.event.spi.EventSource;
|
||||||
import org.hibernate.internal.CoreLogging;
|
import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.collections.IdentitySet;
|
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.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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An {@link EntityCopyObserver} implementation that allows multiple representations of
|
||||||
|
* the same persistent entity to be merged and provides logging of the entity copies that
|
||||||
|
* that are detected.
|
||||||
|
*
|
||||||
* @author Gail Badner
|
* @author Gail Badner
|
||||||
*/
|
*/
|
||||||
public class DefaultEntityCopyObserver implements EntityCopyObserver {
|
public class EntityCopyAllowedLoggedObserver extends EntityCopyAllowedObserver {
|
||||||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultEntityCopyObserver.class );
|
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EntityCopyAllowedLoggedObserver.class );
|
||||||
|
|
||||||
|
// Tracks the number of entity copies per entity name.
|
||||||
|
private Map<String, Integer> countsByEntityName;
|
||||||
|
|
||||||
// managedToMergeEntitiesXref is only maintained for DEBUG logging so that a "nice" message
|
// 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.
|
// 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
|
// If no entity copies have been detected, managedToMergeEntitiesXref will remain null;
|
||||||
// will remain null;
|
|
||||||
private Map<Object, Set<Object>> managedToMergeEntitiesXref = null;
|
private Map<Object, Set<Object>> managedToMergeEntitiesXref = null;
|
||||||
// key is the managed entity;
|
// key is the managed entity;
|
||||||
// value is the set of representations being merged corresponding to the same managed result.
|
// value is the set of representations being merged corresponding to the same managed result.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if DEBUG logging is enabled.
|
||||||
|
*
|
||||||
|
* @return true, if DEBUG logging is enabled.
|
||||||
|
*/
|
||||||
|
public static boolean isDebugLoggingEnabled() {
|
||||||
|
return LOG.isDebugEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void entityCopyDetected(
|
public void entityCopyDetected(
|
||||||
Object managedEntity,
|
Object managedEntity,
|
||||||
Object mergeEntity1,
|
Object mergeEntity1,
|
||||||
Object mergeEntity2,
|
Object mergeEntity2,
|
||||||
EventSource session) {
|
EventSource session) {
|
||||||
|
final String entityName = session.getEntityName( managedEntity );
|
||||||
LOG.trace(
|
LOG.trace(
|
||||||
String.format(
|
String.format(
|
||||||
"More than one representation of the same persistent entity being merged for: %s",
|
"More than one representation of the same persistent entity being merged for: %s",
|
||||||
MessageHelper.infoString(
|
MessageHelper.infoString(
|
||||||
session.getEntityName( managedEntity ),
|
entityName,
|
||||||
session.getIdentifier( managedEntity )
|
session.getIdentifier( managedEntity )
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
Set<Object> detachedEntitiesForManaged = null;
|
||||||
if ( LOG.isDebugEnabled() ) {
|
if ( managedToMergeEntitiesXref == null ) {
|
||||||
// managedToMergeEntitiesXref is only maintained for DEBUG logging
|
// This is the first time multiple representations have been found;
|
||||||
Set<Object> detachedEntitiesForManaged = null;
|
// instantiate managedToMergeEntitiesXref.
|
||||||
if ( managedToMergeEntitiesXref == null ) {
|
managedToMergeEntitiesXref = new IdentityHashMap<Object, Set<Object>>();
|
||||||
// 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 );
|
|
||||||
}
|
}
|
||||||
|
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; only count the
|
||||||
|
// entity copies that have not already been added to detachedEntitiesForManaged.
|
||||||
|
if ( detachedEntitiesForManaged.add( mergeEntity1 ) ) {
|
||||||
|
incrementEntityNameCount( entityName );
|
||||||
|
}
|
||||||
|
if ( detachedEntitiesForManaged.add( mergeEntity2 ) ) {
|
||||||
|
incrementEntityNameCount( entityName );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incrementEntityNameCount(String entityName) {
|
||||||
|
Integer countBeforeIncrement = 0;
|
||||||
|
if ( countsByEntityName == null ) {
|
||||||
|
// Use a TreeMap so counts can be logged by entity name in alphabetic order.
|
||||||
|
countsByEntityName = new TreeMap<String, Integer>();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
countBeforeIncrement = countsByEntityName.get( entityName );
|
||||||
|
if ( countBeforeIncrement == null ) {
|
||||||
|
// no entity copies for entityName detected previously.
|
||||||
|
countBeforeIncrement = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
countsByEntityName.put( entityName, countBeforeIncrement + 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
@ -100,16 +125,34 @@ public class DefaultEntityCopyObserver implements EntityCopyObserver {
|
||||||
managedToMergeEntitiesXref.clear();
|
managedToMergeEntitiesXref.clear();
|
||||||
managedToMergeEntitiesXref = null;
|
managedToMergeEntitiesXref = null;
|
||||||
}
|
}
|
||||||
|
if ( countsByEntityName != null ) {
|
||||||
|
countsByEntityName.clear();
|
||||||
|
countsByEntityName = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void topLevelMergeComplete(EventSource session) {
|
public void topLevelMergeComplete(EventSource session) {
|
||||||
if ( !LOG.isDebugEnabled() ) {
|
// Log the summary.
|
||||||
return;
|
if ( countsByEntityName != null ) {
|
||||||
|
for ( Map.Entry<String, Integer> entry : countsByEntityName.entrySet() ) {
|
||||||
|
final String entityName = entry.getKey();
|
||||||
|
final int count = entry.getValue();
|
||||||
|
LOG.debug(
|
||||||
|
String.format(
|
||||||
|
"%s entity copies merged: %d",
|
||||||
|
entityName,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG.debug( "No entity copies merged." );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( managedToMergeEntitiesXref != null && !managedToMergeEntitiesXref.isEmpty() ) {
|
if ( managedToMergeEntitiesXref != null ) {
|
||||||
for ( Map.Entry<Object,Set<Object>> entry : managedToMergeEntitiesXref.entrySet() ) {
|
for ( Map.Entry<Object,Set<Object>> entry : managedToMergeEntitiesXref.entrySet() ) {
|
||||||
Object managedEntity = entry.getKey();
|
Object managedEntity = entry.getKey();
|
||||||
Set mergeEntities = entry.getValue();
|
Set mergeEntities = entry.getValue();
|
|
@ -25,15 +25,17 @@
|
||||||
package org.hibernate.event.internal;
|
package org.hibernate.event.internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link org.hibernate.event.spi.MergeEventListener} that does not allow merging
|
* A {@link org.hibernate.event.spi.MergeEventListener} that allows merging
|
||||||
* multiple representations of the same persistent entity.
|
* multiple representations of the same persistent entity.
|
||||||
*
|
*
|
||||||
* @author Gail Badner
|
* @author Gail Badner
|
||||||
*/
|
*/
|
||||||
public class NoEntityCopiesMergeEventListener extends DefaultMergeEventListener {
|
public class EntityCopyAllowedMergeEventListener extends DefaultMergeEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
protected EntityCopyObserver createEntityCopyObserver() {
|
||||||
return new EntityCopyNotAllowedObserver();
|
return EntityCopyAllowedLoggedObserver.isDebugLoggingEnabled() ?
|
||||||
|
new EntityCopyAllowedLoggedObserver() :
|
||||||
|
new EntityCopyAllowedObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.event.spi.EventSource;
|
||||||
|
import org.hibernate.internal.CoreLogging;
|
||||||
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
|
import org.hibernate.internal.util.collections.IdentitySet;
|
||||||
|
import org.hibernate.pretty.MessageHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link EntityCopyObserver} implementation that allows multiple representations of
|
||||||
|
* the same persistent entity to be merged.
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
public class EntityCopyAllowedObserver implements EntityCopyObserver {
|
||||||
|
@Override
|
||||||
|
public void entityCopyDetected(
|
||||||
|
Object managedEntity,
|
||||||
|
Object mergeEntity1,
|
||||||
|
Object mergeEntity2,
|
||||||
|
EventSource session) {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void topLevelMergeComplete(EventSource session) {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,504 @@
|
||||||
|
/*
|
||||||
|
* 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.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.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2011/10/20 Unit test for code added in MergeContext for performance improvement.
|
||||||
|
*
|
||||||
|
* @author Wim Ockerman @ CISCO
|
||||||
|
*/
|
||||||
|
public class MergeContextTest extends BaseCoreFunctionalTestCase {
|
||||||
|
private EventSource session = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] { Simple.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
session = (EventSource) openSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
session.close();
|
||||||
|
session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByInvertMapping() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put(mergeEntity, managedEntity);
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
assertTrue( cache.containsKey( mergeEntity ) );
|
||||||
|
assertFalse( cache.containsKey( managedEntity ) );
|
||||||
|
assertTrue( cache.containsValue( managedEntity ) );
|
||||||
|
|
||||||
|
assertTrue( cache.invertMap().containsKey( managedEntity ) );
|
||||||
|
assertFalse( cache.invertMap().containsKey( mergeEntity ) );
|
||||||
|
assertTrue( cache.invertMap().containsValue( mergeEntity ) );
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 0 );
|
||||||
|
|
||||||
|
assertFalse(cache.containsKey(mergeEntity));
|
||||||
|
assertFalse(cache.invertMap().containsKey(managedEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByInvert() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put(mergeEntity, managedEntity);
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
assertTrue(cache.containsKey(mergeEntity));
|
||||||
|
assertFalse( cache.containsKey( managedEntity ) );
|
||||||
|
|
||||||
|
assertTrue( cache.invertMap().containsKey( managedEntity ) );
|
||||||
|
assertFalse( cache.invertMap().containsKey( mergeEntity ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByInvertUsingPutAll() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Map<Object,Object> input = new HashMap<Object,Object>();
|
||||||
|
Object mergeEntity1 = new Simple( 1 );
|
||||||
|
//
|
||||||
|
Object managedEntity1 = 1;
|
||||||
|
input.put(mergeEntity1, managedEntity1);
|
||||||
|
Object mergeEntity2 = new Simple( 3 );
|
||||||
|
Object managedEntity2 = 2;
|
||||||
|
input.put(mergeEntity2, managedEntity2);
|
||||||
|
cache.putAll(input);
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 2 );
|
||||||
|
|
||||||
|
assertTrue(cache.containsKey(mergeEntity1));
|
||||||
|
assertFalse(cache.containsKey(managedEntity1));
|
||||||
|
assertTrue(cache.containsKey(mergeEntity2));
|
||||||
|
assertFalse(cache.containsKey(managedEntity2));
|
||||||
|
|
||||||
|
assertTrue(cache.invertMap().containsKey(managedEntity1));
|
||||||
|
assertFalse(cache.invertMap().containsKey(mergeEntity1));
|
||||||
|
|
||||||
|
assertTrue(cache.invertMap().containsKey(managedEntity2));
|
||||||
|
assertFalse(cache.invertMap().containsKey(mergeEntity2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByInvertUsingPutWithSetOperatedOnArg() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put(mergeEntity, managedEntity, true);
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
assertTrue(cache.containsKey(mergeEntity));
|
||||||
|
assertFalse( cache.containsKey( managedEntity ) );
|
||||||
|
|
||||||
|
assertTrue( cache.invertMap().containsKey( managedEntity ) );
|
||||||
|
assertFalse( cache.invertMap().containsKey( mergeEntity ) );
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 0 );
|
||||||
|
|
||||||
|
cache.put(mergeEntity, managedEntity, false);
|
||||||
|
assertFalse( cache.isOperatedOn( mergeEntity ) );
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
assertTrue(cache.containsKey(mergeEntity));
|
||||||
|
assertFalse(cache.containsKey(managedEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByIterateEntrySet() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put( mergeEntity, managedEntity, true );
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
Iterator it = cache.entrySet().iterator();
|
||||||
|
assertTrue( it.hasNext() );
|
||||||
|
Map.Entry entry = ( Map.Entry ) it.next();
|
||||||
|
assertSame( mergeEntity, entry.getKey() );
|
||||||
|
assertSame( managedEntity, entry.getValue() );
|
||||||
|
assertFalse( it.hasNext() );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByModifyEntrySet() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put( mergeEntity, managedEntity, 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 testMergeToManagedEntityFillFollowedByModifyKeys() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put( mergeEntity, managedEntity, true );
|
||||||
|
|
||||||
|
Iterator it = cache.keySet().iterator();
|
||||||
|
try {
|
||||||
|
it.remove();
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache.keySet().remove( mergeEntity );
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
Object newmanagedEntity = new Simple( 3 );
|
||||||
|
try {
|
||||||
|
cache.keySet().add( newmanagedEntity );
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByModifyValues() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Object mergeEntity = new Simple( 1 );
|
||||||
|
Object managedEntity = new Simple( 2 );
|
||||||
|
|
||||||
|
cache.put( mergeEntity, managedEntity, true );
|
||||||
|
|
||||||
|
Iterator it = cache.values().iterator();
|
||||||
|
try {
|
||||||
|
it.remove();
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache.values().remove( managedEntity );
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
Object newmanagedEntity = new Simple( 3 );
|
||||||
|
try {
|
||||||
|
cache.values().add( newmanagedEntity );
|
||||||
|
fail( "should have thrown UnsupportedOperationException" );
|
||||||
|
}
|
||||||
|
catch ( UnsupportedOperationException ex ) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByModifyKeyOfEntrySetElement() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Simple mergeEntity = new Simple( 1 );
|
||||||
|
Simple managedEntity = new Simple( 0 );
|
||||||
|
cache.put(mergeEntity, managedEntity, true);
|
||||||
|
|
||||||
|
Map.Entry entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||||
|
( ( Simple ) entry.getKey() ).setValue( 2 );
|
||||||
|
assertEquals( 2, mergeEntity.getValue() );
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||||
|
assertSame( mergeEntity, entry.getKey() );
|
||||||
|
assertSame( managedEntity, entry.getValue() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeToManagedEntityFillFollowedByModifyValueOfEntrySetElement() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Simple mergeEntity = new Simple( 1 );
|
||||||
|
Simple managedEntity = new Simple( 0 );
|
||||||
|
cache.put(mergeEntity, managedEntity, true);
|
||||||
|
|
||||||
|
Map.Entry entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||||
|
( ( Simple ) entry.getValue() ).setValue( 2 );
|
||||||
|
assertEquals( 2, managedEntity.getValue() );
|
||||||
|
|
||||||
|
checkCacheConsistency( cache, 1 );
|
||||||
|
|
||||||
|
entry = (Map.Entry) cache.entrySet().iterator().next();
|
||||||
|
assertSame( mergeEntity, entry.getKey() );
|
||||||
|
assertSame( managedEntity, entry.getValue() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReplaceManagedEntity() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
Simple mergeEntity = new Simple( 1 );
|
||||||
|
Simple managedEntity = new Simple( 0 );
|
||||||
|
cache.put(mergeEntity, managedEntity);
|
||||||
|
|
||||||
|
Simple managedEntityNew = new Simple( 0 );
|
||||||
|
try {
|
||||||
|
cache.put( mergeEntity, managedEntityNew );
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException ex) {
|
||||||
|
// expected; cannot replace the managed entity result for a particular merge entity.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManagedEntityAssociatedWithNewAndExistingMergeEntities() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Simple mergeEntity = new Simple( 1 );
|
||||||
|
Simple managedEntity = new Simple( 0 );
|
||||||
|
cache.put(mergeEntity, managedEntity);
|
||||||
|
cache.put( new Simple( 1 ), managedEntity );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManagedAssociatedWith2ExistingMergeEntities() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
|
||||||
|
session.getTransaction().begin();
|
||||||
|
Simple mergeEntity1 = new Simple( 1 );
|
||||||
|
session.persist( mergeEntity1 );
|
||||||
|
Simple managedEntity1 = new Simple( 1 );
|
||||||
|
cache.put( mergeEntity1, managedEntity1 );
|
||||||
|
Simple managedEntity2 = new Simple( 2 );
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache.put( mergeEntity1, managedEntity2 );
|
||||||
|
fail( "should have thrown IllegalArgumentException");
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException ex ) {
|
||||||
|
// expected; cannot change managed entity associated with a merge entity
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
session.getTransaction().rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveNonExistingEntity() {
|
||||||
|
MergeContext cache = new MergeContext( session, new DoNothingEntityCopyObserver() );
|
||||||
|
try {
|
||||||
|
cache.remove( new Simple( 1 ) );
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException ex) {
|
||||||
|
// expected; remove is not supported.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCacheConsistency(MergeContext 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Simple{" +
|
||||||
|
"value=" + value +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DoNothingEntityCopyObserver implements EntityCopyObserver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entityCopyDetected(Object managedEntity, Object mergeEntity1, Object mergeEntity2, EventSource session) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void topLevelMergeComplete(EventSource session) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExceptionThrowingEntityCopyObserver implements EntityCopyObserver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void entityCopyDetected(Object managedEntity, Object mergeEntity1, Object mergeEntity2, EventSource session) {
|
||||||
|
throw new IllegalStateException( "Entity copies not allowed." );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void topLevelMergeComplete(EventSource session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,4 +84,27 @@ public class Category {
|
||||||
", version=" + version +
|
", version=" + version +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( o == null || getClass() != o.getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Category category = (Category) o;
|
||||||
|
|
||||||
|
if ( name != null ? !name.equals( category.name ) : category.name != null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return name != null ? name.hashCode() : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
import org.hibernate.event.internal.NoEntityCopiesMergeEventListener;
|
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
|
||||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
import org.hibernate.event.spi.EventType;
|
import org.hibernate.event.spi.EventType;
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
@ -41,7 +41,7 @@ import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests merging multiple detached representations of the same entity using
|
* Tests merging multiple detached representations of the same entity using
|
||||||
* a MergeEventListener that does not allow this.
|
* a the default MergeEventListener (that does not allow this).
|
||||||
*
|
*
|
||||||
* @author Gail Badner
|
* @author Gail Badner
|
||||||
*/
|
*/
|
||||||
|
@ -54,11 +54,6 @@ public class MergeMultipleEntityRepresentationsNotAllowedTest extends BaseCoreFu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void afterSessionFactoryBuilt() {
|
|
||||||
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
|
||||||
registry.setListeners( EventType.MERGE, new NoEntityCopiesMergeEventListener() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCascadeFromDetachedToNonDirtyRepresentations() {
|
public void testCascadeFromDetachedToNonDirtyRepresentations() {
|
||||||
Item item1 = new Item();
|
Item item1 = new Item();
|
||||||
|
|
|
@ -30,6 +30,9 @@ import org.junit.Test;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
import org.hibernate.testing.FailureExpected;
|
import org.hibernate.testing.FailureExpected;
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
@ -54,6 +57,11 @@ public class MergeMultipleEntityRepresentationsOrphanDeleteTest extends BaseCore
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void afterSessionFactoryBuilt() {
|
||||||
|
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
||||||
|
registry.setListeners( EventType.MERGE, new EntityCopyAllowedMergeEventListener() );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@FailureExpected( jiraKey = "HHH-9240" )
|
@FailureExpected( jiraKey = "HHH-9240" )
|
||||||
public void testTopLevelUnidirOneToManyBackrefWithNewElement() {
|
public void testTopLevelUnidirOneToManyBackrefWithNewElement() {
|
||||||
|
|
|
@ -31,6 +31,9 @@ import org.hibernate.Hibernate;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.StaleObjectStateException;
|
import org.hibernate.StaleObjectStateException;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
|
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
import org.hibernate.testing.FailureExpected;
|
import org.hibernate.testing.FailureExpected;
|
||||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
|
||||||
|
@ -54,6 +57,11 @@ public class MergeMultipleEntityRepresentationsTest extends BaseCoreFunctionalTe
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void afterSessionFactoryBuilt() {
|
||||||
|
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
||||||
|
registry.setListeners( EventType.MERGE, new EntityCopyAllowedMergeEventListener() );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNestedDiffBasicProperty() {
|
public void testNestedDiffBasicProperty() {
|
||||||
Item item1 = new Item();
|
Item item1 = new Item();
|
||||||
|
@ -564,6 +572,79 @@ public class MergeMultipleEntityRepresentationsTest extends BaseCoreFunctionalTe
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCascadeFromDetachedToGT2DirtyRepresentations() {
|
||||||
|
Item item1 = new Item();
|
||||||
|
item1.setName( "item1" );
|
||||||
|
Category category1 = new Category();
|
||||||
|
category1.setName( "category1" );
|
||||||
|
item1.setCategory( category1 );
|
||||||
|
|
||||||
|
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 and item1_1 are unmodified representations of the same persistent entity.
|
||||||
|
assertFalse( item1 == item1_1 );
|
||||||
|
assertTrue( item1.equals( item1_1 ) );
|
||||||
|
|
||||||
|
// Get another representation of the same Item from a different session.
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
Item item1_2 = (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_2 );
|
||||||
|
assertTrue( item1.equals( item1_2) );
|
||||||
|
|
||||||
|
item1_1.setName( "item1_1" );
|
||||||
|
item1_2.setName( "item1_2" );
|
||||||
|
|
||||||
|
// Update hoarder (detached) to references both representations.
|
||||||
|
item1.getCategory().setExampleItem( item1_2 );
|
||||||
|
hoarder.getItems().add( item1 );
|
||||||
|
hoarder.setFavoriteItem( item1_1 );
|
||||||
|
hoarder.getFavoriteItem().getCategory();
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
tx = s.beginTransaction();
|
||||||
|
hoarder = (Hoarder) s.merge( hoarder );
|
||||||
|
assertEquals( 1, hoarder.getItems().size() );
|
||||||
|
assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() );
|
||||||
|
assertSame( hoarder.getFavoriteItem(), hoarder.getFavoriteItem().getCategory().getExampleItem() );
|
||||||
|
assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() );
|
||||||
|
assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() );
|
||||||
|
assertEquals( item1.getName(), hoarder.getFavoriteItem().getName() );
|
||||||
|
tx.commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
tx = s.beginTransaction();
|
||||||
|
hoarder = (Hoarder) s.merge( hoarder );
|
||||||
|
assertEquals( 1, hoarder.getItems().size() );
|
||||||
|
assertSame( hoarder.getFavoriteItem(), hoarder.getItems().iterator().next() );
|
||||||
|
assertSame( hoarder.getFavoriteItem(), hoarder.getFavoriteItem().getCategory().getExampleItem() );
|
||||||
|
assertEquals( item1.getId(), hoarder.getFavoriteItem().getId() );
|
||||||
|
assertEquals( item1.getCategory(), hoarder.getFavoriteItem().getCategory() );
|
||||||
|
tx.commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTopLevelEntityNewerThanNested() {
|
public void testTopLevelEntityNewerThanNested() {
|
||||||
Item item = new Item();
|
Item item = new Item();
|
||||||
|
@ -1111,19 +1192,27 @@ public class MergeMultipleEntityRepresentationsTest extends BaseCoreFunctionalTe
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( Category category : (List<Category>) s.createQuery( "from Category" ).list() ) {
|
for ( Category category : (List<Category>) s.createQuery( "from Category" ).list() ) {
|
||||||
if ( category.getExampleItem() != null ) {
|
Item exampleItem = category.getExampleItem();
|
||||||
|
if ( exampleItem != null ) {
|
||||||
category.setExampleItem( null );
|
category.setExampleItem( null );
|
||||||
|
exampleItem.setCategory( null );
|
||||||
s.delete( category );
|
s.delete( category );
|
||||||
|
s.delete (exampleItem );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( Item item : (List<Item>) s.createQuery( "from Item" ).list() ) {
|
for ( Item item : (List<Item>) s.createQuery( "from Item" ).list() ) {
|
||||||
|
Category category = item.getCategory();
|
||||||
item.setCategory( null );
|
item.setCategory( null );
|
||||||
|
if ( category != null ) {
|
||||||
|
category.setExampleItem( null );
|
||||||
|
}
|
||||||
s.delete( item );
|
s.delete( item );
|
||||||
}
|
}
|
||||||
|
|
||||||
s.createQuery( "delete from Item" ).executeUpdate();
|
s.createQuery( "delete from Item" ).executeUpdate();
|
||||||
|
|
||||||
|
|
||||||
s.getTransaction().commit();
|
s.getTransaction().commit();
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,5 +58,7 @@ log4j.logger.org.hibernate.loader.plan2.exec.spi.EntityLoadQueryDetails=debug
|
||||||
|
|
||||||
log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info
|
log4j.logger.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info
|
||||||
|
|
||||||
### enable the following line to log multiple entity representations being merged for a persistent entity.
|
### When entity copy merge functionality is enabled, the following will provide
|
||||||
#log4j.logger.org.hibernate.event.internal.DefaultEntityCopyObserver=debug
|
### information about merged entity copies.
|
||||||
|
#log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=debug
|
||||||
|
|
||||||
|
|
|
@ -23,20 +23,23 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.jpa.event.internal.core;
|
package org.hibernate.jpa.event.internal.core;
|
||||||
|
|
||||||
import org.hibernate.event.internal.EntityCopyNotAllowedObserver;
|
import org.hibernate.event.internal.EntityCopyAllowedLoggedObserver;
|
||||||
|
import org.hibernate.event.internal.EntityCopyAllowedObserver;
|
||||||
import org.hibernate.event.internal.EntityCopyObserver;
|
import org.hibernate.event.internal.EntityCopyObserver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides {@link JpaMergeEventListener} to disallow merging multiple representations
|
* Overrides {@link JpaMergeEventListener} that allows merging multiple representations
|
||||||
* of the same persistent entity.
|
* of the same persistent entity.
|
||||||
*
|
*
|
||||||
* @author Gail Badner
|
* @author Gail Badner
|
||||||
*/
|
*/
|
||||||
public class JpaNoEntityCopiesMergeEventListener extends JpaMergeEventListener {
|
public class JpaEntityCopyAllowedMergeEventListener extends JpaMergeEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EntityCopyObserver createDetachedEntityCopyObserver() {
|
protected EntityCopyObserver createEntityCopyObserver() {
|
||||||
return new EntityCopyNotAllowedObserver();
|
return EntityCopyAllowedLoggedObserver.isDebugLoggingEnabled() ?
|
||||||
|
new EntityCopyAllowedLoggedObserver() :
|
||||||
|
new EntityCopyAllowedObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -38,5 +38,7 @@ log4j.logger.org.hibernate.tool.hbm2ddl=debug
|
||||||
### 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.
|
### When entity copy merge functionality is enabled, the following will provide
|
||||||
#log4j.logger.org.hibernate.event.internal.DefaultEntityCopyObserver=debug
|
### information about merged entity copies.
|
||||||
|
#log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=debug
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue