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