HHH-10795 : StatefulPersistenceContext.entityEntryContext does not work properly for ManagedEntity associated with a different StatefulPersistenceContext
This commit is contained in:
parent
cddb221288
commit
56b22aeaac
|
@ -6,12 +6,14 @@
|
|||
*/
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
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.ObjectInputStream;
|
||||
|
@ -37,7 +39,11 @@ import java.util.Map;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
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 tail;
|
||||
|
@ -52,7 +58,8 @@ public class 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
|
||||
dirty = true;
|
||||
|
||||
// determine the appropriate ManagedEntity instance to use based on whether the entity is enhanced or not.
|
||||
// also track whether the entity was already associated with the context
|
||||
final ManagedEntity managedEntity;
|
||||
final boolean alreadyAssociated;
|
||||
if ( ManagedEntity.class.isInstance( entity ) ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
alreadyAssociated = managedEntity.$$_hibernate_getEntityEntry() != null;
|
||||
assert AbstractEntityEntry.class.isInstance( entityEntry );
|
||||
|
||||
// We only need to check a mutable EntityEntry is associated with the same PersistenceContext.
|
||||
// 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;
|
||||
}
|
||||
else {
|
||||
ManagedEntity wrapper = null;
|
||||
if ( nonEnhancedEntityXref == null ) {
|
||||
nonEnhancedEntityXref = new IdentityHashMap<Object, ManagedEntity>();
|
||||
|
||||
// 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 ( entityEntry.getPersister().isMutable() ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
// 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 {
|
||||
wrapper = nonEnhancedEntityXref.get( entity );
|
||||
if ( nonEnhancedEntityXref == null ) {
|
||||
nonEnhancedEntityXref = new IdentityHashMap<Object, ManagedEntity>();
|
||||
}
|
||||
managedEntity = new ManagedEntityImpl( 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
|
||||
|
@ -106,9 +127,14 @@ public class EntityEntryContext {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: can dirty be set to true here?
|
||||
|
||||
// finally, set up linking and count
|
||||
if ( tail == 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;
|
||||
tail = head;
|
||||
count = 1;
|
||||
|
@ -116,11 +142,65 @@ public class EntityEntryContext {
|
|||
else {
|
||||
tail.$$_hibernate_setNextManagedEntity( managedEntity );
|
||||
managedEntity.$$_hibernate_setPreviousManagedEntity( tail );
|
||||
// Protect against stale data left in the ManagedEntity nullify next reference.
|
||||
managedEntity.$$_hibernate_setNextManagedEntity( null );
|
||||
tail = managedEntity;
|
||||
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?
|
||||
*
|
||||
|
@ -140,17 +220,8 @@ public class EntityEntryContext {
|
|||
* @return The associated EntityEntry
|
||||
*/
|
||||
public EntityEntry getEntityEntry(Object entity) {
|
||||
// essentially resolve the entity to a ManagedEntity...
|
||||
final ManagedEntity managedEntity;
|
||||
if ( ManagedEntity.class.isInstance( entity ) ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
}
|
||||
else if ( nonEnhancedEntityXref == null ) {
|
||||
managedEntity = null;
|
||||
}
|
||||
else {
|
||||
managedEntity = nonEnhancedEntityXref.get( entity );
|
||||
}
|
||||
// locate a ManagedEntity for the entity, but only if it is associated with the same PersistenceContext.
|
||||
final ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
|
||||
|
||||
// and get/return the EntityEntry from the ManagedEntry
|
||||
return managedEntity == null
|
||||
|
@ -166,27 +237,24 @@ public class EntityEntryContext {
|
|||
* @return Tjee EntityEntry
|
||||
*/
|
||||
public EntityEntry removeEntityEntry(Object entity) {
|
||||
dirty = true;
|
||||
|
||||
// again, resolve the entity to a ManagedEntity (which may not be possible for non-enhanced)...
|
||||
final ManagedEntity managedEntity;
|
||||
if ( ManagedEntity.class.isInstance( entity ) ) {
|
||||
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) {
|
||||
// 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
|
||||
final ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
|
||||
if ( managedEntity == null ) {
|
||||
// not associated with this EntityEntryContext, so nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: should dirty be set to true only if managedEntity is associtated with this context
|
||||
// (instead of setting it at the top of this method)?
|
||||
dirty = true;
|
||||
|
||||
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...
|
||||
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
|
||||
|
@ -228,10 +296,7 @@ public class EntityEntryContext {
|
|||
|
||||
// finally clean out the ManagedEntity and return the associated EntityEntry
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -270,9 +335,7 @@ public class EntityEntryContext {
|
|||
while ( node != null ) {
|
||||
final ManagedEntity nextNode = node.$$_hibernate_getNextManagedEntity();
|
||||
|
||||
if( canClearEntityEntryReference(node) ){
|
||||
node.$$_hibernate_setEntityEntry( null );
|
||||
}
|
||||
node.$$_hibernate_setEntityEntry( null );
|
||||
|
||||
node.$$_hibernate_setPreviousManagedEntity( null );
|
||||
node.$$_hibernate_setNextManagedEntity( null );
|
||||
|
@ -280,6 +343,10 @@ public class EntityEntryContext {
|
|||
node = nextNode;
|
||||
}
|
||||
|
||||
if ( immutableManagedEntityXref != null ) {
|
||||
immutableManagedEntityXref.clear();
|
||||
}
|
||||
|
||||
if ( nonEnhancedEntityXref != null ) {
|
||||
nonEnhancedEntityXref.clear();
|
||||
}
|
||||
|
@ -351,7 +418,7 @@ public class EntityEntryContext {
|
|||
final int count = ois.readInt();
|
||||
log.tracef( "Starting deserialization of [%s] EntityEntry entries", count );
|
||||
|
||||
final EntityEntryContext context = new EntityEntryContext();
|
||||
final EntityEntryContext context = new EntityEntryContext( rtn );
|
||||
context.count = count;
|
||||
context.dirty = true;
|
||||
|
||||
|
@ -376,7 +443,21 @@ public class EntityEntryContext {
|
|||
|
||||
final ManagedEntity managedEntity;
|
||||
if ( isEnhanced ) {
|
||||
managedEntity = (ManagedEntity) entity;
|
||||
if ( entry.getPersister().isMutable() ) {
|
||||
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 {
|
||||
managedEntity = new ManagedEntityImpl( entity );
|
||||
|
@ -431,25 +512,6 @@ public class EntityEntryContext {
|
|||
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
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -162,7 +162,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
);
|
||||
entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE );
|
||||
|
||||
entityEntryContext = new EntityEntryContext();
|
||||
entityEntryContext = new EntityEntryContext( this );
|
||||
// entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
|
||||
collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
|
||||
parentsByChild = new IdentityHashMap<>( INIT_COLL_SIZE );
|
||||
|
|
|
@ -1751,4 +1751,8 @@ public interface CoreMessageLogger extends BasicLogger {
|
|||
id = 479
|
||||
)
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.test.bytecode.enhancement;
|
|||
|
||||
import javassist.CtClass;
|
||||
|
||||
import org.hibernate.test.bytecode.enhancement.otherentityentrycontext.OtherEntityEntryContextTestTask;
|
||||
import org.hibernate.testing.FailureExpected;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
|
||||
|
@ -81,6 +82,11 @@ public class EnhancerTest extends BaseUnitTestCase {
|
|||
EnhancerTestUtils.runEnhancerTestTask( EvictionTestTask.class );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOtherPersistenceContext() {
|
||||
EnhancerTestUtils.runEnhancerTestTask( OtherEntityEntryContextTestTask.class );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssociation() {
|
||||
EnhancerTestUtils.runEnhancerTestTask( OneToOneAssociationTestTask.class );
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,10 @@ import org.hibernate.cfg.AvailableSettings;
|
|||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
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.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -24,8 +27,11 @@ import javax.persistence.Entity;
|
|||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
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.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -91,6 +97,121 @@ public class ByteCodeEnhancedImmutableReferenceCacheTest extends BaseCoreFunctio
|
|||
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")
|
||||
@Immutable
|
||||
@Cacheable
|
||||
|
|
Loading…
Reference in New Issue