Author: steve.ebersole(a)jboss.com
Date: 2008-02-03 00:56:39 -0500 (Sun, 03 Feb 2008)
New Revision: 14307
Modified:
core/trunk/core/src/main/java/org/hibernate/engine/ActionQueue.java
Log:
HHH-2957 : ActionQueue insertions sorting performance
Modified: core/trunk/core/src/main/java/org/hibernate/engine/ActionQueue.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/ActionQueue.java 2008-02-02
07:51:59 UTC (rev 14306)
+++ core/trunk/core/src/main/java/org/hibernate/engine/ActionQueue.java 2008-02-03
05:56:39 UTC (rev 14307)
@@ -1,31 +1,57 @@
-// $Id: ActionQueue.java 11402 2007-04-11 14:24:35Z steve.ebersole(a)jboss.com $
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors. All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301 USA
+ *
+ */
package org.hibernate.engine;
-import org.hibernate.action.EntityInsertAction;
-import org.hibernate.action.EntityDeleteAction;
-import org.hibernate.action.Executable;
-import org.hibernate.action.EntityUpdateAction;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.hibernate.AssertionFailure;
+import org.hibernate.HibernateException;
+import org.hibernate.action.BulkOperationCleanupAction;
import org.hibernate.action.CollectionRecreateAction;
import org.hibernate.action.CollectionRemoveAction;
import org.hibernate.action.CollectionUpdateAction;
+import org.hibernate.action.EntityDeleteAction;
import org.hibernate.action.EntityIdentityInsertAction;
-import org.hibernate.action.BulkOperationCleanupAction;
-import org.hibernate.HibernateException;
-import org.hibernate.AssertionFailure;
+import org.hibernate.action.EntityInsertAction;
+import org.hibernate.action.EntityUpdateAction;
+import org.hibernate.action.Executable;
import org.hibernate.cache.CacheException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.hibernate.type.Type;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.io.ObjectInputStream;
-import java.io.IOException;
-import java.io.Serializable;
-import java.io.ObjectOutputStream;
-
/**
* Responsible for maintaining the queue of actions related to events.
* </p>
@@ -167,7 +193,7 @@
final boolean invalidateQueryCache =
session.getFactory().getSettings().isQueryCacheEnabled();
for ( int i = 0; i < size; i++ ) {
try {
- Executable exec = ( Executable ) executions.get(i);
+ Executable exec = ( Executable ) executions.get( i );
try {
exec.afterTransactionCompletion( success );
}
@@ -177,11 +203,11 @@
}
}
}
- catch (CacheException ce) {
+ catch ( CacheException ce ) {
log.error( "could not release a cache lock", ce );
// continue loop
}
- catch (Exception e) {
+ catch ( Exception e ) {
throw new AssertionFailure( "Exception releasing cache locks", e );
}
}
@@ -193,16 +219,17 @@
* given the currently queued actions.
*
* @param tables The table/query-spaces to check.
+ *
* @return True if we contain pending actions against any of the given
- * tables; false otherwise.
+ * tables; false otherwise.
*/
public boolean areTablesToBeUpdated(Set tables) {
return areTablesToUpdated( updates, tables ) ||
- areTablesToUpdated( insertions, tables ) ||
- areTablesToUpdated( deletions, tables ) ||
- areTablesToUpdated( collectionUpdates, tables ) ||
- areTablesToUpdated( collectionCreations, tables ) ||
- areTablesToUpdated( collectionRemovals, tables );
+ areTablesToUpdated( insertions, tables ) ||
+ areTablesToUpdated( deletions, tables ) ||
+ areTablesToUpdated( collectionUpdates, tables ) ||
+ areTablesToUpdated( collectionCreations, tables ) ||
+ areTablesToUpdated( collectionRemovals, tables );
}
/**
@@ -217,10 +244,12 @@
private static boolean areTablesToUpdated(List executables, Set tablespaces) {
int size = executables.size();
for ( int j = 0; j < size; j++ ) {
- Serializable[] spaces = ( (Executable) executables.get(j) ).getPropertySpaces();
+ Serializable[] spaces = ( ( Executable ) executables.get( j ) ).getPropertySpaces();
for ( int i = 0; i < spaces.length; i++ ) {
if ( tablespaces.contains( spaces[i] ) ) {
- if ( log.isDebugEnabled() ) log.debug( "changes must be flushed to space:
" + spaces[i] );
+ if ( log.isDebugEnabled() ) {
+ log.debug( "changes must be flushed to space: " + spaces[i] );
+ }
return true;
}
}
@@ -231,7 +260,7 @@
private void executeActions(List list) throws HibernateException {
int size = list.size();
for ( int i = 0; i < size; i++ ) {
- execute( (Executable) list.get(i) );
+ execute( ( Executable ) list.get( i ) );
}
list.clear();
session.getBatcher().executeBatch();
@@ -242,18 +271,18 @@
if ( executable.hasAfterTransactionCompletion() || lockQueryCache ) {
executions.add( executable );
}
- if (lockQueryCache) {
+ if ( lockQueryCache ) {
session.getFactory()
- .getUpdateTimestampsCache()
- .preinvalidate( executable.getPropertySpaces() );
+ .getUpdateTimestampsCache()
+ .preinvalidate( executable.getPropertySpaces() );
}
executable.execute();
}
private void prepareActions(List queue) throws HibernateException {
int size = queue.size();
- for ( int i=0; i<size; i++ ) {
- Executable executable = ( Executable ) queue.get(i);
+ for ( int i = 0; i < size; i++ ) {
+ Executable executable = ( Executable ) queue.get( i );
executable.beforeExecutions();
}
}
@@ -265,13 +294,13 @@
*/
public String toString() {
return new StringBuffer()
- .append("ActionQueue[insertions=").append(insertions)
- .append(" updates=").append(updates)
- .append(" deletions=").append(deletions)
- .append(" collectionCreations=").append(collectionCreations)
- .append(" collectionRemovals=").append(collectionRemovals)
- .append(" collectionUpdates=").append(collectionUpdates)
- .append("]")
+ .append( "ActionQueue[insertions=" ).append( insertions )
+ .append( " updates=" ).append( updates )
+ .append( " deletions=" ).append( deletions )
+ .append( " collectionCreations=" ).append( collectionCreations )
+ .append( " collectionRemovals=" ).append( collectionRemovals )
+ .append( " collectionUpdates=" ).append( collectionUpdates )
+ .append( "]" )
.toString();
}
@@ -319,8 +348,7 @@
}
/**
- * Provided the option is set ({@link org.hibernate.cfg.Environment#ORDER_INSERTS}),
- * then order the {@link #insertions} queue such that we group inserts
+ * Order the {@link #insertions} queue such that we group inserts
* against the same entity together (without violating constraints). The
* original order is generated by cascade order, which in turn is based on
* the directionality of foreign-keys. So even though we will be changing
@@ -329,73 +357,11 @@
* violations
*/
private void sortInsertActions() {
- // IMPLEMENTATION NOTES:
- //
- // The main data structure in this ordering algorithm is the
'positionToAction'
- // map. Essentially this can be thought of as an put-ordered map (the problem with
- // actually implementing it that way and doing away with the 'nameList' is
that
- // we'd end up having potential duplicate key values). 'positionToAction'
maitains
- // a mapping from a position within the 'nameList' structure to a "partial
queue"
- // of actions.
-
- HashMap positionToAction = new HashMap();
- List nameList = new ArrayList();
-
- loopInsertion: while( !insertions.isEmpty() ) {
- EntityInsertAction action = ( EntityInsertAction ) insertions.remove( 0 );
- String thisEntityName = action.getEntityName();
-
- // see if we have already encountered this entity-name...
- if ( ! nameList.contains( thisEntityName ) ) {
- // we have not, so create the proper entries in nameList and positionToAction
- ArrayList segmentedActionQueue = new ArrayList();
- segmentedActionQueue.add( action );
- nameList.add( thisEntityName );
- positionToAction.put( new Integer( nameList.indexOf( thisEntityName ) ),
segmentedActionQueue );
- }
- else {
- // we have seen it before, so we need to determine if this insert action is
- // is depenedent upon a previously processed action in terms of FK
- // relationships (this FK checking is done against the entity's property-state
- // associated with the action...)
- int lastPos = nameList.lastIndexOf( thisEntityName );
- Object[] states = action.getState();
- for ( int i = 0; i < states.length; i++ ) {
- for ( int j = 0; j < nameList.size(); j++ ) {
- ArrayList tmpList = ( ArrayList ) positionToAction.get( new Integer( j ) );
- for ( int k = 0; k < tmpList.size(); k++ ) {
- final EntityInsertAction checkAction = ( EntityInsertAction ) tmpList.get( k );
- if ( checkAction.getInstance() == states[i] && j > lastPos ) {
- // 'checkAction' is inserting an entity upon which 'action'
- // depends...
- // note: this is an assumption and may not be correct in the case of one-to-one
- ArrayList segmentedActionQueue = new ArrayList();
- segmentedActionQueue.add( action );
- nameList.add( thisEntityName );
- positionToAction.put(new Integer( nameList.lastIndexOf( thisEntityName ) ),
segmentedActionQueue );
- continue loopInsertion;
- }
- }
- }
- }
-
- ArrayList actionQueue = ( ArrayList ) positionToAction.get( new Integer( lastPos )
);
- actionQueue.add( action );
- }
- }
-
- // now iterate back through positionToAction map and move entityInsertAction back to
insertion list
- for ( int p = 0; p < nameList.size(); p++ ) {
- ArrayList actionQueue = ( ArrayList ) positionToAction.get( new Integer( p ) );
- Iterator itr = actionQueue.iterator();
- while ( itr.hasNext() ) {
- insertions.add( itr.next() );
- }
- }
+ new InsertActionSorter().sort();
}
public ArrayList cloneDeletions() {
- return (ArrayList) deletions.clone();
+ return ( ArrayList ) deletions.clone();
}
public void clearFromFlushNeededCheck(int previousCollectionRemovalSize) {
@@ -404,18 +370,18 @@
updates.clear();
// collection deletions are a special case since update() can add
// deletions of collections not loaded by the session.
- for ( int i = collectionRemovals.size()-1; i >= previousCollectionRemovalSize; i-- )
{
- collectionRemovals.remove(i);
+ for ( int i = collectionRemovals.size() - 1; i >= previousCollectionRemovalSize; i--
) {
+ collectionRemovals.remove( i );
}
}
public boolean hasAnyQueuedActions() {
return updates.size() > 0 ||
- insertions.size() > 0 ||
- deletions.size() > 0 ||
- collectionUpdates.size() > 0 ||
- collectionRemovals.size() > 0 ||
- collectionCreations.size() > 0;
+ insertions.size() > 0 ||
+ deletions.size() > 0 ||
+ collectionUpdates.size() > 0 ||
+ collectionRemovals.size() > 0 ||
+ collectionCreations.size() > 0;
}
/**
@@ -423,6 +389,7 @@
* action queue
*
* @param oos The stream to which the action queue should get written
+ *
* @throws IOException
*/
public void serialize(ObjectOutputStream oos) throws IOException {
@@ -476,11 +443,12 @@
* action queue
*
* @param ois The stream from which to read the action queue
+ *
* @throws IOException
*/
public static ActionQueue deserialize(
ObjectInputStream ois,
- SessionImplementor session) throws IOException, ClassNotFoundException {
+ SessionImplementor session) throws IOException, ClassNotFoundException {
log.trace( "deserializing action-queue" );
ActionQueue rtn = new ActionQueue( session );
@@ -528,4 +496,120 @@
return rtn;
}
+
+ /**
+ * Sorts the insert actions using more hashes.
+ *
+ * @author Jay Erb
+ */
+ private class InsertActionSorter {
+
+ // the mapping of entity names to their latest batch numbers.
+ private HashMap latestBatches = new HashMap();
+ private HashMap entityBatchNumber;
+
+ // the map of batch numbers to EntityInsertAction lists
+ private HashMap actionBatches = new HashMap();
+
+ public InsertActionSorter() {
+ //optimize the hash size to eliminate a rehash.
+ entityBatchNumber = new HashMap( insertions.size() + 1, 1.0f );
+ }
+
+ /**
+ * Sort the insert actions.
+ */
+ public void sort() {
+
+ // the list of entity names that indicate the batch number
+ for ( Iterator actionItr = insertions.iterator(); actionItr.hasNext(); ) {
+ EntityInsertAction action = ( EntityInsertAction ) actionItr.next();
+ // remove the current element from insertions. It will be added back later.
+ String entityName = action.getEntityName();
+
+ // the entity associated with the current action.
+ Object currentEntity = action.getInstance();
+
+ Integer batchNumber;
+ if ( latestBatches.containsKey( entityName ) ) {
+ // There is already an existing batch for this type of entity.
+ // Check to see if the latest batch is acceptable.
+ batchNumber = findBatchNumber( action, entityName );
+ }
+ else {
+ // add an entry for this type of entity.
+ // we can be assured that all referenced entities have already
+ // been processed,
+ // so specify that this entity is with the latest batch.
+ // doing the batch number before adding the name to the list is
+ // a faster way to get an accurate number.
+
+ batchNumber = new Integer( actionBatches.size() );
+ latestBatches.put( entityName, batchNumber );
+ }
+ entityBatchNumber.put( currentEntity, batchNumber );
+ addToBatch( batchNumber, action );
+ }
+ insertions.clear();
+
+ // now rebuild the insertions list. There is a batch for each entry in the name list.
+ for ( int i = 0; i < actionBatches.size(); i++ ) {
+ List batch = ( List ) actionBatches.get( new Integer( i ) );
+ for ( Iterator batchItr = batch.iterator(); batchItr.hasNext(); ) {
+ EntityInsertAction action = ( EntityInsertAction ) batchItr.next();
+ insertions.add( action );
+ }
+ }
+ }
+
+ /**
+ * Finds an acceptable batch for this entity to be a member.
+ */
+ private Integer findBatchNumber(EntityInsertAction action,
+ String entityName) {
+ // loop through all the associated entities and make sure they have been
+ // processed before the latest
+ // batch associated with this entity type.
+
+ // the current batch number is the latest batch for this entity type.
+ Integer latestBatchNumberForType = ( Integer ) latestBatches.get( entityName );
+
+ // loop through all the associations of the current entity and make sure that they are
processed
+ // before the current batch number
+ Object[] propertyValues = action.getState();
+ Type[] propertyTypes = action.getPersister().getClassMetadata()
+ .getPropertyTypes();
+
+ for ( int i = 0; i < propertyValues.length; i++ ) {
+ Object value = propertyValues[i];
+ Type type = propertyTypes[i];
+ if ( type.isEntityType() && value != null ) {
+ // find the batch number associated with the current association, if any.
+ Integer associationBatchNumber = ( Integer ) entityBatchNumber.get( value );
+ if ( associationBatchNumber != null && associationBatchNumber.compareTo(
latestBatchNumberForType ) > 0 ) {
+ // create a new batch for this type. The batch number is the number of current
batches.
+ latestBatchNumberForType = new Integer( actionBatches.size() );
+ latestBatches.put( entityName, latestBatchNumberForType );
+ // since this entity will now be processed in the latest possible batch,
+ // we can be assured that it will come after all other associations,
+ // there's not need to continue checking.
+ break;
+ }
+ }
+ }
+ return latestBatchNumberForType;
+ }
+
+ private void addToBatch(Integer batchNumber, EntityInsertAction action) {
+ List actions = ( List ) actionBatches.get( batchNumber );
+
+ if ( actions == null ) {
+ actions = new LinkedList();
+ actionBatches.put( batchNumber, actions );
+ }
+ actions.add( action );
+ }
+
+ }
+
}