From 1005f19b1a1e8d71b216d353001d73c796d809da Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 8 Dec 2009 18:56:15 +0000 Subject: [PATCH] HHH-3860 - Cascading performance problems when session contains many entities git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18168 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../java/org/hibernate/engine/Cascade.java | 25 ++++++-- .../hibernate/engine/PersistenceContext.java | 13 +++++ .../engine/StatefulPersistenceContext.java | 58 ++++++++++++++++++- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/hibernate/engine/Cascade.java b/core/src/main/java/org/hibernate/engine/Cascade.java index 003723e4af..6d5ec26efa 100644 --- a/core/src/main/java/org/hibernate/engine/Cascade.java +++ b/core/src/main/java/org/hibernate/engine/Cascade.java @@ -152,6 +152,7 @@ public final class Cascade { if ( style.doCascade( action ) ) { cascadeProperty( + parent, persister.getPropertyValue( parent, i, entityMode ), types[i], style, @@ -180,6 +181,7 @@ public final class Cascade { * Cascade an action to the child or children */ private void cascadeProperty( + final Object parent, final Object child, final Type type, final CascadeStyle style, @@ -191,6 +193,7 @@ public final class Cascade { AssociationType associationType = (AssociationType) type; if ( cascadeAssociationNow( associationType ) ) { cascadeAssociation( + parent, child, type, style, @@ -200,7 +203,7 @@ public final class Cascade { } } else if ( type.isComponentType() ) { - cascadeComponent( child, (AbstractComponentType) type, anything ); + cascadeComponent( parent, child, (AbstractComponentType) type, anything ); } } } @@ -211,6 +214,7 @@ public final class Cascade { } private void cascadeComponent( + final Object parent, final Object child, final AbstractComponentType componentType, final Object anything) { @@ -220,6 +224,7 @@ public final class Cascade { CascadeStyle componentPropertyStyle = componentType.getCascadeStyle(i); if ( componentPropertyStyle.doCascade(action) ) { cascadeProperty( + parent, children[i], types[i], componentPropertyStyle, @@ -231,16 +236,17 @@ public final class Cascade { } private void cascadeAssociation( + final Object parent, final Object child, final Type type, final CascadeStyle style, final Object anything, final boolean isCascadeDeleteEnabled) { if ( type.isEntityType() || type.isAnyType() ) { - cascadeToOne( child, type, style, anything, isCascadeDeleteEnabled ); + cascadeToOne( parent, child, type, style, anything, isCascadeDeleteEnabled ); } else if ( type.isCollectionType() ) { - cascadeCollection( child, style, anything, (CollectionType) type ); + cascadeCollection( parent, child, style, anything, (CollectionType) type ); } } @@ -248,6 +254,7 @@ public final class Cascade { * Cascade an action to a collection */ private void cascadeCollection( + final Object parent, final Object child, final CascadeStyle style, final Object anything, @@ -264,6 +271,7 @@ public final class Cascade { //cascade to current collection elements if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) { cascadeCollectionElements( + parent, child, type, style, @@ -280,6 +288,7 @@ public final class Cascade { * Cascade an action to a to-one association or any type */ private void cascadeToOne( + final Object parent, final Object child, final Type type, final CascadeStyle style, @@ -289,7 +298,13 @@ public final class Cascade { ? ( (EntityType) type ).getAssociatedEntityName() : null; if ( style.reallyDoCascade(action) ) { //not really necessary, but good for consistency... - action.cascade(eventSource, child, entityName, anything, isCascadeDeleteEnabled); + eventSource.getPersistenceContext().addChildParent(child, parent); + try { + action.cascade(eventSource, child, entityName, anything, isCascadeDeleteEnabled); + } + finally { + eventSource.getPersistenceContext().removeChildParent(child); + } } } @@ -297,6 +312,7 @@ public final class Cascade { * Cascade to the collection elements */ private void cascadeCollectionElements( + final Object parent, final Object child, final CollectionType collectionType, final CascadeStyle style, @@ -318,6 +334,7 @@ public final class Cascade { Iterator iter = action.getCascadableChildrenIterator(eventSource, collectionType, child); while ( iter.hasNext() ) { cascadeProperty( + parent, iter.next(), elemType, style, diff --git a/core/src/main/java/org/hibernate/engine/PersistenceContext.java b/core/src/main/java/org/hibernate/engine/PersistenceContext.java index 60715e827d..3398b88432 100644 --- a/core/src/main/java/org/hibernate/engine/PersistenceContext.java +++ b/core/src/main/java/org/hibernate/engine/PersistenceContext.java @@ -482,4 +482,17 @@ public interface PersistenceContext { public void setReadOnly(Object entity, boolean readOnly); void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId); + + /** + * Put child/parent relation to cache for cascading op + * @param parent + * @param child + */ + public void addChildParent(Object parent, Object child); + + /** + * Remove child/parent relation from cache + * @param parent + */ + public void removeChildParent(Object child); } diff --git a/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java b/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java index bdbd76c475..d451af4d39 100644 --- a/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java +++ b/core/src/main/java/org/hibernate/engine/StatefulPersistenceContext.java @@ -119,6 +119,10 @@ public class StatefulPersistenceContext implements PersistenceContext { // yet loaded ... for now, this is purely transient! private Map unownedCollections; + // Parent entities cache by their child for cascading + // May be empty or not contains all relation + private Map parentsByChild; + private int cascading = 0; private int loadCounter = 0; private boolean flushing = false; @@ -147,7 +151,8 @@ public class StatefulPersistenceContext implements PersistenceContext { collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); collectionsByKey = new HashMap( INIT_COLL_SIZE ); arrayHolders = IdentityMap.instantiate( INIT_COLL_SIZE ); - + parentsByChild = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); + nullifiableEntityKeys = new HashSet(); initTransientState(); @@ -214,6 +219,7 @@ public class StatefulPersistenceContext implements PersistenceContext { entitiesByKey.clear(); entitiesByUniqueKey.clear(); entityEntries.clear(); + parentsByChild.clear(); entitySnapshotsByKey.clear(); collectionsByKey.clear(); collectionEntries.clear(); @@ -360,6 +366,8 @@ public class StatefulPersistenceContext implements PersistenceContext { while ( iter.hasNext() ) { if ( iter.next()==entity ) iter.remove(); } + // Clear all parent cache + parentsByChild.clear(); entitySnapshotsByKey.remove(key); nullifiableEntityKeys.remove(key); getBatchFetchQueue().removeBatchLoadableEntityKey(key); @@ -1104,8 +1112,18 @@ public class StatefulPersistenceContext implements PersistenceContext { final EntityPersister persister = session.getFactory().getEntityPersister( entityName ); final CollectionPersister collectionPersister = session.getFactory().getCollectionPersister( collectionRole ); + // try cache lookup first + Object parent = parentsByChild.get(childEntity); + if (parent != null) { + if (isFoundInParent(propertyName, childEntity, persister, collectionPersister, parent)) { + return getEntry(parent).getId(); + } + else { + parentsByChild.remove(childEntity); // remove wrong entry + } + } // iterate all the entities currently associated with the persistence context. - Iterator entities = entityEntries.entrySet().iterator(); + Iterator entities = IdentityMap.entries(entityEntries).iterator(); while ( entities.hasNext() ) { final Map.Entry me = ( Map.Entry ) entities.next(); final EntityEntry entityEntry = ( EntityEntry ) me.getValue(); @@ -1207,7 +1225,26 @@ public class StatefulPersistenceContext implements PersistenceContext { .getEntityPersister(entity); CollectionPersister cp = session.getFactory() .getCollectionPersister(entity + '.' + property); - Iterator entities = entityEntries.entrySet().iterator(); + + // try cache lookup first + Object parent = parentsByChild.get(childEntity); + if (parent != null) { + Object index = getIndexInParent(property, childEntity, persister, cp, parent); + + if (index==null && mergeMap!=null) { + Object unmergedInstance = mergeMap.get(parent); + Object unmergedChild = mergeMap.get(childEntity); + if ( unmergedInstance!=null && unmergedChild!=null ) { + index = getIndexInParent(property, unmergedChild, persister, cp, unmergedInstance); + } + } + if (index!=null) { + return index; + } + parentsByChild.remove(childEntity); // remove wrong entry + } + + Iterator entities = IdentityMap.entries(entityEntries).iterator(); while ( entities.hasNext() ) { Map.Entry me = (Map.Entry) entities.next(); EntityEntry ee = (EntityEntry) me.getValue(); @@ -1277,6 +1314,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) { Object entity = entitiesByKey.remove( oldKey ); EntityEntry oldEntry = ( EntityEntry ) entityEntries.remove( entity ); + parentsByChild.clear(); EntityKey newKey = new EntityKey( generatedId, oldEntry.getPersister(), getSession().getEntityMode() ); addEntity( newKey, entity ); @@ -1487,4 +1525,18 @@ public class StatefulPersistenceContext implements PersistenceContext { return rtn; } + + /** + * @see org.hibernate.engine.PersistenceContext#addChildParent(java.lang.Object, java.lang.Object) + */ + public void addChildParent(Object child, Object parent) { + parentsByChild.put(child, parent); + } + + /** + * @see org.hibernate.engine.PersistenceContext#removeChildParent(java.lang.Object) + */ + public void removeChildParent(Object child) { + parentsByChild.remove(child); + } }