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 }