1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.transaction.support;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.Serializable;
22 import java.util.Iterator;
23 import java.util.List;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import org.springframework.core.Constants;
29 import org.springframework.transaction.IllegalTransactionStateException;
30 import org.springframework.transaction.InvalidTimeoutException;
31 import org.springframework.transaction.NestedTransactionNotSupportedException;
32 import org.springframework.transaction.PlatformTransactionManager;
33 import org.springframework.transaction.TransactionDefinition;
34 import org.springframework.transaction.TransactionException;
35 import org.springframework.transaction.TransactionStatus;
36 import org.springframework.transaction.TransactionSuspensionNotSupportedException;
37 import org.springframework.transaction.UnexpectedRollbackException;
38
39 /**
40 * Abstract base class that implements Spring's standard transaction workflow,
41 * serving as basis for concrete platform transaction managers like
42 * {@link org.springframework.transaction.jta.JtaTransactionManager} and
43 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
44 *
45 * <p>This base class provides the following workflow handling:
46 * <ul>
47 * <li>determines if there is an existing transaction;
48 * <li>applies the appropriate propagation behavior;
49 * <li>suspends and resumes transactions if necessary;
50 * <li>checks the rollback-only flag on commit;
51 * <li>applies the appropriate modification on rollback
52 * (actual rollback or setting rollback-only);
53 * <li>triggers registered synchronization callbacks
54 * (if transaction synchronization is active).
55 * </ul>
56 *
57 * <p>Subclasses have to implement specific template methods for specific
58 * states of a transaction, e.g.: begin, suspend, resume, commit, rollback.
59 * The most important of them are abstract and must be provided by a concrete
60 * implementation; for the rest, defaults are provided, so overriding is optional.
61 *
62 * <p>Transaction synchronization is a generic mechanism for registering callbacks
63 * that get invoked at transaction completion time. This is mainly used internally
64 * by the data access support classes for JDBC, Hibernate, JDO, etc when running
65 * within a JTA transaction: They register resources that are opened within the
66 * transaction for closing at transaction completion time, allowing e.g. for reuse
67 * of the same Hibernate Session within the transaction. The same mechanism can
68 * also be leveraged for custom synchronization needs in an application.
69 *
70 * <p>The state of this class is serializable, to allow for serializing the
71 * transaction strategy along with proxies that carry a transaction interceptor.
72 * It is up to subclasses if they wish to make their state to be serializable too.
73 * They should implement the <code>java.io.Serializable</code> marker interface in
74 * that case, and potentially a private <code>readObject()</code> method (according
75 * to Java serialization rules) if they need to restore any transient state.
76 *
77 * @author Juergen Hoeller
78 * @since 28.03.2003
79 * @see #setTransactionSynchronization
80 * @see TransactionSynchronizationManager
81 * @see org.springframework.transaction.jta.JtaTransactionManager
82 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
83 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
84 */
85 public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
86
87 /**
88 * Always activate transaction synchronization, even for "empty" transactions
89 * that result from PROPAGATION_SUPPORTS with no existing backend transaction.
90 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS
91 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED
92 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER
93 */
94 public static final int SYNCHRONIZATION_ALWAYS = 0;
95
96 /**
97 * Activate transaction synchronization only for actual transactions,
98 * that is, not for empty ones that result from PROPAGATION_SUPPORTS with
99 * no existing backend transaction.
100 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED
101 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY
102 * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW
103 */
104 public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;
105
106 /**
107 * Never active transaction synchronization, not even for actual transactions.
108 */
109 public static final int SYNCHRONIZATION_NEVER = 2;
110
111
112 /** Constants instance for AbstractPlatformTransactionManager */
113 private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class);
114
115
116 /** Transient to optimize serialization */
117 protected transient Log logger = LogFactory.getLog(getClass());
118
119 private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
120
121 private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT;
122
123 private boolean nestedTransactionAllowed = false;
124
125 private boolean validateExistingTransaction = false;
126
127 private boolean globalRollbackOnParticipationFailure = true;
128
129 private boolean failEarlyOnGlobalRollbackOnly = false;
130
131 private boolean rollbackOnCommitFailure = false;
132
133
134 /**
135 * Set the transaction synchronization by the name of the corresponding constant
136 * in this class, e.g. "SYNCHRONIZATION_ALWAYS".
137 * @param constantName name of the constant
138 * @see #SYNCHRONIZATION_ALWAYS
139 */
140 public final void setTransactionSynchronizationName(String constantName) {
141 setTransactionSynchronization(constants.asNumber(constantName).intValue());
142 }
143
144 /**
145 * Set when this transaction manager should activate the thread-bound
146 * transaction synchronization support. Default is "always".
147 * <p>Note that transaction synchronization isn't supported for
148 * multiple concurrent transactions by different transaction managers.
149 * Only one transaction manager is allowed to activate it at any time.
150 * @see #SYNCHRONIZATION_ALWAYS
151 * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
152 * @see #SYNCHRONIZATION_NEVER
153 * @see TransactionSynchronizationManager
154 * @see TransactionSynchronization
155 */
156 public final void setTransactionSynchronization(int transactionSynchronization) {
157 this.transactionSynchronization = transactionSynchronization;
158 }
159
160 /**
161 * Return if this transaction manager should activate the thread-bound
162 * transaction synchronization support.
163 */
164 public final int getTransactionSynchronization() {
165 return this.transactionSynchronization;
166 }
167
168 /**
169 * Specify the default timeout that this transaction manager should apply
170 * if there is no timeout specified at the transaction level, in seconds.
171 * <p>Default is the underlying transaction infrastructure's default timeout,
172 * e.g. typically 30 seconds in case of a JTA provider, indicated by the
173 * <code>TransactionDefinition.TIMEOUT_DEFAULT</code> value.
174 * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT
175 */
176 public final void setDefaultTimeout(int defaultTimeout) {
177 if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) {
178 throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout);
179 }
180 this.defaultTimeout = defaultTimeout;
181 }
182
183 /**
184 * Return the default timeout that this transaction manager should apply
185 * if there is no timeout specified at the transaction level, in seconds.
186 * <p>Returns <code>TransactionDefinition.TIMEOUT_DEFAULT</code> to indicate
187 * the underlying transaction infrastructure's default timeout.
188 */
189 public final int getDefaultTimeout() {
190 return this.defaultTimeout;
191 }
192
193 /**
194 * Set whether nested transactions are allowed. Default is "false".
195 * <p>Typically initialized with an appropriate default by the
196 * concrete transaction manager subclass.
197 */
198 public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) {
199 this.nestedTransactionAllowed = nestedTransactionAllowed;
200 }
201
202 /**
203 * Return whether nested transactions are allowed.
204 */
205 public final boolean isNestedTransactionAllowed() {
206 return this.nestedTransactionAllowed;
207 }
208
209 /**
210 * Set whether existing transactions should be validated before participating
211 * in them.
212 * <p>When participating in an existing transaction (e.g. with
213 * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing
214 * transaction), this outer transaction's characteristics will apply even
215 * to the inner transaction scope. Validation will detect incompatible
216 * isolation level and read-only settings on the inner transaction definition
217 * and reject participation accordingly through throwing a corresponding exception.
218 * <p>Default is "false", leniently ignoring inner transaction settings,
219 * simply overriding them with the outer transaction's characteristics.
220 * Switch this flag to "true" in order to enforce strict validation.
221 */
222 public final void setValidateExistingTransaction(boolean validateExistingTransaction) {
223 this.validateExistingTransaction = validateExistingTransaction;
224 }
225
226 /**
227 * Return whether existing transactions should be validated before participating
228 * in them.
229 */
230 public final boolean isValidateExistingTransaction() {
231 return this.validateExistingTransaction;
232 }
233
234 /**
235 * Set whether to globally mark an existing transaction as rollback-only
236 * after a participating transaction failed.
237 * <p>Default is "true": If a participating transaction (e.g. with
238 * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing
239 * transaction) fails, the transaction will be globally marked as rollback-only.
240 * The only possible outcome of such a transaction is a rollback: The
241 * transaction originator <i>cannot</i> make the transaction commit anymore.
242 * <p>Switch this to "false" to let the transaction originator make the rollback
243 * decision. If a participating transaction fails with an exception, the caller
244 * can still decide to continue with a different path within the transaction.
245 * However, note that this will only work as long as all participating resources
246 * are capable of continuing towards a transaction commit even after a data access
247 * failure: This is generally not the case for a Hibernate Session, for example;
248 * neither is it for a sequence of JDBC insert/update/delete operations.
249 * <p><b>Note:</b>This flag only applies to an explicit rollback attempt for a
250 * subtransaction, typically caused by an exception thrown by a data access operation
251 * (where TransactionInterceptor will trigger a <code>PlatformTransactionManager.rollback()</code>
252 * call according to a rollback rule). If the flag is off, the caller can handle the exception
253 * and decide on a rollback, independent of the rollback rules of the subtransaction.
254 * This flag does, however, <i>not</i> apply to explicit <code>setRollbackOnly</code>
255 * calls on a <code>TransactionStatus</code>, which will always cause an eventual
256 * global rollback (as it might not throw an exception after the rollback-only call).
257 * <p>The recommended solution for handling failure of a subtransaction
258 * is a "nested transaction", where the global transaction can be rolled
259 * back to a savepoint taken at the beginning of the subtransaction.
260 * PROPAGATION_NESTED provides exactly those semantics; however, it will
261 * only work when nested transaction support is available. This is the case
262 * with DataSourceTransactionManager, but not with JtaTransactionManager.
263 * @see #setNestedTransactionAllowed
264 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
265 * @see org.springframework.transaction.jta.JtaTransactionManager
266 */
267 public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) {
268 this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure;
269 }
270
271 /**
272 * Return whether to globally mark an existing transaction as rollback-only
273 * after a participating transaction failed.
274 */
275 public final boolean isGlobalRollbackOnParticipationFailure() {
276 return this.globalRollbackOnParticipationFailure;
277 }
278
279 /**
280 * Set whether to fail early in case of the transaction being globally marked
281 * as rollback-only.
282 * <p>Default is "false", only causing an UnexpectedRollbackException at the
283 * outermost transaction boundary. Switch this flag on to cause an
284 * UnexpectedRollbackException as early as the global rollback-only marker
285 * has been first detected, even from within an inner transaction boundary.
286 * <p>Note that, as of Spring 2.0, the fail-early behavior for global
287 * rollback-only markers has been unified: All transaction managers will by
288 * default only cause UnexpectedRollbackException at the outermost transaction
289 * boundary. This allows, for example, to continue unit tests even after an
290 * operation failed and the transaction will never be completed. All transaction
291 * managers will only fail earlier if this flag has explicitly been set to "true".
292 * @see org.springframework.transaction.UnexpectedRollbackException
293 */
294 public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) {
295 this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly;
296 }
297
298 /**
299 * Return whether to fail early in case of the transaction being globally marked
300 * as rollback-only.
301 */
302 public final boolean isFailEarlyOnGlobalRollbackOnly() {
303 return this.failEarlyOnGlobalRollbackOnly;
304 }
305
306 /**
307 * Set whether <code>doRollback</code> should be performed on failure of the
308 * <code>doCommit</code> call. Typically not necessary and thus to be avoided,
309 * as it can potentially override the commit exception with a subsequent
310 * rollback exception.
311 * <p>Default is "false".
312 * @see #doCommit
313 * @see #doRollback
314 */
315 public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) {
316 this.rollbackOnCommitFailure = rollbackOnCommitFailure;
317 }
318
319 /**
320 * Return whether <code>doRollback</code> should be performed on failure of the
321 * <code>doCommit</code> call.
322 */
323 public final boolean isRollbackOnCommitFailure() {
324 return this.rollbackOnCommitFailure;
325 }
326
327
328 //---------------------------------------------------------------------
329 // Implementation of PlatformTransactionManager
330 //---------------------------------------------------------------------
331
332 /**
333 * This implementation handles propagation behavior. Delegates to
334 * <code>doGetTransaction</code>, <code>isExistingTransaction</code>
335 * and <code>doBegin</code>.
336 * @see #doGetTransaction
337 * @see #isExistingTransaction
338 * @see #doBegin
339 */
340 public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
341 Object transaction = doGetTransaction();
342
343 // Cache debug flag to avoid repeated checks.
344 boolean debugEnabled = logger.isDebugEnabled();
345
346 if (definition == null) {
347 // Use defaults if no transaction definition given.
348 definition = new DefaultTransactionDefinition();
349 }
350
351 if (isExistingTransaction(transaction)) {
352 // Existing transaction found -> check propagation behavior to find out how to behave.
353 return handleExistingTransaction(definition, transaction, debugEnabled);
354 }
355
356 // Check definition settings for new transaction.
357 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
358 throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
359 }
360
361 // No existing transaction found -> check propagation behavior to find out how to proceed.
362 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
363 throw new IllegalTransactionStateException(
364 "No existing transaction found for transaction marked with propagation 'mandatory'");
365 }
366 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
367 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
368 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
369 SuspendedResourcesHolder suspendedResources = suspend(null);
370 if (debugEnabled) {
371 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
372 }
373 try {
374 doBegin(transaction, definition);
375 }
376 catch (RuntimeException ex) {
377 resume(null, suspendedResources);
378 throw ex;
379 }
380 catch (Error err) {
381 resume(null, suspendedResources);
382 throw err;
383 }
384 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
385 return newTransactionStatus(
386 definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
387 }
388 else {
389 // Create "empty" transaction: no actual transaction, but potentially synchronization.
390 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
391 return newTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
392 }
393 }
394
395 /**
396 * Create a TransactionStatus for an existing transaction.
397 */
398 private TransactionStatus handleExistingTransaction(
399 TransactionDefinition definition, Object transaction, boolean debugEnabled)
400 throws TransactionException {
401
402 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
403 throw new IllegalTransactionStateException(
404 "Existing transaction found for transaction marked with propagation 'never'");
405 }
406
407 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
408 if (debugEnabled) {
409 logger.debug("Suspending current transaction");
410 }
411 Object suspendedResources = suspend(transaction);
412 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
413 return newTransactionStatus(
414 definition, null, false, newSynchronization, debugEnabled, suspendedResources);
415 }
416
417 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
418 if (debugEnabled) {
419 logger.debug("Suspending current transaction, creating new transaction with name [" +
420 definition.getName() + "]");
421 }
422 SuspendedResourcesHolder suspendedResources = suspend(transaction);
423 try {
424 doBegin(transaction, definition);
425 }
426 catch (RuntimeException beginEx) {
427 resumeAfterBeginException(transaction, suspendedResources, beginEx);
428 throw beginEx;
429 }
430 catch (Error beginErr) {
431 resumeAfterBeginException(transaction, suspendedResources, beginErr);
432 throw beginErr;
433 }
434 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
435 return newTransactionStatus(
436 definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
437 }
438
439 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
440 if (!isNestedTransactionAllowed()) {
441 throw new NestedTransactionNotSupportedException(
442 "Transaction manager does not allow nested transactions by default - " +
443 "specify 'nestedTransactionAllowed' property with value 'true'");
444 }
445 if (debugEnabled) {
446 logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
447 }
448 if (useSavepointForNestedTransaction()) {
449 // Create savepoint within existing Spring-managed transaction,
450 // through the SavepointManager API implemented by TransactionStatus.
451 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
452 DefaultTransactionStatus status =
453 newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
454 status.createAndHoldSavepoint();
455 return status;
456 }
457 else {
458 // Nested transaction through nested begin and commit/rollback calls.
459 // Usually only for JTA: Spring synchronization might get activated here
460 // in case of a pre-existing JTA transaction.
461 doBegin(transaction, definition);
462 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
463 return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
464 }
465 }
466
467 // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
468 if (debugEnabled) {
469 logger.debug("Participating in existing transaction");
470 }
471 if (isValidateExistingTransaction()) {
472 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
473 Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
474 if (currentIsolationLevel == null || currentIsolationLevel.intValue() != definition.getIsolationLevel()) {
475 Constants isoConstants = DefaultTransactionDefinition.constants;
476 throw new IllegalTransactionStateException("Participating transaction with definition [" +
477 definition + "] specifies isolation level which is incompatible with existing transaction: " +
478 (currentIsolationLevel != null ?
479 isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
480 "(unknown)"));
481 }
482 }
483 if (!definition.isReadOnly()) {
484 if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
485 throw new IllegalTransactionStateException("Participating transaction with definition [" +
486 definition + "] is not marked as read-only but existing transaction is");
487 }
488 }
489 }
490 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
491 return newTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
492 }
493
494 /**
495 * Create a new TransactionStatus for the given arguments,
496 * initializing transaction synchronization as appropriate.
497 */
498 protected DefaultTransactionStatus newTransactionStatus(
499 TransactionDefinition definition, Object transaction, boolean newTransaction,
500 boolean newSynchronization, boolean debug, Object suspendedResources) {
501
502 boolean actualNewSynchronization = newSynchronization &&
503 !TransactionSynchronizationManager.isSynchronizationActive();
504 if (actualNewSynchronization) {
505 TransactionSynchronizationManager.setActualTransactionActive(transaction != null);
506 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
507 (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ?
508 new Integer(definition.getIsolationLevel()) : null);
509 TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
510 TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
511 TransactionSynchronizationManager.initSynchronization();
512 }
513 return new DefaultTransactionStatus(
514 transaction, newTransaction, actualNewSynchronization,
515 definition.isReadOnly(), debug, suspendedResources);
516 }
517
518 /**
519 * Determine the actual timeout to use for the given definition.
520 * Will fall back to this manager's default timeout if the
521 * transaction definition doesn't specify a non-default value.
522 * @param definition the transaction definition
523 * @return the actual timeout to use
524 * @see org.springframework.transaction.TransactionDefinition#getTimeout()
525 * @see #setDefaultTimeout
526 */
527 protected int determineTimeout(TransactionDefinition definition) {
528 if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
529 return definition.getTimeout();
530 }
531 return this.defaultTimeout;
532 }
533
534
535 /**
536 * Suspend the given transaction. Suspends transaction synchronization first,
537 * then delegates to the <code>doSuspend</code> template method.
538 * @param transaction the current transaction object
539 * (or <code>null</code> to just suspend active synchronizations, if any)
540 * @return an object that holds suspended resources
541 * (or <code>null</code> if neither transaction nor synchronization active)
542 * @see #doSuspend
543 * @see #resume
544 */
545 protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
546 if (TransactionSynchronizationManager.isSynchronizationActive()) {
547 List suspendedSynchronizations = doSuspendSynchronization();
548 try {
549 Object suspendedResources = null;
550 if (transaction != null) {
551 suspendedResources = doSuspend(transaction);
552 }
553 String name = TransactionSynchronizationManager.getCurrentTransactionName();
554 TransactionSynchronizationManager.setCurrentTransactionName(null);
555 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
556 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
557 Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
558 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
559 boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
560 TransactionSynchronizationManager.setActualTransactionActive(false);
561 return new SuspendedResourcesHolder(
562 suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
563 }
564 catch (RuntimeException ex) {
565 // doSuspend failed - original transaction is still active...
566 doResumeSynchronization(suspendedSynchronizations);
567 throw ex;
568 }
569 catch (Error err) {
570 // doSuspend failed - original transaction is still active...
571 doResumeSynchronization(suspendedSynchronizations);
572 throw err;
573 }
574 }
575 else if (transaction != null) {
576 // Transaction active but no synchronization active.
577 Object suspendedResources = doSuspend(transaction);
578 return new SuspendedResourcesHolder(suspendedResources);
579 }
580 else {
581 // Neither transaction nor synchronization active.
582 return null;
583 }
584 }
585
586 /**
587 * Resume the given transaction. Delegates to the <code>doResume</code>
588 * template method first, then resuming transaction synchronization.
589 * @param transaction the current transaction object
590 * @param resourcesHolder the object that holds suspended resources,
591 * as returned by <code>suspend</code> (or <code>null</code> to just
592 * resume synchronizations, if any)
593 * @see #doResume
594 * @see #suspend
595 */
596 protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder)
597 throws TransactionException {
598
599 if (resourcesHolder != null) {
600 Object suspendedResources = resourcesHolder.suspendedResources;
601 if (suspendedResources != null) {
602 doResume(transaction, suspendedResources);
603 }
604 List suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
605 if (suspendedSynchronizations != null) {
606 TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
607 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
608 TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
609 TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
610 doResumeSynchronization(suspendedSynchronizations);
611 }
612 }
613 }
614
615 /**
616 * Resume outer transaction after inner transaction begin failed.
617 */
618 private void resumeAfterBeginException(
619 Object transaction, SuspendedResourcesHolder suspendedResources, Throwable beginEx) {
620
621 String exMessage = "Inner transaction begin exception overridden by outer transaction resume exception";
622 try {
623 resume(transaction, suspendedResources);
624 }
625 catch (RuntimeException resumeEx) {
626 logger.error(exMessage, beginEx);
627 throw resumeEx;
628 }
629 catch (Error resumeErr) {
630 logger.error(exMessage, beginEx);
631 throw resumeErr;
632 }
633 }
634
635 /**
636 * Suspend all current synchronizations and deactivate transaction
637 * synchronization for the current thread.
638 * @return the List of suspended TransactionSynchronization objects
639 */
640 private List doSuspendSynchronization() {
641 List suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations();
642 for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) {
643 ((TransactionSynchronization) it.next()).suspend();
644 }
645 TransactionSynchronizationManager.clearSynchronization();
646 return suspendedSynchronizations;
647 }
648
649 /**
650 * Reactivate transaction synchronization for the current thread
651 * and resume all given synchronizations.
652 * @param suspendedSynchronizations List of TransactionSynchronization objects
653 */
654 private void doResumeSynchronization(List suspendedSynchronizations) {
655 TransactionSynchronizationManager.initSynchronization();
656 for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) {
657 TransactionSynchronization synchronization = (TransactionSynchronization) it.next();
658 synchronization.resume();
659 TransactionSynchronizationManager.registerSynchronization(synchronization);
660 }
661 }
662
663
664 /**
665 * This implementation of commit handles participating in existing
666 * transactions and programmatic rollback requests.
667 * Delegates to <code>isRollbackOnly</code>, <code>doCommit</code>
668 * and <code>rollback</code>.
669 * @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
670 * @see #doCommit
671 * @see #rollback
672 */
673 public final void commit(TransactionStatus status) throws TransactionException {
674 if (status.isCompleted()) {
675 throw new IllegalTransactionStateException(
676 "Transaction is already completed - do not call commit or rollback more than once per transaction");
677 }
678
679 DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
680 if (defStatus.isLocalRollbackOnly()) {
681 if (defStatus.isDebug()) {
682 logger.debug("Transactional code has requested rollback");
683 }
684 processRollback(defStatus);
685 return;
686 }
687 if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
688 if (defStatus.isDebug()) {
689 logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
690 }
691 processRollback(defStatus);
692 // Throw UnexpectedRollbackException only at outermost transaction boundary
693 // or if explicitly asked to.
694 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
695 throw new UnexpectedRollbackException(
696 "Transaction rolled back because it has been marked as rollback-only");
697 }
698 return;
699 }
700
701 processCommit(defStatus);
702 }
703
704 /**
705 * Process an actual commit.
706 * Rollback-only flags have already been checked and applied.
707 * @param status object representing the transaction
708 * @throws TransactionException in case of commit failure
709 */
710 private void processCommit(DefaultTransactionStatus status) throws TransactionException {
711 try {
712 boolean beforeCompletionInvoked = false;
713 try {
714 prepareForCommit(status);
715 triggerBeforeCommit(status);
716 triggerBeforeCompletion(status);
717 beforeCompletionInvoked = true;
718 boolean globalRollbackOnly = false;
719 if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
720 globalRollbackOnly = status.isGlobalRollbackOnly();
721 }
722 if (status.hasSavepoint()) {
723 if (status.isDebug()) {
724 logger.debug("Releasing transaction savepoint");
725 }
726 status.releaseHeldSavepoint();
727 }
728 else if (status.isNewTransaction()) {
729 if (status.isDebug()) {
730 logger.debug("Initiating transaction commit");
731 }
732 doCommit(status);
733 }
734 // Throw UnexpectedRollbackException if we have a global rollback-only
735 // marker but still didn't get a corresponding exception from commit.
736 if (globalRollbackOnly) {
737 throw new UnexpectedRollbackException(
738 "Transaction silently rolled back because it has been marked as rollback-only");
739 }
740 }
741 catch (UnexpectedRollbackException ex) {
742 // can only be caused by doCommit
743 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
744 throw ex;
745 }
746 catch (TransactionException ex) {
747 // can only be caused by doCommit
748 if (isRollbackOnCommitFailure()) {
749 doRollbackOnCommitException(status, ex);
750 }
751 else {
752 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
753 }
754 throw ex;
755 }
756 catch (RuntimeException ex) {
757 if (!beforeCompletionInvoked) {
758 triggerBeforeCompletion(status);
759 }
760 doRollbackOnCommitException(status, ex);
761 throw ex;
762 }
763 catch (Error err) {
764 if (!beforeCompletionInvoked) {
765 triggerBeforeCompletion(status);
766 }
767 doRollbackOnCommitException(status, err);
768 throw err;
769 }
770
771 // Trigger afterCommit callbacks, with an exception thrown there
772 // propagated to callers but the transaction still considered as committed.
773 try {
774 triggerAfterCommit(status);
775 }
776 finally {
777 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
778 }
779
780 }
781 finally {
782 cleanupAfterCompletion(status);
783 }
784 }
785
786 /**
787 * This implementation of rollback handles participating in existing
788 * transactions. Delegates to <code>doRollback</code> and
789 * <code>doSetRollbackOnly</code>.
790 * @see #doRollback
791 * @see #doSetRollbackOnly
792 */
793 public final void rollback(TransactionStatus status) throws TransactionException {
794 if (status.isCompleted()) {
795 throw new IllegalTransactionStateException(
796 "Transaction is already completed - do not call commit or rollback more than once per transaction");
797 }
798
799 DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
800 processRollback(defStatus);
801 }
802
803 /**
804 * Process an actual rollback.
805 * The completed flag has already been checked.
806 * @param status object representing the transaction
807 * @throws TransactionException in case of rollback failure
808 */
809 private void processRollback(DefaultTransactionStatus status) {
810 try {
811 try {
812 triggerBeforeCompletion(status);
813 if (status.hasSavepoint()) {
814 if (status.isDebug()) {
815 logger.debug("Rolling back transaction to savepoint");
816 }
817 status.rollbackToHeldSavepoint();
818 }
819 else if (status.isNewTransaction()) {
820 if (status.isDebug()) {
821 logger.debug("Initiating transaction rollback");
822 }
823 doRollback(status);
824 }
825 else if (status.hasTransaction()) {
826 if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
827 if (status.isDebug()) {
828 logger.debug(
829 "Participating transaction failed - marking existing transaction as rollback-only");
830 }
831 doSetRollbackOnly(status);
832 }
833 else {
834 if (status.isDebug()) {
835 logger.debug(
836 "Participating transaction failed - letting transaction originator decide on rollback");
837 }
838 }
839 }
840 else {
841 logger.debug("Should roll back transaction but cannot - no transaction available");
842 }
843 }
844 catch (RuntimeException ex) {
845 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
846 throw ex;
847 }
848 catch (Error err) {
849 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
850 throw err;
851 }
852 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
853 }
854 finally {
855 cleanupAfterCompletion(status);
856 }
857 }
858
859 /**
860 * Invoke <code>doRollback</code>, handling rollback exceptions properly.
861 * @param status object representing the transaction
862 * @param ex the thrown application exception or error
863 * @throws TransactionException in case of rollback failure
864 * @see #doRollback
865 */
866 private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex)
867 throws TransactionException {
868 try {
869 if (status.isNewTransaction()) {
870 if (status.isDebug()) {
871 logger.debug("Initiating transaction rollback after commit exception", ex);
872 }
873 doRollback(status);
874 }
875 else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) {
876 if (status.isDebug()) {
877 logger.debug("Marking existing transaction as rollback-only after commit exception", ex);
878 }
879 doSetRollbackOnly(status);
880 }
881 }
882 catch (RuntimeException rbex) {
883 logger.error("Commit exception overridden by rollback exception", ex);
884 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
885 throw rbex;
886 }
887 catch (Error rberr) {
888 logger.error("Commit exception overridden by rollback exception", ex);
889 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
890 throw rberr;
891 }
892 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
893 }
894
895
896 /**
897 * Trigger <code>beforeCommit</code> callbacks.
898 * @param status object representing the transaction
899 */
900 protected final void triggerBeforeCommit(DefaultTransactionStatus status) {
901 if (status.isNewSynchronization()) {
902 if (status.isDebug()) {
903 logger.trace("Triggering beforeCommit synchronization");
904 }
905 TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());
906 }
907 }
908
909 /**
910 * Trigger <code>beforeCompletion</code> callbacks.
911 * @param status object representing the transaction
912 */
913 protected final void triggerBeforeCompletion(DefaultTransactionStatus status) {
914 if (status.isNewSynchronization()) {
915 if (status.isDebug()) {
916 logger.trace("Triggering beforeCompletion synchronization");
917 }
918 TransactionSynchronizationUtils.triggerBeforeCompletion();
919 }
920 }
921
922 /**
923 * Trigger <code>afterCommit</code> callbacks.
924 * @param status object representing the transaction
925 */
926 private void triggerAfterCommit(DefaultTransactionStatus status) {
927 if (status.isNewSynchronization()) {
928 if (status.isDebug()) {
929 logger.trace("Triggering afterCommit synchronization");
930 }
931 TransactionSynchronizationUtils.triggerAfterCommit();
932 }
933 }
934
935 /**
936 * Trigger <code>afterCompletion</code> callbacks.
937 * @param status object representing the transaction
938 * @param completionStatus completion status according to TransactionSynchronization constants
939 */
940 private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
941 if (status.isNewSynchronization()) {
942 List synchronizations = TransactionSynchronizationManager.getSynchronizations();
943 if (!status.hasTransaction() || status.isNewTransaction()) {
944 if (status.isDebug()) {
945 logger.trace("Triggering afterCompletion synchronization");
946 }
947 // No transaction or new transaction for the current scope ->
948 // invoke the afterCompletion callbacks immediately
949 invokeAfterCompletion(synchronizations, completionStatus);
950 }
951 else {
952 // Existing transaction that we participate in, controlled outside
953 // of the scope of this Spring transaction manager -> try to register
954 // an afterCompletion callback with the existing (JTA) transaction.
955 registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
956 }
957 }
958 }
959
960 /**
961 * Actually invoke the <code>afterCompletion</code> methods of the
962 * given Spring TransactionSynchronization objects.
963 * <p>To be called by this abstract manager itself, or by special implementations
964 * of the <code>registerAfterCompletionWithExistingTransaction</code> callback.
965 * @param synchronizations List of TransactionSynchronization objects
966 * @param completionStatus the completion status according to the
967 * constants in the TransactionSynchronization interface
968 * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List)
969 * @see TransactionSynchronization#STATUS_COMMITTED
970 * @see TransactionSynchronization#STATUS_ROLLED_BACK
971 * @see TransactionSynchronization#STATUS_UNKNOWN
972 */
973 protected final void invokeAfterCompletion(List synchronizations, int completionStatus) {
974 TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus);
975 }
976
977 /**
978 * Clean up after completion, clearing synchronization if necessary,
979 * and invoking doCleanupAfterCompletion.
980 * @param status object representing the transaction
981 * @see #doCleanupAfterCompletion
982 */
983 private void cleanupAfterCompletion(DefaultTransactionStatus status) {
984 status.setCompleted();
985 if (status.isNewSynchronization()) {
986 TransactionSynchronizationManager.clear();
987 }
988 if (status.isNewTransaction()) {
989 doCleanupAfterCompletion(status.getTransaction());
990 }
991 if (status.getSuspendedResources() != null) {
992 if (status.isDebug()) {
993 logger.debug("Resuming suspended transaction");
994 }
995 resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
996 }
997 }
998
999
1000 //---------------------------------------------------------------------
1001 // Template methods to be implemented in subclasses
1002 //---------------------------------------------------------------------
1003
1004 /**
1005 * Return a transaction object for the current transaction state.
1006 * <p>The returned object will usually be specific to the concrete transaction
1007 * manager implementation, carrying corresponding transaction state in a
1008 * modifiable fashion. This object will be passed into the other template
1009 * methods (e.g. doBegin and doCommit), either directly or as part of a
1010 * DefaultTransactionStatus instance.
1011 * <p>The returned object should contain information about any existing
1012 * transaction, that is, a transaction that has already started before the
1013 * current <code>getTransaction</code> call on the transaction manager.
1014 * Consequently, a <code>doGetTransaction</code> implementation will usually
1015 * look for an existing transaction and store corresponding state in the
1016 * returned transaction object.
1017 * @return the current transaction object
1018 * @throws org.springframework.transaction.CannotCreateTransactionException
1019 * if transaction support is not available
1020 * @throws TransactionException in case of lookup or system errors
1021 * @see #doBegin
1022 * @see #doCommit
1023 * @see #doRollback
1024 * @see DefaultTransactionStatus#getTransaction
1025 */
1026 protected abstract Object doGetTransaction() throws TransactionException;
1027
1028 /**
1029 * Check if the given transaction object indicates an existing transaction
1030 * (that is, a transaction which has already started).
1031 * <p>The result will be evaluated according to the specified propagation
1032 * behavior for the new transaction. An existing transaction might get
1033 * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction
1034 * might participate in the existing one (in case of PROPAGATION_REQUIRED).
1035 * <p>The default implementation returns <code>false</code>, assuming that
1036 * participating in existing transactions is generally not supported.
1037 * Subclasses are of course encouraged to provide such support.
1038 * @param transaction transaction object returned by doGetTransaction
1039 * @return if there is an existing transaction
1040 * @throws TransactionException in case of system errors
1041 * @see #doGetTransaction
1042 */
1043 protected boolean isExistingTransaction(Object transaction) throws TransactionException {
1044 return false;
1045 }
1046
1047 /**
1048 * Return whether to use a savepoint for a nested transaction.
1049 * <p>Default is <code>true</code>, which causes delegation to DefaultTransactionStatus
1050 * for creating and holding a savepoint. If the transaction object does not implement
1051 * the SavepointManager interface, a NestedTransactionNotSupportedException will be
1052 * thrown. Else, the SavepointManager will be asked to create a new savepoint to
1053 * demarcate the start of the nested transaction.
1054 * <p>Subclasses can override this to return <code>false</code>, causing a further
1055 * call to <code>doBegin</code> - within the context of an already existing transaction.
1056 * The <code>doBegin</code> implementation needs to handle this accordingly in such
1057 * a scenario. This is appropriate for JTA, for example.
1058 * @see DefaultTransactionStatus#createAndHoldSavepoint
1059 * @see DefaultTransactionStatus#rollbackToHeldSavepoint
1060 * @see DefaultTransactionStatus#releaseHeldSavepoint
1061 * @see #doBegin
1062 */
1063 protected boolean useSavepointForNestedTransaction() {
1064 return true;
1065 }
1066
1067 /**
1068 * Begin a new transaction with semantics according to the given transaction
1069 * definition. Does not have to care about applying the propagation behavior,
1070 * as this has already been handled by this abstract manager.
1071 * <p>This method gets called when the transaction manager has decided to actually
1072 * start a new transaction. Either there wasn't any transaction before, or the
1073 * previous transaction has been suspended.
1074 * <p>A special scenario is a nested transaction without savepoint: If
1075 * <code>useSavepointForNestedTransaction()</code> returns "false", this method
1076 * will be called to start a nested transaction when necessary. In such a context,
1077 * there will be an active transaction: The implementation of this method has
1078 * to detect this and start an appropriate nested transaction.
1079 * @param transaction transaction object returned by <code>doGetTransaction</code>
1080 * @param definition TransactionDefinition instance, describing propagation
1081 * behavior, isolation level, read-only flag, timeout, and transaction name
1082 * @throws TransactionException in case of creation or system errors
1083 */
1084 protected abstract void doBegin(Object transaction, TransactionDefinition definition)
1085 throws TransactionException;
1086
1087 /**
1088 * Suspend the resources of the current transaction.
1089 * Transaction synchronization will already have been suspended.
1090 * <p>The default implementation throws a TransactionSuspensionNotSupportedException,
1091 * assuming that transaction suspension is generally not supported.
1092 * @param transaction transaction object returned by <code>doGetTransaction</code>
1093 * @return an object that holds suspended resources
1094 * (will be kept unexamined for passing it into doResume)
1095 * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException
1096 * if suspending is not supported by the transaction manager implementation
1097 * @throws TransactionException in case of system errors
1098 * @see #doResume
1099 */
1100 protected Object doSuspend(Object transaction) throws TransactionException {
1101 throw new TransactionSuspensionNotSupportedException(
1102 "Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
1103 }
1104
1105 /**
1106 * Resume the resources of the current transaction.
1107 * Transaction synchronization will be resumed afterwards.
1108 * <p>The default implementation throws a TransactionSuspensionNotSupportedException,
1109 * assuming that transaction suspension is generally not supported.
1110 * @param transaction transaction object returned by <code>doGetTransaction</code>
1111 * @param suspendedResources the object that holds suspended resources,
1112 * as returned by doSuspend
1113 * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException
1114 * if resuming is not supported by the transaction manager implementation
1115 * @throws TransactionException in case of system errors
1116 * @see #doSuspend
1117 */
1118 protected void doResume(Object transaction, Object suspendedResources) throws TransactionException {
1119 throw new TransactionSuspensionNotSupportedException(
1120 "Transaction manager [" + getClass().getName() + "] does not support transaction suspension");
1121 }
1122
1123 /**
1124 * Return whether to call <code>doCommit</code> on a transaction that has been
1125 * marked as rollback-only in a global fashion.
1126 * <p>Does not apply if an application locally sets the transaction to rollback-only
1127 * via the TransactionStatus, but only to the transaction itself being marked as
1128 * rollback-only by the transaction coordinator.
1129 * <p>Default is "false": Local transaction strategies usually don't hold the rollback-only
1130 * marker in the transaction itself, therefore they can't handle rollback-only transactions
1131 * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger
1132 * a rollback in that case, throwing an UnexpectedRollbackException afterwards.
1133 * <p>Override this to return "true" if the concrete transaction manager expects a
1134 * <code>doCommit</code> call even for a rollback-only transaction, allowing for
1135 * special handling there. This will, for example, be the case for JTA, where
1136 * <code>UserTransaction.commit</code> will check the read-only flag itself and
1137 * throw a corresponding RollbackException, which might include the specific reason
1138 * (such as a transaction timeout).
1139 * <p>If this method returns "true" but the <code>doCommit</code> implementation does not
1140 * throw an exception, this transaction manager will throw an UnexpectedRollbackException
1141 * itself. This should not be the typical case; it is mainly checked to cover misbehaving
1142 * JTA providers that silently roll back even when the rollback has not been requested
1143 * by the calling code.
1144 * @see #doCommit
1145 * @see DefaultTransactionStatus#isGlobalRollbackOnly()
1146 * @see DefaultTransactionStatus#isLocalRollbackOnly()
1147 * @see org.springframework.transaction.TransactionStatus#setRollbackOnly()
1148 * @see org.springframework.transaction.UnexpectedRollbackException
1149 * @see javax.transaction.UserTransaction#commit()
1150 * @see javax.transaction.RollbackException
1151 */
1152 protected boolean shouldCommitOnGlobalRollbackOnly() {
1153 return false;
1154 }
1155
1156 /**
1157 * Make preparations for commit, to be performed before the
1158 * <code>beforeCommit</code> synchronization callbacks occur.
1159 * <p>Note that exceptions will get propagated to the commit caller
1160 * and cause a rollback of the transaction.
1161 * @param status the status representation of the transaction
1162 * @throws RuntimeException in case of errors; will be <b>propagated to the caller</b>
1163 * (note: do not throw TransactionException subclasses here!)
1164 */
1165 protected void prepareForCommit(DefaultTransactionStatus status) {
1166 }
1167
1168 /**
1169 * Perform an actual commit of the given transaction.
1170 * <p>An implementation does not need to check the "new transaction" flag
1171 * or the rollback-only flag; this will already have been handled before.
1172 * Usually, a straight commit will be performed on the transaction object
1173 * contained in the passed-in status.
1174 * @param status the status representation of the transaction
1175 * @throws TransactionException in case of commit or system errors
1176 * @see DefaultTransactionStatus#getTransaction
1177 */
1178 protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException;
1179
1180 /**
1181 * Perform an actual rollback of the given transaction.
1182 * <p>An implementation does not need to check the "new transaction" flag;
1183 * this will already have been handled before. Usually, a straight rollback
1184 * will be performed on the transaction object contained in the passed-in status.
1185 * @param status the status representation of the transaction
1186 * @throws TransactionException in case of system errors
1187 * @see DefaultTransactionStatus#getTransaction
1188 */
1189 protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException;
1190
1191 /**
1192 * Set the given transaction rollback-only. Only called on rollback
1193 * if the current transaction participates in an existing one.
1194 * <p>The default implementation throws an IllegalTransactionStateException,
1195 * assuming that participating in existing transactions is generally not
1196 * supported. Subclasses are of course encouraged to provide such support.
1197 * @param status the status representation of the transaction
1198 * @throws TransactionException in case of system errors
1199 */
1200 protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
1201 throw new IllegalTransactionStateException(
1202 "Participating in existing transactions is not supported - when 'isExistingTransaction' " +
1203 "returns true, appropriate 'doSetRollbackOnly' behavior must be provided");
1204 }
1205
1206 /**
1207 * Register the given list of transaction synchronizations with the existing transaction.
1208 * <p>Invoked when the control of the Spring transaction manager and thus all Spring
1209 * transaction synchronizations end, without the transaction being completed yet. This
1210 * is for example the case when participating in an existing JTA or EJB CMT transaction.
1211 * <p>The default implementation simply invokes the <code>afterCompletion</code> methods
1212 * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no
1213 * chance to determine the actual outcome of the outer transaction.
1214 * @param transaction transaction object returned by <code>doGetTransaction</code>
1215 * @param synchronizations List of TransactionSynchronization objects
1216 * @throws TransactionException in case of system errors
1217 * @see #invokeAfterCompletion(java.util.List, int)
1218 * @see TransactionSynchronization#afterCompletion(int)
1219 * @see TransactionSynchronization#STATUS_UNKNOWN
1220 */
1221 protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations)
1222 throws TransactionException {
1223
1224 logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " +
1225 "processing Spring after-completion callbacks immediately, with outcome status 'unknown'");
1226 invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN);
1227 }
1228
1229 /**
1230 * Cleanup resources after transaction completion.
1231 * <p>Called after <code>doCommit</code> and <code>doRollback</code> execution,
1232 * on any outcome. The default implementation does nothing.
1233 * <p>Should not throw any exceptions but just issue warnings on errors.
1234 * @param transaction transaction object returned by <code>doGetTransaction</code>
1235 */
1236 protected void doCleanupAfterCompletion(Object transaction) {
1237 }
1238
1239
1240 //---------------------------------------------------------------------
1241 // Serialization support
1242 //---------------------------------------------------------------------
1243
1244 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
1245 // Rely on default serialization; just initialize state after deserialization.
1246 ois.defaultReadObject();
1247
1248 // Initialize transient fields.
1249 this.logger = LogFactory.getLog(getClass());
1250 }
1251
1252
1253 /**
1254 * Holder for suspended resources.
1255 * Used internally by <code>suspend</code> and <code>resume</code>.
1256 */
1257 protected static class SuspendedResourcesHolder {
1258
1259 private final Object suspendedResources;
1260 private List suspendedSynchronizations;
1261 private String name;
1262 private boolean readOnly;
1263 private Integer isolationLevel;
1264 private boolean wasActive;
1265
1266 private SuspendedResourcesHolder(Object suspendedResources) {
1267 this.suspendedResources = suspendedResources;
1268 }
1269
1270 private SuspendedResourcesHolder(
1271 Object suspendedResources, List suspendedSynchronizations,
1272 String name, boolean readOnly, Integer isolationLevel, boolean wasActive) {
1273 this.suspendedResources = suspendedResources;
1274 this.suspendedSynchronizations = suspendedSynchronizations;
1275 this.name = name;
1276 this.readOnly = readOnly;
1277 this.isolationLevel = isolationLevel;
1278 this.wasActive = wasActive;
1279 }
1280 }
1281
1282 }