HHH-10795 : StatefulPersistenceContext.entityEntryContext does not work properly for ManagedEntity associated with a different StatefulPersistenceContext

This commit is contained in:
Gail Badner 2016-07-22 14:14:16 -07:00
parent cddb221288
commit 56b22aeaac
7 changed files with 504 additions and 84 deletions

View File

@ -6,12 +6,14 @@
*/ */
package org.hibernate.engine.internal; package org.hibernate.engine.internal;
import org.hibernate.HibernateException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.jboss.logging.Logger; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -37,7 +39,11 @@ import java.util.Map;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class EntityEntryContext { public class EntityEntryContext {
private static final Logger log = Logger.getLogger( EntityEntryContext.class ); private static final CoreMessageLogger log = CoreLogging.messageLogger( EntityEntryContext.class );
private transient PersistenceContext persistenceContext;
private transient IdentityHashMap<ManagedEntity,ImmutableManagedEntityHolder> immutableManagedEntityXref;
private transient ManagedEntity head; private transient ManagedEntity head;
private transient ManagedEntity tail; private transient ManagedEntity tail;
@ -52,7 +58,8 @@ public class EntityEntryContext {
/** /**
* Constructs a EntityEntryContext * Constructs a EntityEntryContext
*/ */
public EntityEntryContext() { public EntityEntryContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
} }
/** /**
@ -69,33 +76,47 @@ public class EntityEntryContext {
// any addition (even the double one described above) should invalidate the cross-ref array // any addition (even the double one described above) should invalidate the cross-ref array
dirty = true; dirty = true;
// determine the appropriate ManagedEntity instance to use based on whether the entity is enhanced or not. assert AbstractEntityEntry.class.isInstance( entityEntry );
// also track whether the entity was already associated with the context
final ManagedEntity managedEntity; // We only need to check a mutable EntityEntry is associated with the same PersistenceContext.
final boolean alreadyAssociated; // Immutable EntityEntry can be associated with multiple PersistenceContexts, so no need to check.
// ImmutableEntityEntry#getPersistenceContext() throws an exception (HHH-10251).
if ( entityEntry.getPersister().isMutable() ) {
assert AbstractEntityEntry.class.cast( entityEntry ).getPersistenceContext() == persistenceContext;
}
// Determine the appropriate ManagedEntity instance to use based on whether the entity is enhanced or not.
// Throw an exception if entity is a mutable ManagedEntity that is associated with a different
// PersistenceContext.
ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
final boolean alreadyAssociated = managedEntity != null;
if ( !alreadyAssociated ) {
if ( ManagedEntity.class.isInstance( entity ) ) { if ( ManagedEntity.class.isInstance( entity ) ) {
if ( entityEntry.getPersister().isMutable() ) {
managedEntity = (ManagedEntity) entity; managedEntity = (ManagedEntity) entity;
alreadyAssociated = managedEntity.$$_hibernate_getEntityEntry() != null; // We know that managedEntity is not associated with the same PersistenceContext.
// Check if managedEntity is associated with a different PersistenceContext.
checkNotAssociatedWithOtherPersistenceContextIfMutable( managedEntity );
}
else {
// Create a holder for PersistenceContext-related data.
managedEntity = new ImmutableManagedEntityHolder( (ManagedEntity) entity );
if ( immutableManagedEntityXref == null ) {
immutableManagedEntityXref = new IdentityHashMap<ManagedEntity, ImmutableManagedEntityHolder>();
}
immutableManagedEntityXref.put(
(ManagedEntity) entity,
(ImmutableManagedEntityHolder) managedEntity
);
}
} }
else { else {
ManagedEntity wrapper = null;
if ( nonEnhancedEntityXref == null ) { if ( nonEnhancedEntityXref == null ) {
nonEnhancedEntityXref = new IdentityHashMap<Object, ManagedEntity>(); nonEnhancedEntityXref = new IdentityHashMap<Object, ManagedEntity>();
} }
else { managedEntity = new ManagedEntityImpl( entity );
wrapper = nonEnhancedEntityXref.get( entity ); nonEnhancedEntityXref.put( entity, managedEntity );
} }
if ( wrapper == null ) {
wrapper = new ManagedEntityImpl( entity );
nonEnhancedEntityXref.put( entity, wrapper );
alreadyAssociated = false;
}
else {
alreadyAssociated = true;
}
managedEntity = wrapper;
} }
// associate the EntityEntry with the entity // associate the EntityEntry with the entity
@ -106,9 +127,14 @@ public class EntityEntryContext {
return; return;
} }
// TODO: can dirty be set to true here?
// finally, set up linking and count // finally, set up linking and count
if ( tail == null ) { if ( tail == null ) {
assert head == null; assert head == null;
// Protect against stale data in the ManagedEntity and nullify previous/next references.
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
managedEntity.$$_hibernate_setNextManagedEntity( null );
head = managedEntity; head = managedEntity;
tail = head; tail = head;
count = 1; count = 1;
@ -116,11 +142,65 @@ public class EntityEntryContext {
else { else {
tail.$$_hibernate_setNextManagedEntity( managedEntity ); tail.$$_hibernate_setNextManagedEntity( managedEntity );
managedEntity.$$_hibernate_setPreviousManagedEntity( tail ); managedEntity.$$_hibernate_setPreviousManagedEntity( tail );
// Protect against stale data left in the ManagedEntity nullify next reference.
managedEntity.$$_hibernate_setNextManagedEntity( null );
tail = managedEntity; tail = managedEntity;
count++; count++;
} }
} }
private ManagedEntity getAssociatedManagedEntity(Object entity) {
if ( ManagedEntity.class.isInstance( entity ) ) {
final ManagedEntity managedEntity = (ManagedEntity) entity;
if ( managedEntity.$$_hibernate_getEntityEntry() == null ) {
// it is not associated
return null;
}
final AbstractEntityEntry entityEntry = (AbstractEntityEntry) managedEntity.$$_hibernate_getEntityEntry();
if ( entityEntry.getPersister().isMutable() ) {
return entityEntry.getPersistenceContext() == persistenceContext
? managedEntity // it is associated
: null;
}
else {
// if managedEntity is associated with this EntityEntryContext, then
// it will have an entry in immutableManagedEntityXref and its
// holder will be returned.
return immutableManagedEntityXref != null
? immutableManagedEntityXref.get( managedEntity )
: null;
}
}
else {
return nonEnhancedEntityXref != null
? nonEnhancedEntityXref.get( entity )
: null;
}
}
private void checkNotAssociatedWithOtherPersistenceContextIfMutable(ManagedEntity managedEntity) {
// we only have to check mutable managedEntity
final AbstractEntityEntry entityEntry = (AbstractEntityEntry) managedEntity.$$_hibernate_getEntityEntry();
if ( entityEntry == null ||
!entityEntry.getPersister().isMutable() ||
entityEntry.getPersistenceContext() == null ||
entityEntry.getPersistenceContext() == persistenceContext ) {
return;
}
if ( entityEntry.getPersistenceContext().getSession().isOpen() ) {
// NOTE: otherPersistenceContext may be operating on the entityEntry in a different thread.
// it is not safe to associate entityEntry with this EntityEntryContext.
throw new HibernateException(
"Illegal attempt to associate a ManagedEntity with two open persistence contexts. " + entityEntry
);
}
else {
// otherPersistenceContext is associated with a closed PersistenceContext
log.stalePersistenceContextInEntityEntry( entityEntry.toString() );
}
}
/** /**
* Does this entity exist in this context, associated with an EntityEntry? * Does this entity exist in this context, associated with an EntityEntry?
* *
@ -140,17 +220,8 @@ public class EntityEntryContext {
* @return The associated EntityEntry * @return The associated EntityEntry
*/ */
public EntityEntry getEntityEntry(Object entity) { public EntityEntry getEntityEntry(Object entity) {
// essentially resolve the entity to a ManagedEntity... // locate a ManagedEntity for the entity, but only if it is associated with the same PersistenceContext.
final ManagedEntity managedEntity; final ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity;
}
else if ( nonEnhancedEntityXref == null ) {
managedEntity = null;
}
else {
managedEntity = nonEnhancedEntityXref.get( entity );
}
// and get/return the EntityEntry from the ManagedEntry // and get/return the EntityEntry from the ManagedEntry
return managedEntity == null return managedEntity == null
@ -166,27 +237,24 @@ public class EntityEntryContext {
* @return Tjee EntityEntry * @return Tjee EntityEntry
*/ */
public EntityEntry removeEntityEntry(Object entity) { public EntityEntry removeEntityEntry(Object entity) {
dirty = true; // locate a ManagedEntity for the entity, but only if it is associated with the same PersistenceContext.
// no need to check if the entity is a ManagedEntity that is associated with a different PersistenceContext
// again, resolve the entity to a ManagedEntity (which may not be possible for non-enhanced)... final ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
final ManagedEntity managedEntity; if ( managedEntity == null ) {
if ( ManagedEntity.class.isInstance( entity ) ) { // not associated with this EntityEntryContext, so nothing to do.
managedEntity = (ManagedEntity) entity;
}
else if ( nonEnhancedEntityXref == null ) {
managedEntity = null;
}
else {
managedEntity = nonEnhancedEntityXref.remove( entity );
}
// if we could not resolve it, just return (it was not associated with this context)
if ( managedEntity == null || managedEntity.$$_hibernate_getEntityEntry() == null) {
return null; return null;
} }
// TODO: should dirty be set to true only if managedEntity is associtated with this context dirty = true;
// (instead of setting it at the top of this method)?
if ( ImmutableManagedEntityHolder.class.isInstance( managedEntity ) ) {
assert entity == ( (ImmutableManagedEntityHolder) managedEntity ).managedEntity;
immutableManagedEntityXref.remove( (ManagedEntity) entity );
}
else if ( !ManagedEntity.class.isInstance( entity ) ) {
nonEnhancedEntityXref.remove( entity );
}
// prepare for re-linking... // prepare for re-linking...
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity(); final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
@ -228,10 +296,7 @@ public class EntityEntryContext {
// finally clean out the ManagedEntity and return the associated EntityEntry // finally clean out the ManagedEntity and return the associated EntityEntry
final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry(); final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
// need to think about implications for memory leaks here if we don't removed reference to EntityEntry
if( canClearEntityEntryReference(managedEntity) ){
managedEntity.$$_hibernate_setEntityEntry( null ); managedEntity.$$_hibernate_setEntityEntry( null );
}
return theEntityEntry; return theEntityEntry;
} }
@ -270,9 +335,7 @@ public class EntityEntryContext {
while ( node != null ) { while ( node != null ) {
final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity(); final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity();
if( canClearEntityEntryReference(node) ){
node.$$_hibernate_setEntityEntry( null ); node.$$_hibernate_setEntityEntry( null );
}
node.$$_hibernate_setPreviousManagedEntity( null ); node.$$_hibernate_setPreviousManagedEntity( null );
node.$$_hibernate_setNextManagedEntity( null ); node.$$_hibernate_setNextManagedEntity( null );
@ -280,6 +343,10 @@ public class EntityEntryContext {
node = nextNode; node = nextNode;
} }
if ( immutableManagedEntityXref != null ) {
immutableManagedEntityXref.clear();
}
if ( nonEnhancedEntityXref != null ) { if ( nonEnhancedEntityXref != null ) {
nonEnhancedEntityXref.clear(); nonEnhancedEntityXref.clear();
} }
@ -351,7 +418,7 @@ public class EntityEntryContext {
final int count = ois.readInt(); final int count = ois.readInt();
log.tracef( "Starting deserialization of [%s] EntityEntry entries", count ); log.tracef( "Starting deserialization of [%s] EntityEntry entries", count );
final EntityEntryContext context = new EntityEntryContext(); final EntityEntryContext context = new EntityEntryContext( rtn );
context.count = count; context.count = count;
context.dirty = true; context.dirty = true;
@ -376,8 +443,22 @@ public class EntityEntryContext {
final ManagedEntity managedEntity; final ManagedEntity managedEntity;
if ( isEnhanced ) { if ( isEnhanced ) {
if ( entry.getPersister().isMutable() ) {
managedEntity = (ManagedEntity) entity; managedEntity = (ManagedEntity) entity;
} }
else {
managedEntity = new ImmutableManagedEntityHolder( (ManagedEntity) entity );
if ( context.immutableManagedEntityXref == null ) {
context.immutableManagedEntityXref =
new IdentityHashMap<ManagedEntity, ImmutableManagedEntityHolder>();
}
context.immutableManagedEntityXref.put(
(ManagedEntity) entity,
(ImmutableManagedEntityHolder) managedEntity
);
}
}
else { else {
managedEntity = new ManagedEntityImpl( entity ); managedEntity = new ManagedEntityImpl( entity );
if ( context.nonEnhancedEntityXref == null ) { if ( context.nonEnhancedEntityXref == null ) {
@ -431,25 +512,6 @@ public class EntityEntryContext {
return count; return count;
} }
/*
Check instance type of EntityEntry and if type is ImmutableEntityEntry, check to see if entity is referenced cached in the second level cache
*/
private boolean canClearEntityEntryReference(ManagedEntity managedEntity){
if( managedEntity.$$_hibernate_getEntityEntry() == null ) {
return true;
}
if( !(managedEntity.$$_hibernate_getEntityEntry() instanceof ImmutableEntityEntry) ) {
return true;
}
else if( managedEntity.$$_hibernate_getEntityEntry().getPersister().canUseReferenceCacheEntries() ) {
return false;
}
return true;
}
/** /**
* The wrapper for entity classes which do not implement ManagedEntity * The wrapper for entity classes which do not implement ManagedEntity
*/ */
@ -499,6 +561,96 @@ public class EntityEntryContext {
} }
} }
private static class ImmutableManagedEntityHolder implements ManagedEntity {
private ManagedEntity managedEntity;
private ManagedEntity previous;
private ManagedEntity next;
public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) {
this.managedEntity = immutableManagedEntity;
}
@Override
public Object $$_hibernate_getEntityInstance() {
return managedEntity.$$_hibernate_getEntityInstance();
}
@Override
public EntityEntry $$_hibernate_getEntityEntry() {
return managedEntity.$$_hibernate_getEntityEntry();
}
@Override
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
// need to think about implications for memory leaks here if we don't removed reference to EntityEntry
if ( entityEntry == null ) {
if ( canClearEntityEntryReference() ) {
managedEntity.$$_hibernate_setEntityEntry( null );
}
// otherwise, do nothing.
}
else {
// TODO: we may want to do something different here if
// managedEntity is in the process of being deleted.
// An immutable ManagedEntity can be associated with
// multiple PersistenceContexts. Changing the status
// to DELETED probably should not happen directly
// in the ManagedEntity because that would affect all
// PersistenceContexts to which the ManagedEntity is
// associated.
managedEntity.$$_hibernate_setEntityEntry( entityEntry );
}
}
@Override
public ManagedEntity $$_hibernate_getPreviousManagedEntity() {
// previous reference cannot be stored in an immutable ManagedEntity;
// previous reference is maintained by this ManagedEntityHolder.
return previous;
}
@Override
public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) {
// previous reference cannot be stored in an immutable ManagedEntity;
// previous reference is maintained by this ManagedEntityHolder.
this.previous = previous;
}
@Override
public ManagedEntity $$_hibernate_getNextManagedEntity() {
// next reference cannot be stored in an immutable ManagedEntity;
// next reference is maintained by this ManagedEntityHolder.
return next;
}
@Override
public void $$_hibernate_setNextManagedEntity(ManagedEntity next) {
// next reference cannot be stored in an immutable ManagedEntity;
// next reference is maintained by this ManagedEntityHolder.
this.next = next;
}
/*
Check instance type of EntityEntry and if type is ImmutableEntityEntry, check to see if entity is referenced cached in the second level cache
*/
private boolean canClearEntityEntryReference(){
if( managedEntity.$$_hibernate_getEntityEntry() == null ) {
return true;
}
if( !(managedEntity.$$_hibernate_getEntityEntry() instanceof ImmutableEntityEntry) ) {
return true;
}
else if( managedEntity.$$_hibernate_getEntityEntry().getPersister().canUseReferenceCacheEntries() ) {
return false;
}
return true;
}
}
/** /**
* Used in building the {@link #reentrantSafeEntityEntries()} entries * Used in building the {@link #reentrantSafeEntityEntries()} entries
*/ */

View File

@ -162,7 +162,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
); );
entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE ); entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE );
entityEntryContext = new EntityEntryContext(); entityEntryContext = new EntityEntryContext( this );
// entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); // entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
parentsByChild = new IdentityHashMap<>( INIT_COLL_SIZE ); parentsByChild = new IdentityHashMap<>( INIT_COLL_SIZE );

View File

@ -1751,4 +1751,8 @@ public interface CoreMessageLogger extends BasicLogger {
id = 479 id = 479
) )
String collectionNotProcessedByFlush(String role); String collectionNotProcessedByFlush(String role);
@LogMessage(level = WARN)
@Message(value = "A ManagedEntity was associated with a stale PersistenceContext. A ManagedEntity may only be associated with one PersistenceContext at a time; %s", id = 480)
void stalePersistenceContextInEntityEntry(String msg);
} }

View File

@ -8,6 +8,7 @@ package org.hibernate.test.bytecode.enhancement;
import javassist.CtClass; import javassist.CtClass;
import org.hibernate.test.bytecode.enhancement.otherentityentrycontext.OtherEntityEntryContextTestTask;
import org.hibernate.testing.FailureExpected; import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext; import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
@ -81,6 +82,11 @@ public class EnhancerTest extends BaseUnitTestCase {
EnhancerTestUtils.runEnhancerTestTask( EvictionTestTask.class ); EnhancerTestUtils.runEnhancerTestTask( EvictionTestTask.class );
} }
@Test
public void testOtherPersistenceContext() {
EnhancerTestUtils.runEnhancerTestTask( OtherEntityEntryContextTestTask.class );
}
@Test @Test
public void testAssociation() { public void testAssociation() {
EnhancerTestUtils.runEnhancerTestTask( OneToOneAssociationTestTask.class ); EnhancerTestUtils.runEnhancerTestTask( OneToOneAssociationTestTask.class );

View File

@ -0,0 +1,100 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.bytecode.enhancement.otherentityentrycontext;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.test.bytecode.enhancement.AbstractEnhancerTestTask;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* This task tests ManagedEntity objects that are already associated with a different PersistenceContext.
*
* @author Gail Badner
*/
public class OtherEntityEntryContextTestTask extends AbstractEnhancerTestTask {
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {Parent.class};
}
public void prepare() {
Configuration cfg = new Configuration();
cfg.setProperty( Environment.ENABLE_LAZY_LOAD_NO_TRANS, "true" );
cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "false" );
super.prepare( cfg );
// Create a Parent
Session s = getFactory().openSession();
s.beginTransaction();
s.persist( new Parent( 1, "first" ) );
s.getTransaction().commit();
s.close();
}
public void execute() {
Session s1 = getFactory().openSession();
s1.beginTransaction();
Parent p = (Parent) s1.get( Parent.class, 1 );
assertTrue( ManagedEntity.class.isInstance( p ) );
p.setName( "second" );
assertTrue( s1.contains( p ) );
// open another session and evict p from the new session
Session s2 = getFactory().openSession();
s2.beginTransaction();
// s2 should contains no entities
assertFalse( s2.contains( p ) );
// evict should do nothing, since p is not associated with s2
s2.evict( p );
assertFalse( s2.contains( p ) );
assertNull( ( (SharedSessionContractImplementor) s2 ).getPersistenceContext().getEntry( p ) );
try {
s2.update( p );
fail( "should have failed because p is already associated with a PersistenceContext that is still open." );
}
catch (HibernateException expected) {
// expected
s2.getTransaction().rollback();
s2.close();
}
s1.getTransaction().commit();
s1.close();
p.setName( "third" );
s1 = getFactory().openSession();
s1.getTransaction().begin();
s1.update( p );
assertTrue( s1.contains( p ) );
s1.evict( p );
assertFalse( s1.contains( p ) );
p = s1.get( Parent.class, p.getId() );
assertEquals( "second", p.getName() );
s1.getTransaction().commit();
s1.close();
}
protected void cleanup() {
}
}

View File

@ -0,0 +1,37 @@
package org.hibernate.test.bytecode.enhancement.otherentityentrycontext;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* Created by barreiro on 12/9/15.
*/
@Entity
public class Parent {
private Integer id;
private String name;
public Parent() {
}
public Parent(Integer id, String name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -15,7 +15,10 @@ import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
@ -24,8 +27,11 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Transient; import javax.persistence.Transient;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -91,6 +97,121 @@ public class ByteCodeEnhancedImmutableReferenceCacheTest extends BaseCoreFunctio
s.close(); s.close();
} }
@Test
@TestForIssue( jiraKey = "HHH-10795")
public void testAssociatedWithMultiplePersistenceContexts() {
MyEnhancedReferenceData myReferenceData = new MyEnhancedReferenceData( 1, "first item", "abc" );
MyEnhancedReferenceData myOtherReferenceData = new MyEnhancedReferenceData( 2, "second item", "def" );
// save a reference in one session
Session s1 = openSession();
s1.beginTransaction();
s1.save( myReferenceData );
s1.save( myOtherReferenceData );
s1.getTransaction().commit();
s1.close();
assertNotNull( myReferenceData.$$_hibernate_getEntityEntry() );
assertNotNull( myOtherReferenceData.$$_hibernate_getEntityEntry() );
// now associate myReferenceData with 2 sessions
s1 = openSession();
s1.beginTransaction();
myReferenceData = s1.get( MyEnhancedReferenceData.class, myReferenceData.getId() );
myOtherReferenceData = s1.get( MyEnhancedReferenceData.class, myOtherReferenceData.getId() );
assertTrue( s1.contains( myReferenceData ) );
assertTrue( s1.contains( myOtherReferenceData ) );
// prev/next references should be null; entityEntry should be non-null;
assertNull( myReferenceData.$$_hibernate_getPreviousManagedEntity() );
assertNull( myReferenceData.$$_hibernate_getNextManagedEntity() );
assertNull( myOtherReferenceData.$$_hibernate_getPreviousManagedEntity() );
assertNull( myOtherReferenceData.$$_hibernate_getNextManagedEntity() );
assertSame(
myReferenceData.$$_hibernate_getEntityEntry(),
( (SharedSessionContractImplementor) s1 ).getPersistenceContext().getEntry( myReferenceData )
);
assertSame(
myOtherReferenceData.$$_hibernate_getEntityEntry(),
( (SharedSessionContractImplementor) s1 ).getPersistenceContext().getEntry( myOtherReferenceData )
);
Session s2 = openSession();
s2.beginTransaction();
// s2 should contains no entities
assertFalse( s2.contains( myReferenceData ) );
assertFalse( s2.contains( myOtherReferenceData ) );
assertNull( ( (SharedSessionContractImplementor) s2 ).getPersistenceContext().getEntry( myReferenceData ) );
assertNull( ( (SharedSessionContractImplementor) s2 ).getPersistenceContext().getEntry( myOtherReferenceData ) );
// evict should do nothing, since p is not associated with s2
s2.evict( myReferenceData );
s2.evict( myOtherReferenceData );
assertSame( myReferenceData, s2.get( MyEnhancedReferenceData.class, myReferenceData.getId() ) );
assertSame( myOtherReferenceData, s2.get( MyEnhancedReferenceData.class, myOtherReferenceData.getId() ) );
assertTrue( s2.contains( myReferenceData ) );
assertTrue( s2.contains( myOtherReferenceData ) );
// still associated with s1
assertTrue( s1.contains( myReferenceData ) );
assertTrue( s1.contains( myOtherReferenceData ) );
s2.evict( myReferenceData );
s2.evict( myOtherReferenceData );
assertFalse( s2.contains( myReferenceData ) );
assertFalse( s2.contains( myOtherReferenceData ) );
s2.getTransaction().commit();
s2.close();
// still associated with s1
assertTrue( s1.contains( myReferenceData ) );
assertTrue( s1.contains( myOtherReferenceData ) );
s1.clear();
assertFalse( s1.contains( myReferenceData ) );
assertFalse( s1.contains( myOtherReferenceData ) );
s1.close();
// EntityEntry should still be set
assertNotNull( myReferenceData.$$_hibernate_getEntityEntry() );
assertNotNull( myOtherReferenceData.$$_hibernate_getEntityEntry() );
// load them into 2 sessions
s1 = openSession();
s1.getTransaction().begin();
assertSame( myReferenceData, s1.get( MyEnhancedReferenceData.class, myReferenceData.getId() ) );
assertSame( myOtherReferenceData, s1.get( MyEnhancedReferenceData.class, myOtherReferenceData.getId() ) );
s2 = openSession();
s2.getTransaction().begin();
assertSame( myReferenceData, s2.get( MyEnhancedReferenceData.class, myReferenceData.getId() ) );
assertSame( myOtherReferenceData, s2.get( MyEnhancedReferenceData.class, myOtherReferenceData.getId() ) );
// delete myReferenceData from s1
s1.delete( myReferenceData );
// delete myOtherReferenceData from s2
s2.delete( myOtherReferenceData );
s1.getTransaction().commit();
s1.close();
assertEquals( Status.GONE, myReferenceData.$$_hibernate_getEntityEntry().getStatus() );
s2.getTransaction().commit();
s2.close();
assertEquals( Status.GONE, myOtherReferenceData.$$_hibernate_getEntityEntry().getStatus() );
}
@Entity(name = "MyEnhancedReferenceData") @Entity(name = "MyEnhancedReferenceData")
@Immutable @Immutable
@Cacheable @Cacheable