diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java index 94a2cf29de..2d2617ffc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java @@ -34,6 +34,7 @@ import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import javassist.CtNewMethod; +import javassist.Modifier; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.FieldInfo; @@ -44,8 +45,8 @@ import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.bytecode.enhance.EnhancementException; import org.hibernate.bytecode.internal.javassist.FieldHandled; -import org.hibernate.engine.ManagedComposite; -import org.hibernate.engine.ManagedEntity; +import org.hibernate.engine.spi.ManagedComposite; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.internal.CoreMessageLogger; @@ -56,16 +57,27 @@ import org.hibernate.internal.CoreMessageLogger; public class Enhancer { private static final CoreMessageLogger log = Logger.getMessageLogger( CoreMessageLogger.class, Enhancer.class.getName() ); + public static final String ENTITY_INSTANCE_GETTER_NAME = "hibernate_getEntityInstance"; + public static final String ENTITY_ENTRY_FIELD_NAME = "$hibernate_entityEntryHolder"; public static final String ENTITY_ENTRY_GETTER_NAME = "hibernate_getEntityEntry"; public static final String ENTITY_ENTRY_SETTER_NAME = "hibernate_setEntityEntry"; + public static final String PREVIOUS_FIELD_NAME = "$hibernate_previousManagedEntity"; + public static final String PREVIOUS_GETTER_NAME = "hibernate_getPreviousManagedEntity"; + public static final String PREVIOUS_SETTER_NAME = "hibernate_setPreviousManagedEntity"; + + public static final String NEXT_FIELD_NAME = "$hibernate_nextManagedEntity"; + public static final String NEXT_GETTER_NAME = "hibernate_getNextManagedEntity"; + public static final String NEXT_SETTER_NAME = "hibernate_setNextManagedEntity"; + private final EnhancementContext enhancementContext; private final ClassPool classPool; private final CtClass managedEntityCtClass; private final CtClass managedCompositeCtClass; private final CtClass entityEntryCtClass; + private final CtClass objectCtClass; public Enhancer(EnhancementContext enhancementContext) { this.enhancementContext = enhancementContext; @@ -88,6 +100,8 @@ public class Enhancer { // "add" EntityEntry this.entityEntryCtClass = classPool.makeClass( EntityEntry.class.getName() ); + + this.objectCtClass = classPool.makeClass( Object.class.getName() ); } catch (IOException e) { throw new EnhancementException( "Could not prepare Javassist ClassPool", e ); @@ -177,6 +191,40 @@ public class Enhancer { // add the ManagedEntity interface managedCtClass.addInterface( managedEntityCtClass ); + addEntityInstanceHandling( managedCtClass, constPool ); + addEntityEntryHandling( managedCtClass, constPool ); + addLinkedPreviousHandling( managedCtClass, constPool ); + addLinkedNextHandling( managedCtClass, constPool ); + } + + private void addEntityInstanceHandling(CtClass managedCtClass, ConstPool constPool) { + // add the ManagedEntity#hibernate_getEntityInstance method + try { + managedCtClass.addMethod( + CtNewMethod.make( + objectCtClass, + ENTITY_INSTANCE_GETTER_NAME, + new CtClass[0], + new CtClass[0], + "{ return this; }", + managedCtClass + ) + ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add EntityEntry getter", + managedCtClass.getName() + ), + e + ); + } + + // essentially add `return this;` + } + + private void addEntityEntryHandling(CtClass managedCtClass, ConstPool constPool) { // add field to hold EntityEntry final CtField entityEntryField; try { @@ -193,7 +241,8 @@ public class Enhancer { ); } - // make that new field @Transient + // make that new field transient and @Transient + entityEntryField.setModifiers( entityEntryField.getModifiers() | Modifier.TRANSIENT ); AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( entityEntryField.getFieldInfo() ); annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); @@ -230,6 +279,108 @@ public class Enhancer { } } + private void addLinkedPreviousHandling(CtClass managedCtClass, ConstPool constPool) { + // add field to hold "previous" ManagedEntity + final CtField previousField; + try { + previousField = new CtField( managedCtClass, PREVIOUS_FIELD_NAME, managedCtClass ); + managedCtClass.addField( previousField ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add field for holding previous ManagedEntity", + managedCtClass.getName() + ), + e + ); + } + + // make that new field transient and @Transient + previousField.setModifiers( previousField.getModifiers() | Modifier.TRANSIENT ); + AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( previousField.getFieldInfo() ); + annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); + + // add the "getter" + try { + managedCtClass.addMethod( CtNewMethod.getter( PREVIOUS_GETTER_NAME, previousField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add previous ManagedEntity getter", + managedCtClass.getName() + ), + e + ); + } + + // add the "setter" + try { + managedCtClass.addMethod( CtNewMethod.setter( PREVIOUS_SETTER_NAME, previousField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add previous ManagedEntity setter", + managedCtClass.getName() + ), + e + ); + } + } + + private void addLinkedNextHandling(CtClass managedCtClass, ConstPool constPool) { + // add field to hold "next" ManagedEntity + final CtField nextField; + try { + nextField = new CtField( managedCtClass, NEXT_FIELD_NAME, managedCtClass ); + managedCtClass.addField( nextField ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add field for holding next ManagedEntity", + managedCtClass.getName() + ), + e + ); + } + + // make that new field transient and @Transient + nextField.setModifiers( nextField.getModifiers() | Modifier.TRANSIENT ); + AnnotationsAttribute annotationsAttribute = getVisibleAnnotations( nextField.getFieldInfo() ); + annotationsAttribute.addAnnotation( new Annotation( Transient.class.getName(), constPool ) ); + + // add the "getter" + try { + managedCtClass.addMethod( CtNewMethod.getter( NEXT_GETTER_NAME, nextField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add next ManagedEntity getter", + managedCtClass.getName() + ), + e + ); + } + + // add the "setter" + try { + managedCtClass.addMethod( CtNewMethod.setter( NEXT_SETTER_NAME, nextField ) ); + } + catch (CannotCompileException e) { + throw new EnhancementException( + String.format( + "Could not enhance entity class [%s] to add next ManagedEntity setter", + managedCtClass.getName() + ), + e + ); + } + } + private AnnotationsAttribute getVisibleAnnotations(FieldInfo fieldInfo) { AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) fieldInfo.getAttribute( AnnotationsAttribute.visibleTag ); if ( annotationsAttribute == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java index 4cb2999dd5..c51a1404d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/EntityEntryContext.java @@ -26,15 +26,15 @@ package org.hibernate.engine.internal; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.LinkedHashMap; +import java.util.IdentityHashMap; import java.util.Map; import org.jboss.logging.Logger; +import org.hibernate.AssertionFailure; import org.hibernate.LockMode; -import org.hibernate.engine.ManagedEntity; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; /** * Defines a context for maintaining the relation between an entity associated with the Session ultimately owning this @@ -43,22 +43,22 @@ import org.hibernate.engine.spi.EntityEntry; * the entity->EntityEntry association is maintained in a Map within this class * *
  • - * the EntityEntry is injected into the entity via it implementing the {@link org.hibernate.engine.ManagedEntity} contract, + * the EntityEntry is injected into the entity via it implementing the {@link org.hibernate.engine.spi.ManagedEntity} contract, * either directly or through bytecode enhancement. *
  • * *

    - * IMPL NOTE : This current implementation is not ideal in the {@link org.hibernate.engine.ManagedEntity} case. The problem is that - * the 'backingMap' is still the means to maintain ordering of the entries; but in the {@link org.hibernate.engine.ManagedEntity} case - * the use of a Map is overkill, and double here so because of the need for wrapping the map keys. But this is just - * a quick prototype. * * @author Steve Ebersole */ public class EntityEntryContext { private static final Logger log = Logger.getLogger( EntityEntryContext.class ); - private LinkedHashMap backingMap = new LinkedHashMap(); + private transient ManagedEntity head; + private transient ManagedEntity tail; + private transient int count = 0; + + private transient IdentityHashMap nonEnhancedEntityXref; @SuppressWarnings( {"unchecked"}) private transient Map.Entry[] reentrantSafeEntries = new Map.Entry[0]; @@ -67,193 +67,356 @@ public class EntityEntryContext { public EntityEntryContext() { } - /** - * Private constructor used during deserialization - * - * @param backingMap The backing map re-built from the serial stream - */ - private EntityEntryContext(LinkedHashMap backingMap) { - this.backingMap = backingMap; - // mark dirty so we can rebuild the 'reentrantSafeEntries' + public void addEntityEntry(Object entity, EntityEntry entityEntry) { + // IMPORTANT!!!!! + // add is called more than once of some entities. In such cases the first + // call is simply setting up a "marker" to avoid infinite looping from reentrancy + // + // any addition (even the double one described above) should invalidate the cross-ref array dirty = true; - } - public void clear() { - dirty = true; - for ( Map.Entry mapEntry : backingMap.entrySet() ) { - final Object realKey = mapEntry.getKey().getRealKey(); - if ( ManagedEntity.class.isInstance( realKey ) ) { - ( (ManagedEntity) realKey ).hibernate_setEntityEntry( null ); - } + // 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; } - backingMap.clear(); - reentrantSafeEntries = null; - } + else { + ManagedEntity wrapper = null; + if ( nonEnhancedEntityXref == null ) { + nonEnhancedEntityXref = new IdentityHashMap(); + } + else { + wrapper = nonEnhancedEntityXref.get( entity ); + } - public void downgradeLocks() { - // Downgrade locks - for ( Map.Entry mapEntry : backingMap.entrySet() ) { - final Object realKey = mapEntry.getKey().getRealKey(); - final EntityEntry entityEntry = ManagedEntity.class.isInstance( realKey ) - ? ( (ManagedEntity) realKey ).hibernate_getEntityEntry() - : mapEntry.getValue(); - entityEntry.setLockMode( LockMode.NONE ); + if ( wrapper == null ) { + wrapper = new ManagedEntityImpl( entity ); + nonEnhancedEntityXref.put( entity, wrapper ); + alreadyAssociated = false; + } + else { + alreadyAssociated = true; + } + + managedEntity = wrapper; + } + + // associate the EntityEntry with the entity + managedEntity.hibernate_setEntityEntry( entityEntry ); + + if ( alreadyAssociated ) { + // if the entity was already associated with the context, skip the linking step. + return; + } + + // finally, set up linking and count + if ( tail == null ) { + assert head == null; + head = managedEntity; + tail = head; + count = 1; + } + else { + tail.hibernate_setNextManagedEntity( managedEntity ); + managedEntity.hibernate_setPreviousManagedEntity( tail ); + tail = managedEntity; + count++; } } public boolean hasEntityEntry(Object entity) { - return ManagedEntity.class.isInstance( entity ) - ? ( (ManagedEntity) entity ).hibernate_getEntityEntry() == null - : backingMap.containsKey( new KeyWrapper( entity ) ); + return getEntityEntry( entity ) != null; } public EntityEntry getEntityEntry(Object entity) { - return ManagedEntity.class.isInstance( entity ) - ? ( (ManagedEntity) entity ).hibernate_getEntityEntry() - : backingMap.get( new KeyWrapper( entity ) ); + final ManagedEntity managedEntity; + if ( ManagedEntity.class.isInstance( entity ) ) { + managedEntity = (ManagedEntity) entity; + } + else if ( nonEnhancedEntityXref == null ) { + managedEntity = null; + } + else { + managedEntity = nonEnhancedEntityXref.get( entity ); + } + + return managedEntity == null + ? null + : managedEntity.hibernate_getEntityEntry(); } public EntityEntry removeEntityEntry(Object entity) { dirty = true; - if ( ManagedEntity.class.isInstance( entity ) ) { - backingMap.remove( new KeyWrapper( entity ) ); - final EntityEntry entityEntry = ( (ManagedEntity) entity ).hibernate_getEntityEntry(); - ( (ManagedEntity) entity ).hibernate_setEntityEntry( null ); - return entityEntry; - } - else { - return backingMap.remove( new KeyWrapper( entity ) ); - } - } - public void addEntityEntry(Object entity, EntityEntry entityEntry) { - dirty = true; + final ManagedEntity managedEntity; if ( ManagedEntity.class.isInstance( entity ) ) { - backingMap.put( new KeyWrapper( entity ), null ); - ( (ManagedEntity) entity ).hibernate_setEntityEntry( entityEntry ); + managedEntity = (ManagedEntity) entity; + } + else if ( nonEnhancedEntityXref == null ) { + managedEntity = null; } else { - backingMap.put( new KeyWrapper( entity ), entityEntry ); + managedEntity = nonEnhancedEntityXref.remove( entity ); } + + if ( managedEntity == null ) { + throw new AssertionFailure( "Unable to resolve entity reference to EntityEntry for removal" ); + } + + // prepare for re-linking... + ManagedEntity previous = managedEntity.hibernate_getPreviousManagedEntity(); + ManagedEntity next = managedEntity.hibernate_getNextManagedEntity(); + managedEntity.hibernate_setPreviousManagedEntity( null ); + managedEntity.hibernate_setNextManagedEntity( null ); + + count--; + + if ( count == 0 ) { + // handle as a special case... + head = null; + tail = null; + + assert previous == null; + assert next == null; + } + else { + // otherwise, previous or next (or both) should be non-null + if ( previous == null ) { + // we are removing head + assert managedEntity == head; + head = next; + } + else { + previous.hibernate_setNextManagedEntity( next ); + } + + if ( next == null ) { + // we are removing tail + assert managedEntity == tail; + tail = previous; + } + else { + next.hibernate_setPreviousManagedEntity( previous ); + } + } + + EntityEntry theEntityEntry = managedEntity.hibernate_getEntityEntry(); + managedEntity.hibernate_setEntityEntry( null ); + return theEntityEntry; } public Map.Entry[] reentrantSafeEntityEntries() { if ( dirty ) { - reentrantSafeEntries = new MapEntryImpl[ backingMap.size() ]; + reentrantSafeEntries = new EntityEntryCrossRefImpl[count]; int i = 0; - for ( Map.Entry mapEntry : backingMap.entrySet() ) { - final Object entity = mapEntry.getKey().getRealKey(); - final EntityEntry entityEntry = ManagedEntity.class.isInstance( entity ) - ? ( (ManagedEntity) entity ).hibernate_getEntityEntry() - : mapEntry.getValue(); - reentrantSafeEntries[i++] = new MapEntryImpl( entity, entityEntry ); + ManagedEntity managedEntity = head; + while ( managedEntity != null ) { + reentrantSafeEntries[i++] = new EntityEntryCrossRefImpl( + managedEntity.hibernate_getEntityInstance(), + managedEntity.hibernate_getEntityEntry() + ); + managedEntity = managedEntity.hibernate_getNextManagedEntity(); } dirty = false; } return reentrantSafeEntries; } + public void clear() { + dirty = true; + + ManagedEntity node = head; + while ( node != null ) { + final ManagedEntity nextNode = node.hibernate_getNextManagedEntity(); + + node.hibernate_setEntityEntry( null ); + node.hibernate_setPreviousManagedEntity( null ); + node.hibernate_setNextManagedEntity( null ); + + node = nextNode; + } + + if ( nonEnhancedEntityXref != null ) { + nonEnhancedEntityXref.clear(); + } + + head = null; + tail = null; + count = 0; + + reentrantSafeEntries = null; + } + + public void downgradeLocks() { + if ( head == null ) { + return; + } + + ManagedEntity node = head; + while ( node != null ) { + node.hibernate_getEntityEntry().setLockMode( LockMode.NONE ); + + node = node.hibernate_getNextManagedEntity(); + } + } + public void serialize(ObjectOutputStream oos) throws IOException { - final int count = backingMap.size(); log.tracef( "Starting serialization of [%s] EntityEntry entries", count ); oos.writeInt( count ); - for ( Map.Entry mapEntry : backingMap.entrySet() ) { - oos.writeObject( mapEntry.getKey().getRealKey() ); - mapEntry.getValue().serialize( oos ); + if ( count == 0 ) { + return; + } + + ManagedEntity managedEntity = head; + while ( managedEntity != null ) { + // so we know whether or not to build a ManagedEntityImpl on deserialize + oos.writeBoolean( managedEntity == managedEntity.hibernate_getEntityInstance() ); + oos.writeObject( managedEntity.hibernate_getEntityInstance() ); + managedEntity.hibernate_getEntityEntry().serialize( oos ); + + managedEntity = managedEntity.hibernate_getNextManagedEntity(); } } public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn) throws IOException, ClassNotFoundException { final int count = ois.readInt(); log.tracef( "Starting deserialization of [%s] EntityEntry entries", count ); - final LinkedHashMap backingMap = new LinkedHashMap( count ); + + final EntityEntryContext context = new EntityEntryContext(); + context.count = count; + context.dirty = true; + + if ( count == 0 ) { + return context; + } + + ManagedEntity previous = null; + for ( int i = 0; i < count; i++ ) { + final boolean isEnhanced = ois.readBoolean(); final Object entity = ois.readObject(); final EntityEntry entry = EntityEntry.deserialize( ois, rtn ); - backingMap.put( new KeyWrapper( entity ), entry ); - } - return new EntityEntryContext( backingMap ); - } - - /** - * @deprecated Added to support (also deprecated) PersistenceContext.getEntityEntries method until it can be removed. Safe to use for counts. - * - */ - @Deprecated - public Map getEntityEntryMap() { - return backingMap; - } - - /** - * We need to base the identity on {@link System#identityHashCode(Object)} but - * attempt to lazily initialize and cache this value: being a native invocation - * it is an expensive value to retrieve. - */ - public static final class KeyWrapper implements Serializable { - private final K realKey; - private int hash = 0; - - KeyWrapper(K realKey) { - this.realKey = realKey; - } - - @SuppressWarnings( {"EqualsWhichDoesntCheckParameterClass"}) - @Override - public boolean equals(Object other) { - return realKey == ( (KeyWrapper) other ).realKey; - } - - @Override - public int hashCode() { - if ( this.hash == 0 ) { - //We consider "zero" as non-initialized value - final int newHash = System.identityHashCode( realKey ); - if ( newHash == 0 ) { - //So make sure we don't store zeros as it would trigger initialization again: - //any value is fine as long as we're deterministic. - this.hash = -1; - } - else { - this.hash = newHash; - } + final ManagedEntity managedEntity; + if ( isEnhanced ) { + managedEntity = (ManagedEntity) entity; } - return hash; + else { + managedEntity = new ManagedEntityImpl( entity ); + if ( context.nonEnhancedEntityXref == null ) { + context.nonEnhancedEntityXref = new IdentityHashMap(); + } + context.nonEnhancedEntityXref.put( entity, managedEntity ); + } + managedEntity.hibernate_setEntityEntry( entry ); + + if ( previous == null ) { + context.head = managedEntity; + } + else { + previous.hibernate_setNextManagedEntity( managedEntity ); + managedEntity.hibernate_setPreviousManagedEntity( previous ); + } + + previous = managedEntity; + } + + context.tail = previous; + + return context; + } + + public int getNumberOfManagedEntities() { + return count; + } + + private static class ManagedEntityImpl implements ManagedEntity { + private final Object entityInstance; + private EntityEntry entityEntry; + private ManagedEntity previous; + private ManagedEntity next; + + public ManagedEntityImpl(Object entityInstance) { + this.entityInstance = entityInstance; } @Override - public String toString() { - return realKey.toString(); + public Object hibernate_getEntityInstance() { + return entityInstance; } - public K getRealKey() { - return realKey; + @Override + public EntityEntry hibernate_getEntityEntry() { + return entityEntry; + } + + @Override + public void hibernate_setEntityEntry(EntityEntry entityEntry) { + this.entityEntry = entityEntry; + } + + @Override + public ManagedEntity hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; } } - private static class MapEntryImpl implements Map.Entry { - private final Object key; - private EntityEntry value; + private static class EntityEntryCrossRefImpl implements EntityEntryCrossRef { + private final Object entity; + private EntityEntry entityEntry; - private MapEntryImpl(Object key, EntityEntry value) { - this.key = key; - this.value = value; + private EntityEntryCrossRefImpl(Object entity, EntityEntry entityEntry) { + this.entity = entity; + this.entityEntry = entityEntry; + } + + @Override + public Object getEntity() { + return entity; + } + + @Override + public EntityEntry getEntityEntry() { + return entityEntry; } @Override public Object getKey() { - return key; + return getEntity(); } @Override public EntityEntry getValue() { - return value; + return getEntityEntry(); } @Override - public EntityEntry setValue(EntityEntry value) { - final EntityEntry old = this.value; - this.value = value; - return value; + public EntityEntry setValue(EntityEntry entityEntry) { + final EntityEntry old = this.entityEntry; + this.entityEntry = entityEntry; + return old; } } + + public static interface EntityEntryCrossRef extends Map.Entry { + public Object getEntity(); + public EntityEntry getEntityEntry(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 130cb8edb5..dc24d0a256 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -1152,10 +1152,14 @@ public class StatefulPersistenceContext implements PersistenceContext { return proxiesByKey; } + @Override + public int getNumberOfManagedEntities() { + return entityEntryContext.getNumberOfManagedEntities(); + } + @Override public Map getEntityEntries() { - return entityEntryContext.getEntityEntryMap(); -// return entityEntries; + return null; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/Managed.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java similarity index 78% rename from hibernate-core/src/main/java/org/hibernate/engine/Managed.java rename to hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java index e822877a10..0570605b3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/Managed.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/Managed.java @@ -21,12 +21,21 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine; +package org.hibernate.engine.spi; /** * Contract for classes (specifically, entities and components/embeddables) that are "managed". Developers can * choose to either have their classes manually implement these interfaces or Hibernate can enhance their classes * to implement these interfaces via built-time or run-time enhancement. + *

    + * The term managed here is used to describe both:

      + *
    • + * the fact that they are known to the persistence provider (this is defined by the interface itself) + *
    • + *
    • + * its association with Session/EntityManager (this is defined by the state exposed through the interface) + *
    • + *
    * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/ManagedComposite.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java similarity index 97% rename from hibernate-core/src/main/java/org/hibernate/engine/ManagedComposite.java rename to hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java index 6ae9c65604..602dbf22ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/ManagedComposite.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedComposite.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine; +package org.hibernate.engine.spi; /** * Specialized {@link Managed} contract for component/embeddable classes. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/ManagedEntity.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java similarity index 55% rename from hibernate-core/src/main/java/org/hibernate/engine/ManagedEntity.java rename to hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java index 28ecf15d92..a71e050039 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/ManagedEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ManagedEntity.java @@ -21,18 +21,35 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.engine; - -import org.hibernate.engine.spi.EntityEntry; +package org.hibernate.engine.spi; /** - * Specialized {@link Managed} contract for entity classes. + * Specialized {@link Managed} contract for entity classes. Essentially provides access to information + * about an instance's association to a Session/EntityManager. Specific information includes:
      + *
    • + * the association's {@link EntityEntry} (by way of {@link #hibernate_getEntityEntry()} and + * {@link #hibernate_setEntityEntry}). EntityEntry describes states, snapshots, etc. + *
    • + *
    • + * link information. ManagedEntity instances are part of a "linked list", thus link information + * describes the next and previous entries/nodes in that ordering. See + * {@link #hibernate_getNextManagedEntity}, {@link #hibernate_setNextManagedEntity}, + * {@link #hibernate_getPreviousManagedEntity()}, {@link #hibernate_setPreviousManagedEntity} + *
    • + *
    * * @author Steve Ebersole */ public interface ManagedEntity extends Managed { /** - * Callback to get any associated EntityEntry. + * Obtain a reference to the entity instance. + * + * @return The entity instance. + */ + public Object hibernate_getEntityInstance(); + + /** + * Provides access to the associated EntityEntry. * * @return The EntityEntry associated with this entity instance. * @@ -47,4 +64,12 @@ public interface ManagedEntity extends Managed { * @param entityEntry The EntityEntry associated with this entity instance. */ public void hibernate_setEntityEntry(EntityEntry entityEntry); + + public ManagedEntity hibernate_getPreviousManagedEntity(); + + public void hibernate_setPreviousManagedEntity(ManagedEntity previous); + + public ManagedEntity hibernate_getNextManagedEntity(); + + public void hibernate_setNextManagedEntity(ManagedEntity next); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index 56875c7427..1ac0388104 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -481,16 +481,26 @@ public interface PersistenceContext { */ public Map getEntitiesByKey(); + /** + * Provides access to the entity/EntityEntry combos associated with the persistence context in a manner that + * is safe from reentrant access. Specifically, it is safe from additions/removals while iterating. + * + * @return + */ public Map.Entry[] reentrantSafeEntityEntries(); /** * Get the mapping from entity instance to entity entry * - * @deprecated Due to the introduction of EntityEntryContext and bytecode enhancement + * @deprecated Due to the introduction of EntityEntryContext and bytecode enhancement; only valid really for + * sizing, see {@link #getNumberOfManagedEntities}. For iterating the entity/EntityEntry combos, see + * {@link #reentrantSafeEntityEntries} */ @Deprecated public Map getEntityEntries(); + public int getNumberOfManagedEntities(); + /** * Get the mapping from collection instance to collection entry */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 59b732371a..3feea9fa83 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -119,7 +119,7 @@ public abstract class AbstractFlushingEventListener implements Serializable { session.getActionQueue().numberOfInsertions(), session.getActionQueue().numberOfUpdates(), session.getActionQueue().numberOfDeletions(), - persistenceContext.getEntityEntries().size() + persistenceContext.getNumberOfManagedEntities() ); LOG.debugf( "Flushed: %s (re)creations, %s updates, %s removals to %s collections", diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java index fb573f5810..34c70cc374 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java @@ -86,7 +86,7 @@ public class DefaultAutoFlushEventListener extends AbstractFlushingEventListener private boolean flushMightBeNeeded(final EventSource source) { return !source.getFlushMode().lessThan(FlushMode.AUTO) && source.getDontFlushFromFind() == 0 && - ( source.getPersistenceContext().getEntityEntries().size() > 0 || + ( source.getPersistenceContext().getNumberOfManagedEntities() > 0 || source.getPersistenceContext().getCollectionEntries().size() > 0 ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java index 915df116c0..60b3a7657c 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java @@ -45,7 +45,7 @@ public class DefaultFlushEventListener extends AbstractFlushingEventListener imp public void onFlush(FlushEvent event) throws HibernateException { final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContext(); - if ( persistenceContext.getEntityEntries().size() > 0 || + if ( persistenceContext.getNumberOfManagedEntities() > 0 || persistenceContext.getCollectionEntries().size() > 0 ) { flushEverythingToExecutions(event); diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java index 9ba57f4204..d28c6778ae 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java @@ -41,7 +41,7 @@ public class SessionStatisticsImpl implements SessionStatistics { } public int getEntityCount() { - return session.getPersistenceContext().getEntityEntries().size(); + return session.getPersistenceContext().getNumberOfManagedEntities(); } public int getCollectionCount() { diff --git a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java index 5803f4bd8b..e51901bd8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/enhance/EnhancementTask.java @@ -52,7 +52,7 @@ import org.hibernate.bytecode.enhance.spi.Enhancer; * * @author Steve Ebersole * - * @see org.hibernate.engine.Managed + * @see org.hibernate.engine.spi.Managed */ public class EnhancementTask extends Task { private List filesets = new ArrayList(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java index 71da48fc0b..add986ae51 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/EnhancerTest.java @@ -53,6 +53,7 @@ import org.hibernate.testing.junit4.BaseUnitTestCase; 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.fail; /** @@ -95,6 +96,9 @@ public class EnhancerTest extends BaseUnitTestCase { assertNotNull( getter.invoke( simpleEntityInstance ) ); setter.invoke( simpleEntityInstance, new Object[] { null } ); assertNull( getter.invoke( simpleEntityInstance ) ); + + Method entityInstanceGetter = simpleEntityClass.getMethod( Enhancer.ENTITY_INSTANCE_GETTER_NAME ); + assertSame( simpleEntityInstance, entityInstanceGetter.invoke( simpleEntityInstance ) ); } private CtClass generateCtClassForAnEntity() throws IOException, NotFoundException { diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java index 2fb9671b97..75f26f6cc0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MostBasicEnhancementTest.java @@ -31,6 +31,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; /** * @author Steve Ebersole @@ -46,13 +47,28 @@ public class MostBasicEnhancementTest extends BaseCoreFunctionalTestCase { Session s = openSession(); s.beginTransaction(); s.save( new MyEntity( 1L ) ); + s.save( new MyEntity( 2L ) ); s.getTransaction().commit(); s.close(); s = openSession(); s.beginTransaction(); - MyEntity myEntity = (MyEntity) s.load( MyEntity.class, 1L ); + MyEntity myEntity = (MyEntity) s.get( MyEntity.class, 1L ); + MyEntity myEntity2 = (MyEntity) s.get( MyEntity.class, 2L ); + + assertNotNull( myEntity.hibernate_getEntityInstance() ); + assertSame( myEntity, myEntity.hibernate_getEntityInstance() ); assertNotNull( myEntity.hibernate_getEntityEntry() ); + assertNull( myEntity.hibernate_getPreviousManagedEntity() ); + assertNotNull( myEntity.hibernate_getNextManagedEntity() ); + + assertNotNull( myEntity2.hibernate_getEntityInstance() ); + assertSame( myEntity2, myEntity2.hibernate_getEntityInstance() ); + assertNotNull( myEntity2.hibernate_getEntityEntry() ); + assertNotNull( myEntity2.hibernate_getPreviousManagedEntity() ); + assertNull( myEntity2.hibernate_getNextManagedEntity() ); + + s.createQuery( "delete MyEntity" ).executeUpdate(); s.getTransaction().commit(); s.close(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java index 98c5fd6670..23c21157e2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/MyEntity.java @@ -27,7 +27,7 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Transient; -import org.hibernate.engine.ManagedEntity; +import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.EntityEntry; /** @@ -37,6 +37,10 @@ import org.hibernate.engine.spi.EntityEntry; public class MyEntity implements ManagedEntity { @Transient private transient EntityEntry entityEntry; + @Transient + private transient ManagedEntity previous; + @Transient + private transient ManagedEntity next; private Long id; private String name; @@ -65,6 +69,11 @@ public class MyEntity implements ManagedEntity { this.name = name; } + @Override + public Object hibernate_getEntityInstance() { + return this; + } + @Override public EntityEntry hibernate_getEntityEntry() { return entityEntry; @@ -74,4 +83,24 @@ public class MyEntity implements ManagedEntity { public void hibernate_setEntityEntry(EntityEntry entityEntry) { this.entityEntry = entityEntry; } + + @Override + public ManagedEntity hibernate_getNextManagedEntity() { + return next; + } + + @Override + public void hibernate_setNextManagedEntity(ManagedEntity next) { + this.next = next; + } + + @Override + public ManagedEntity hibernate_getPreviousManagedEntity() { + return previous; + } + + @Override + public void hibernate_setPreviousManagedEntity(ManagedEntity previous) { + this.previous = previous; + } } diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java index 9999914314..f88d44ec86 100644 --- a/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/performance/EvictAuditDataAfterCommitTest.java @@ -3,6 +3,7 @@ package org.hibernate.envers.test.performance; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.Assert; @@ -93,8 +94,8 @@ public class EvictAuditDataAfterCommitTest extends BaseEnversFunctionalTestCase private void checkEmptyAuditSessionCache(Session session, String ... auditEntityNames) { List entityNames = Arrays.asList(auditEntityNames); PersistenceContext persistenceContext = ((SessionImplementor) session).getPersistenceContext(); - for (Object entry : persistenceContext.getEntityEntries().values()) { - EntityEntry entityEntry = (EntityEntry) entry; + for ( Map.Entry entrySet : persistenceContext.reentrantSafeEntityEntries() ) { + final EntityEntry entityEntry = entrySet.getValue(); if (entityNames.contains(entityEntry.getEntityName())) { assert false : "Audit data shall not be stored in the session level cache. This causes performance issues."; }