Save This Page
Home » Hibernate-3.3.2.GA » org.hibernate » engine » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.engine;
   26   
   27   import java.io.IOException;
   28   import java.io.ObjectInputStream;
   29   import java.io.ObjectOutputStream;
   30   import java.io.Serializable;
   31   import java.util.ArrayList;
   32   import java.util.HashMap;
   33   import java.util.Iterator;
   34   import java.util.LinkedList;
   35   import java.util.List;
   36   import java.util.Set;
   37   
   38   import org.slf4j.Logger;
   39   import org.slf4j.LoggerFactory;
   40   
   41   import org.hibernate.AssertionFailure;
   42   import org.hibernate.HibernateException;
   43   import org.hibernate.action.BulkOperationCleanupAction;
   44   import org.hibernate.action.CollectionRecreateAction;
   45   import org.hibernate.action.CollectionRemoveAction;
   46   import org.hibernate.action.CollectionUpdateAction;
   47   import org.hibernate.action.EntityDeleteAction;
   48   import org.hibernate.action.EntityIdentityInsertAction;
   49   import org.hibernate.action.EntityInsertAction;
   50   import org.hibernate.action.EntityUpdateAction;
   51   import org.hibernate.action.Executable;
   52   import org.hibernate.cache.CacheException;
   53   import org.hibernate.type.Type;
   54   
   55   /**
   56    * Responsible for maintaining the queue of actions related to events.
   57    * </p>
   58    * The ActionQueue holds the DML operations queued as part of a session's
   59    * transactional-write-behind semantics.  DML operations are queued here
   60    * until a flush forces them to be executed against the database.
   61    *
   62    * @author Steve Ebersole
   63    */
   64   public class ActionQueue {
   65   
   66   	private static final Logger log = LoggerFactory.getLogger( ActionQueue.class );
   67   	private static final int INIT_QUEUE_LIST_SIZE = 5;
   68   
   69   	private SessionImplementor session;
   70   
   71   	// Object insertions, updates, and deletions have list semantics because
   72   	// they must happen in the right order so as to respect referential
   73   	// integrity
   74   	private ArrayList insertions;
   75   	private ArrayList deletions;
   76   	private ArrayList updates;
   77   	// Actually the semantics of the next three are really "Bag"
   78   	// Note that, unlike objects, collection insertions, updates,
   79   	// deletions are not really remembered between flushes. We
   80   	// just re-use the same Lists for convenience.
   81   	private ArrayList collectionCreations;
   82   	private ArrayList collectionUpdates;
   83   	private ArrayList collectionRemovals;
   84   
   85   	private ArrayList executions;
   86   
   87   	/**
   88   	 * Constructs an action queue bound to the given session.
   89   	 *
   90   	 * @param session The session "owning" this queue.
   91   	 */
   92   	public ActionQueue(SessionImplementor session) {
   93   		this.session = session;
   94   		init();
   95   	}
   96   
   97   	private void init() {
   98   		insertions = new ArrayList( INIT_QUEUE_LIST_SIZE );
   99   		deletions = new ArrayList( INIT_QUEUE_LIST_SIZE );
  100   		updates = new ArrayList( INIT_QUEUE_LIST_SIZE );
  101   
  102   		collectionCreations = new ArrayList( INIT_QUEUE_LIST_SIZE );
  103   		collectionRemovals = new ArrayList( INIT_QUEUE_LIST_SIZE );
  104   		collectionUpdates = new ArrayList( INIT_QUEUE_LIST_SIZE );
  105   
  106   		executions = new ArrayList( INIT_QUEUE_LIST_SIZE * 3 );
  107   	}
  108   
  109   	public void clear() {
  110   		updates.clear();
  111   		insertions.clear();
  112   		deletions.clear();
  113   
  114   		collectionCreations.clear();
  115   		collectionRemovals.clear();
  116   		collectionUpdates.clear();
  117   	}
  118   
  119   	public void addAction(EntityInsertAction action) {
  120   		insertions.add( action );
  121   	}
  122   
  123   	public void addAction(EntityDeleteAction action) {
  124   		deletions.add( action );
  125   	}
  126   
  127   	public void addAction(EntityUpdateAction action) {
  128   		updates.add( action );
  129   	}
  130   
  131   	public void addAction(CollectionRecreateAction action) {
  132   		collectionCreations.add( action );
  133   	}
  134   
  135   	public void addAction(CollectionRemoveAction action) {
  136   		collectionRemovals.add( action );
  137   	}
  138   
  139   	public void addAction(CollectionUpdateAction action) {
  140   		collectionUpdates.add( action );
  141   	}
  142   
  143   	public void addAction(EntityIdentityInsertAction insert) {
  144   		insertions.add( insert );
  145   	}
  146   
  147   	public void addAction(BulkOperationCleanupAction cleanupAction) {
  148   		// Add these directly to the executions queue
  149   		executions.add( cleanupAction );
  150   	}
  151   
  152   	/**
  153   	 * Perform all currently queued entity-insertion actions.
  154   	 *
  155   	 * @throws HibernateException error executing queued insertion actions.
  156   	 */
  157   	public void executeInserts() throws HibernateException {
  158   		executeActions( insertions );
  159   	}
  160   
  161   	/**
  162   	 * Perform all currently queued actions.
  163   	 *
  164   	 * @throws HibernateException error executing queued actions.
  165   	 */
  166   	public void executeActions() throws HibernateException {
  167   		executeActions( insertions );
  168   		executeActions( updates );
  169   		executeActions( collectionRemovals );
  170   		executeActions( collectionUpdates );
  171   		executeActions( collectionCreations );
  172   		executeActions( deletions );
  173   	}
  174   
  175   	/**
  176   	 * Prepares the internal action queues for execution.
  177   	 *
  178   	 * @throws HibernateException error preparing actions.
  179   	 */
  180   	public void prepareActions() throws HibernateException {
  181   		prepareActions( collectionRemovals );
  182   		prepareActions( collectionUpdates );
  183   		prepareActions( collectionCreations );
  184   	}
  185   
  186   	/**
  187   	 * Performs cleanup of any held cache softlocks.
  188   	 *
  189   	 * @param success Was the transaction successful.
  190   	 */
  191   	public void afterTransactionCompletion(boolean success) {
  192   		int size = executions.size();
  193   		final boolean invalidateQueryCache = session.getFactory().getSettings().isQueryCacheEnabled();
  194   		for ( int i = 0; i < size; i++ ) {
  195   			try {
  196   				Executable exec = ( Executable ) executions.get( i );
  197   				try {
  198   					exec.afterTransactionCompletion( success );
  199   				}
  200   				finally {
  201   					if ( invalidateQueryCache ) {
  202   						session.getFactory().getUpdateTimestampsCache().invalidate( exec.getPropertySpaces() );
  203   					}
  204   				}
  205   			}
  206   			catch ( CacheException ce ) {
  207   				log.error( "could not release a cache lock", ce );
  208   				// continue loop
  209   			}
  210   			catch ( Exception e ) {
  211   				throw new AssertionFailure( "Exception releasing cache locks", e );
  212   			}
  213   		}
  214   		executions.clear();
  215   	}
  216   
  217   	/**
  218   	 * Check whether the given tables/query-spaces are to be executed against
  219   	 * given the currently queued actions.
  220   	 *
  221   	 * @param tables The table/query-spaces to check.
  222   	 *
  223   	 * @return True if we contain pending actions against any of the given
  224   	 *         tables; false otherwise.
  225   	 */
  226   	public boolean areTablesToBeUpdated(Set tables) {
  227   		return areTablesToUpdated( updates, tables ) ||
  228   				areTablesToUpdated( insertions, tables ) ||
  229   				areTablesToUpdated( deletions, tables ) ||
  230   				areTablesToUpdated( collectionUpdates, tables ) ||
  231   				areTablesToUpdated( collectionCreations, tables ) ||
  232   				areTablesToUpdated( collectionRemovals, tables );
  233   	}
  234   
  235   	/**
  236   	 * Check whether any insertion or deletion actions are currently queued.
  237   	 *
  238   	 * @return True if insertions or deletions are currently queued; false otherwise.
  239   	 */
  240   	public boolean areInsertionsOrDeletionsQueued() {
  241   		return ( insertions.size() > 0 || deletions.size() > 0 );
  242   	}
  243   
  244   	private static boolean areTablesToUpdated(List executables, Set tablespaces) {
  245   		int size = executables.size();
  246   		for ( int j = 0; j < size; j++ ) {
  247   			Serializable[] spaces = ( ( Executable ) executables.get( j ) ).getPropertySpaces();
  248   			for ( int i = 0; i < spaces.length; i++ ) {
  249   				if ( tablespaces.contains( spaces[i] ) ) {
  250   					if ( log.isDebugEnabled() ) {
  251   						log.debug( "changes must be flushed to space: " + spaces[i] );
  252   					}
  253   					return true;
  254   				}
  255   			}
  256   		}
  257   		return false;
  258   	}
  259   
  260   	private void executeActions(List list) throws HibernateException {
  261   		int size = list.size();
  262   		for ( int i = 0; i < size; i++ ) {
  263   			execute( ( Executable ) list.get( i ) );
  264   		}
  265   		list.clear();
  266   		session.getBatcher().executeBatch();
  267   	}
  268   
  269   	public void execute(Executable executable) {
  270   		final boolean lockQueryCache = session.getFactory().getSettings().isQueryCacheEnabled();
  271   		if ( executable.hasAfterTransactionCompletion() || lockQueryCache ) {
  272   			executions.add( executable );
  273   		}
  274   		if ( lockQueryCache ) {
  275   			session.getFactory()
  276   					.getUpdateTimestampsCache()
  277   					.preinvalidate( executable.getPropertySpaces() );
  278   		}
  279   		executable.execute();
  280   	}
  281   
  282   	private void prepareActions(List queue) throws HibernateException {
  283   		int size = queue.size();
  284   		for ( int i = 0; i < size; i++ ) {
  285   			Executable executable = ( Executable ) queue.get( i );
  286   			executable.beforeExecutions();
  287   		}
  288   	}
  289   
  290   	/**
  291   	 * Returns a string representation of the object.
  292   	 *
  293   	 * @return a string representation of the object.
  294   	 */
  295   	public String toString() {
  296   		return new StringBuffer()
  297   				.append( "ActionQueue[insertions=" ).append( insertions )
  298   				.append( " updates=" ).append( updates )
  299   				.append( " deletions=" ).append( deletions )
  300   				.append( " collectionCreations=" ).append( collectionCreations )
  301   				.append( " collectionRemovals=" ).append( collectionRemovals )
  302   				.append( " collectionUpdates=" ).append( collectionUpdates )
  303   				.append( "]" )
  304   				.toString();
  305   	}
  306   
  307   	public int numberOfCollectionRemovals() {
  308   		return collectionRemovals.size();
  309   	}
  310   
  311   	public int numberOfCollectionUpdates() {
  312   		return collectionUpdates.size();
  313   	}
  314   
  315   	public int numberOfCollectionCreations() {
  316   		return collectionCreations.size();
  317   	}
  318   
  319   	public int numberOfDeletions() {
  320   		return deletions.size();
  321   	}
  322   
  323   	public int numberOfUpdates() {
  324   		return updates.size();
  325   	}
  326   
  327   	public int numberOfInsertions() {
  328   		return insertions.size();
  329   	}
  330   
  331   	public void sortCollectionActions() {
  332   		if ( session.getFactory().getSettings().isOrderUpdatesEnabled() ) {
  333   			//sort the updates by fk
  334   			java.util.Collections.sort( collectionCreations );
  335   			java.util.Collections.sort( collectionUpdates );
  336   			java.util.Collections.sort( collectionRemovals );
  337   		}
  338   	}
  339   
  340   	public void sortActions() {
  341   		if ( session.getFactory().getSettings().isOrderUpdatesEnabled() ) {
  342   			//sort the updates by pk
  343   			java.util.Collections.sort( updates );
  344   		}
  345   		if ( session.getFactory().getSettings().isOrderInsertsEnabled() ) {
  346   			sortInsertActions();
  347   		}
  348   	}
  349   
  350   	/**
  351   	 * Order the {@link #insertions} queue such that we group inserts
  352   	 * against the same entity together (without violating constraints).  The
  353   	 * original order is generated by cascade order, which in turn is based on
  354   	 * the directionality of foreign-keys.  So even though we will be changing
  355   	 * the ordering here, we need to make absolutely certain that we do not
  356   	 * circumvent this FK ordering to the extent of causing constraint
  357   	 * violations
  358   	 */
  359   	private void sortInsertActions() {
  360   		new InsertActionSorter().sort();
  361   	}
  362   
  363   	public ArrayList cloneDeletions() {
  364   		return ( ArrayList ) deletions.clone();
  365   	}
  366   
  367   	public void clearFromFlushNeededCheck(int previousCollectionRemovalSize) {
  368   		collectionCreations.clear();
  369   		collectionUpdates.clear();
  370   		updates.clear();
  371   		// collection deletions are a special case since update() can add
  372   		// deletions of collections not loaded by the session.
  373   		for ( int i = collectionRemovals.size() - 1; i >= previousCollectionRemovalSize; i-- ) {
  374   			collectionRemovals.remove( i );
  375   		}
  376   	}
  377   
  378   	public boolean hasAfterTransactionActions() {
  379   		return executions.size() > 0;
  380   	}
  381   
  382   	public boolean hasAnyQueuedActions() {
  383   		return updates.size() > 0 ||
  384   				insertions.size() > 0 ||
  385   				deletions.size() > 0 ||
  386   				collectionUpdates.size() > 0 ||
  387   				collectionRemovals.size() > 0 ||
  388   				collectionCreations.size() > 0;
  389   	}
  390   
  391   	/**
  392   	 * Used by the owning session to explicitly control serialization of the
  393   	 * action queue
  394   	 *
  395   	 * @param oos The stream to which the action queue should get written
  396   	 *
  397   	 * @throws IOException
  398   	 */
  399   	public void serialize(ObjectOutputStream oos) throws IOException {
  400   		log.trace( "serializing action-queue" );
  401   
  402   		int queueSize = insertions.size();
  403   		log.trace( "starting serialization of [" + queueSize + "] insertions entries" );
  404   		oos.writeInt( queueSize );
  405   		for ( int i = 0; i < queueSize; i++ ) {
  406   			oos.writeObject( insertions.get( i ) );
  407   		}
  408   
  409   		queueSize = deletions.size();
  410   		log.trace( "starting serialization of [" + queueSize + "] deletions entries" );
  411   		oos.writeInt( queueSize );
  412   		for ( int i = 0; i < queueSize; i++ ) {
  413   			oos.writeObject( deletions.get( i ) );
  414   		}
  415   
  416   		queueSize = updates.size();
  417   		log.trace( "starting serialization of [" + queueSize + "] updates entries" );
  418   		oos.writeInt( queueSize );
  419   		for ( int i = 0; i < queueSize; i++ ) {
  420   			oos.writeObject( updates.get( i ) );
  421   		}
  422   
  423   		queueSize = collectionUpdates.size();
  424   		log.trace( "starting serialization of [" + queueSize + "] collectionUpdates entries" );
  425   		oos.writeInt( queueSize );
  426   		for ( int i = 0; i < queueSize; i++ ) {
  427   			oos.writeObject( collectionUpdates.get( i ) );
  428   		}
  429   
  430   		queueSize = collectionRemovals.size();
  431   		log.trace( "starting serialization of [" + queueSize + "] collectionRemovals entries" );
  432   		oos.writeInt( queueSize );
  433   		for ( int i = 0; i < queueSize; i++ ) {
  434   			oos.writeObject( collectionRemovals.get( i ) );
  435   		}
  436   
  437   		queueSize = collectionCreations.size();
  438   		log.trace( "starting serialization of [" + queueSize + "] collectionCreations entries" );
  439   		oos.writeInt( queueSize );
  440   		for ( int i = 0; i < queueSize; i++ ) {
  441   			oos.writeObject( collectionCreations.get( i ) );
  442   		}
  443   	}
  444   
  445   	/**
  446   	 * Used by the owning session to explicitly control deserialization of the
  447   	 * action queue
  448   	 *
  449   	 * @param ois The stream from which to read the action queue
  450   	 *
  451   	 * @throws IOException
  452   	 */
  453   	public static ActionQueue deserialize(
  454   			ObjectInputStream ois,
  455   			SessionImplementor session) throws IOException, ClassNotFoundException {
  456   		log.trace( "deserializing action-queue" );
  457   		ActionQueue rtn = new ActionQueue( session );
  458   
  459   		int queueSize = ois.readInt();
  460   		log.trace( "starting deserialization of [" + queueSize + "] insertions entries" );
  461   		rtn.insertions = new ArrayList( queueSize );
  462   		for ( int i = 0; i < queueSize; i++ ) {
  463   			rtn.insertions.add( ois.readObject() );
  464   		}
  465   
  466   		queueSize = ois.readInt();
  467   		log.trace( "starting deserialization of [" + queueSize + "] deletions entries" );
  468   		rtn.deletions = new ArrayList( queueSize );
  469   		for ( int i = 0; i < queueSize; i++ ) {
  470   			rtn.deletions.add( ois.readObject() );
  471   		}
  472   
  473   		queueSize = ois.readInt();
  474   		log.trace( "starting deserialization of [" + queueSize + "] updates entries" );
  475   		rtn.updates = new ArrayList( queueSize );
  476   		for ( int i = 0; i < queueSize; i++ ) {
  477   			rtn.updates.add( ois.readObject() );
  478   		}
  479   
  480   		queueSize = ois.readInt();
  481   		log.trace( "starting deserialization of [" + queueSize + "] collectionUpdates entries" );
  482   		rtn.collectionUpdates = new ArrayList( queueSize );
  483   		for ( int i = 0; i < queueSize; i++ ) {
  484   			rtn.collectionUpdates.add( ois.readObject() );
  485   		}
  486   
  487   		queueSize = ois.readInt();
  488   		log.trace( "starting deserialization of [" + queueSize + "] collectionRemovals entries" );
  489   		rtn.collectionRemovals = new ArrayList( queueSize );
  490   		for ( int i = 0; i < queueSize; i++ ) {
  491   			rtn.collectionRemovals.add( ois.readObject() );
  492   		}
  493   
  494   		queueSize = ois.readInt();
  495   		log.trace( "starting deserialization of [" + queueSize + "] collectionCreations entries" );
  496   		rtn.collectionCreations = new ArrayList( queueSize );
  497   		for ( int i = 0; i < queueSize; i++ ) {
  498   			rtn.collectionCreations.add( ois.readObject() );
  499   		}
  500   		return rtn;
  501   	}
  502   
  503   
  504   	/**
  505   	 * Sorts the insert actions using more hashes.
  506   	 *
  507   	 * @author Jay Erb
  508   	 */
  509   	private class InsertActionSorter {
  510   
  511   		// the mapping of entity names to their latest batch numbers.
  512   		private HashMap latestBatches = new HashMap();
  513   		private HashMap entityBatchNumber;
  514   
  515   		// the map of batch numbers to EntityInsertAction lists
  516   		private HashMap actionBatches = new HashMap();
  517   
  518   		public InsertActionSorter() {
  519   			//optimize the hash size to eliminate a rehash.
  520   			entityBatchNumber = new HashMap( insertions.size() + 1, 1.0f );
  521   		}
  522   
  523   		/**
  524   		 * Sort the insert actions.
  525   		 */
  526   		public void sort() {
  527   
  528   			// the list of entity names that indicate the batch number
  529   			for ( Iterator actionItr = insertions.iterator(); actionItr.hasNext(); ) {
  530   				EntityInsertAction action = ( EntityInsertAction ) actionItr.next();
  531   				// remove the current element from insertions. It will be added back later.
  532   				String entityName = action.getEntityName();
  533   
  534   				// the entity associated with the current action.
  535   				Object currentEntity = action.getInstance();
  536   
  537   				Integer batchNumber;
  538   				if ( latestBatches.containsKey( entityName ) ) {
  539   					// There is already an existing batch for this type of entity.
  540   					// Check to see if the latest batch is acceptable.
  541   					batchNumber = findBatchNumber( action, entityName );
  542   				}
  543   				else {
  544   					// add an entry for this type of entity.
  545   					// we can be assured that all referenced entities have already
  546   					// been processed,
  547   					// so specify that this entity is with the latest batch.
  548   					// doing the batch number before adding the name to the list is
  549   					// a faster way to get an accurate number.
  550   
  551   					batchNumber = new Integer( actionBatches.size() );
  552   					latestBatches.put( entityName, batchNumber );
  553   				}
  554   				entityBatchNumber.put( currentEntity, batchNumber );
  555   				addToBatch( batchNumber, action );
  556   			}
  557   			insertions.clear();
  558   
  559   			// now rebuild the insertions list. There is a batch for each entry in the name list.
  560   			for ( int i = 0; i < actionBatches.size(); i++ ) {
  561   				List batch = ( List ) actionBatches.get( new Integer( i ) );
  562   				for ( Iterator batchItr = batch.iterator(); batchItr.hasNext(); ) {
  563   					EntityInsertAction action = ( EntityInsertAction ) batchItr.next();
  564   					insertions.add( action );
  565   				}
  566   			}
  567   		}
  568   
  569   		/**
  570   		 * Finds an acceptable batch for this entity to be a member.
  571   		 */
  572   		private Integer findBatchNumber(EntityInsertAction action,
  573   										String entityName) {
  574   			// loop through all the associated entities and make sure they have been
  575   			// processed before the latest
  576   			// batch associated with this entity type.
  577   
  578   			// the current batch number is the latest batch for this entity type.
  579   			Integer latestBatchNumberForType = ( Integer ) latestBatches.get( entityName );
  580   
  581   			// loop through all the associations of the current entity and make sure that they are processed
  582   			// before the current batch number
  583   			Object[] propertyValues = action.getState();
  584   			Type[] propertyTypes = action.getPersister().getClassMetadata()
  585   					.getPropertyTypes();
  586   
  587   			for ( int i = 0; i < propertyValues.length; i++ ) {
  588   				Object value = propertyValues[i];
  589   				Type type = propertyTypes[i];
  590   				if ( type.isEntityType() && value != null ) {
  591   					// find the batch number associated with the current association, if any.
  592   					Integer associationBatchNumber = ( Integer ) entityBatchNumber.get( value );
  593   					if ( associationBatchNumber != null && associationBatchNumber.compareTo( latestBatchNumberForType ) > 0 ) {
  594   						// create a new batch for this type. The batch number is the number of current batches.
  595   						latestBatchNumberForType = new Integer( actionBatches.size() );
  596   						latestBatches.put( entityName, latestBatchNumberForType );
  597   						// since this entity will now be processed in the latest possible batch,
  598   						// we can be assured that it will come after all other associations,
  599   						// there's not need to continue checking.
  600   						break;
  601   					}
  602   				}
  603   			}
  604   			return latestBatchNumberForType;
  605   		}
  606   
  607   		private void addToBatch(Integer batchNumber, EntityInsertAction action) {
  608   			List actions = ( List ) actionBatches.get( batchNumber );
  609   
  610   			if ( actions == null ) {
  611   				actions = new LinkedList();
  612   				actionBatches.put( batchNumber, actions );
  613   			}
  614   			actions.add( action );
  615   		}
  616   
  617   	}
  618   
  619   }

Save This Page
Home » Hibernate-3.3.2.GA » org.hibernate » engine » [javadoc | source]