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;
|
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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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.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
|
||||||
|
|
Loading…
Reference in New Issue