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