All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.transaction.support.AbstractPlatformTransactionManager Maven / Gradle / Ivy

There is a newer version: 5.3.34
Show newest version
/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.support;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.Constants;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidTimeoutException;
import org.springframework.transaction.NestedTransactionNotSupportedException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSuspensionNotSupportedException;
import org.springframework.transaction.UnexpectedRollbackException;

/**
 * Abstract base class that implements Spring's standard transaction workflow,
 * serving as basis for concrete platform transaction managers like
 * {@link org.springframework.transaction.jta.JtaTransactionManager} and
 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}.
 *
 * 

This base class provides the following workflow handling: *

    *
  • determines if there is an existing transaction; *
  • applies the appropriate propagation behavior; *
  • suspends and resumes transactions if necessary; *
  • checks the rollback-only flag on commit; *
  • applies the appropriate modification on rollback * (actual rollback or setting rollback-only); *
  • triggers registered synchronization callbacks * (if transaction synchronization is active). *
* *

Subclasses have to implement specific template methods for specific * states of a transaction, e.g.: begin, suspend, resume, commit, rollback. * The most important of them are abstract and must be provided by a concrete * implementation; for the rest, defaults are provided, so overriding is optional. * *

Transaction synchronization is a generic mechanism for registering callbacks * that get invoked at transaction completion time. This is mainly used internally * by the data access support classes for JDBC, Hibernate, JDO, etc when running * within a JTA transaction: They register resources that are opened within the * transaction for closing at transaction completion time, allowing e.g. for reuse * of the same Hibernate Session within the transaction. The same mechanism can * also be leveraged for custom synchronization needs in an application. * *

The state of this class is serializable, to allow for serializing the * transaction strategy along with proxies that carry a transaction interceptor. * It is up to subclasses if they wish to make their state to be serializable too. * They should implement the java.io.Serializable marker interface in * that case, and potentially a private readObject() method (according * to Java serialization rules) if they need to restore any transient state. * * @author Juergen Hoeller * @since 28.03.2003 * @see #setTransactionSynchronization * @see TransactionSynchronizationManager * @see org.springframework.transaction.jta.JtaTransactionManager * @see org.springframework.jdbc.datasource.DataSourceTransactionManager * @see org.springframework.orm.hibernate.HibernateTransactionManager */ public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { /** * Always activate transaction synchronization, even for "empty" transactions * that result from PROPAGATION_SUPPORTS with no existing backend transaction. * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER */ public static final int SYNCHRONIZATION_ALWAYS = 0; /** * Activate transaction synchronization only for actual transactions, * that is, not for empty ones that result from PROPAGATION_SUPPORTS with * no existing backend transaction. * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW */ public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1; /** * Never active transaction synchronization, not even for actual transactions. */ public static final int SYNCHRONIZATION_NEVER = 2; /** Constants instance for AbstractPlatformTransactionManager */ private static final Constants constants = new Constants(AbstractPlatformTransactionManager.class); /** Transient to optimize serialization */ protected transient Log logger = LogFactory.getLog(getClass()); private int transactionSynchronization = SYNCHRONIZATION_ALWAYS; private int defaultTimeout = TransactionDefinition.TIMEOUT_DEFAULT; private boolean nestedTransactionAllowed = false; private boolean globalRollbackOnParticipationFailure = true; private boolean failEarlyOnGlobalRollbackOnly = false; private boolean rollbackOnCommitFailure = false; /** * Set the transaction synchronization by the name of the corresponding constant * in this class, e.g. "SYNCHRONIZATION_ALWAYS". * @param constantName name of the constant * @see #SYNCHRONIZATION_ALWAYS */ public final void setTransactionSynchronizationName(String constantName) { setTransactionSynchronization(constants.asNumber(constantName).intValue()); } /** * Set when this transaction manager should activate the thread-bound * transaction synchronization support. Default is "always". *

Note that transaction synchronization isn't supported for * multiple concurrent transactions by different transaction managers. * Only one transaction manager is allowed to activate it at any time. * @see #SYNCHRONIZATION_ALWAYS * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION * @see #SYNCHRONIZATION_NEVER * @see TransactionSynchronizationManager * @see TransactionSynchronization */ public final void setTransactionSynchronization(int transactionSynchronization) { this.transactionSynchronization = transactionSynchronization; } /** * Return if this transaction manager should activate the thread-bound * transaction synchronization support. */ public final int getTransactionSynchronization() { return this.transactionSynchronization; } /** * Specify the default timeout that this transaction manager should apply * if there is no timeout specified at the transaction level, in seconds. *

Default is the underlying transaction infrastructure's default timeout, * e.g. typically 30 seconds in case of a JTA provider, indicated by the * TransactionDefinition.TIMEOUT_DEFAULT value. * @see org.springframework.transaction.TransactionDefinition#TIMEOUT_DEFAULT */ public final void setDefaultTimeout(int defaultTimeout) { if (defaultTimeout < TransactionDefinition.TIMEOUT_DEFAULT) { throw new InvalidTimeoutException("Invalid default timeout", defaultTimeout); } this.defaultTimeout = defaultTimeout; } /** * Return the default timeout that this transaction manager should apply * if there is no timeout specified at the transaction level, in seconds. *

Returns TransactionDefinition.TIMEOUT_DEFAULT to indicate * the underlying transaction infrastructure's default timeout. */ public final int getDefaultTimeout() { return this.defaultTimeout; } /** * Set whether nested transactions are allowed. Default is "false". *

Typically initialized with an appropriate default by the * concrete transaction manager subclass. */ public final void setNestedTransactionAllowed(boolean nestedTransactionAllowed) { this.nestedTransactionAllowed = nestedTransactionAllowed; } /** * Return whether nested transactions are allowed. */ public final boolean isNestedTransactionAllowed() { return this.nestedTransactionAllowed; } /** * Set whether to globally mark an existing transaction as rollback-only * after a participating transaction failed. *

Default is "true": If a participating transaction (e.g. with * PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing * transaction) fails, the transaction will be globally marked as rollback-only. * The only possible outcome of such a transaction is a rollback: The * transaction originator cannot make the transaction commit anymore. *

Switch this to "false" to let the transaction originator make the rollback * decision. If a participating transaction fails with an exception, the caller * can still decide to continue with a different path within the transaction. * However, note that this will only work as long as all participating resources * are capable of continuing towards a transaction commit even after a data access * failure: This is generally not the case for a Hibernate Session, for example; * neither is it for a sequence of JDBC insert/update/delete operations. *

Note:This flag only applies to an explicit rollback attempt for a * subtransaction, typically caused by an exception thrown by a data access operation * (where TransactionInterceptor will trigger a PlatformTransactionManager.rollback() * call according to a rollback rule). If the flag is off, the caller can handle the exception * and decide on a rollback, independent of the rollback rules of the subtransaction. * This flag does, however, not apply to explicit setRollbackOnly * calls on a TransactionStatus, which will always cause an eventual * global rollback (as it might not throw an exception after the rollback-only call). *

The recommended solution for handling failure of a subtransaction * is a "nested transaction", where the global transaction can be rolled * back to a savepoint taken at the beginning of the subtransaction. * PROPAGATION_NESTED provides exactly those semantics; however, it will * only work when nested transaction support is available. This is the case * with DataSourceTransactionManager, but not with JtaTransactionManager. * @see #setNestedTransactionAllowed * @see org.springframework.jdbc.datasource.DataSourceTransactionManager * @see org.springframework.transaction.jta.JtaTransactionManager */ public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) { this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure; } /** * Return whether to globally mark an existing transaction as rollback-only * after a participating transaction failed. */ public final boolean isGlobalRollbackOnParticipationFailure() { return this.globalRollbackOnParticipationFailure; } /** * Set whether to fail early in case of the transaction being globally marked * as rollback-only. *

Default is "false", only causing an UnexpectedRollbackException at the * outermost transaction boundary. Switch this flag on to cause an * UnexpectedRollbackException as early as the global rollback-only marker * has been first detected, even from within an inner transaction boundary. *

Note that, as of Spring 2.0, the fail-early behavior for global * rollback-only markers has been unified: All transaction managers will by * default only cause UnexpectedRollbackException at the outermost transaction * boundary. This allows, for example, to continue unit tests even after an * operation failed and the transaction will never be completed. All transaction * managers will only fail earlier if this flag has explicitly been set to "true". * @see org.springframework.transaction.UnexpectedRollbackException */ public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) { this.failEarlyOnGlobalRollbackOnly = failEarlyOnGlobalRollbackOnly; } /** * Return whether to fail early in case of the transaction being globally marked * as rollback-only. */ public final boolean isFailEarlyOnGlobalRollbackOnly() { return this.failEarlyOnGlobalRollbackOnly; } /** * Set whether doRollback should be performed on failure of the * doCommit call. Typically not necessary and thus to be avoided, * as it can potentially override the commit exception with a subsequent * rollback exception. *

Default is "false". * @see #doCommit * @see #doRollback */ public final void setRollbackOnCommitFailure(boolean rollbackOnCommitFailure) { this.rollbackOnCommitFailure = rollbackOnCommitFailure; } /** * Return whether doRollback should be performed on failure of the * doCommit call. */ public final boolean isRollbackOnCommitFailure() { return this.rollbackOnCommitFailure; } //--------------------------------------------------------------------- // Implementation of PlatformTransactionManager //--------------------------------------------------------------------- /** * This implementation handles propagation behavior. Delegates to * doGetTransaction, isExistingTransaction * and doBegin. * @see #doGetTransaction * @see #isExistingTransaction * @see #doBegin */ public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { Object transaction = doGetTransaction(); // Cache debug flag to avoid repeated checks. boolean debugEnabled = logger.isDebugEnabled(); if (debugEnabled) { logger.debug("Using transaction object [" + transaction + "]"); } if (definition == null) { // Use defaults if no transaction definition given. definition = new DefaultTransactionDefinition(); } if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); } // Check definition settings for new transaction. if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); } // No existing transaction found -> check propagation behavior to find out how to proceed. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { throw new IllegalTransactionStateException( "No existing transaction found for transaction marked with propagation 'mandatory'"); } else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { Object suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); } doBegin(transaction, definition); boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); return newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); } else { // Create "empty" transaction: no actual transaction, but potentially synchronization. boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return newTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); } } /** * Create a TransactionStatus for an existing transaction. */ private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { if (debugEnabled) { logger.debug("Suspending current transaction"); } Object suspendedResources = suspend(transaction); boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return newTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); } if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { if (debugEnabled) { logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]"); } SuspendedResourcesHolder suspendedResources = suspend(transaction); try { doBegin(transaction, definition); } catch (TransactionException beginEx) { try { resume(transaction, suspendedResources); } catch (TransactionException resumeEx) { logger.error( "Inner transaction begin exception overridden by outer transaction resume exception", beginEx); throw resumeEx; } throw beginEx; } boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); return newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); } if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { if (!isNestedTransactionAllowed()) { throw new NestedTransactionNotSupportedException( "Transaction manager does not allow nested transactions by default - " + "specify 'nestedTransactionAllowed' property with value 'true'"); } if (debugEnabled) { logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); } if (useSavepointForNestedTransaction()) { // Create savepoint within existing Spring-managed transaction, // through the SavepointManager API implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization. DefaultTransactionStatus status = newTransactionStatus(definition, transaction, false, false, debugEnabled, null); status.createAndHoldSavepoint(); return status; } else { // Nested transaction through nested begin and commit/rollback calls. // Usually only for JTA: Spring synchronization might get activated here // in case of a pre-existing JTA transaction. doBegin(transaction, definition); boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null); } } // Assumably PROPAGATION_SUPPORTS. if (debugEnabled) { logger.debug("Participating in existing transaction"); } boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); return newTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); } /** * Create a new TransactionStatus for the given arguments, * initializing transaction synchronization as appropriate. */ protected DefaultTransactionStatus newTransactionStatus( TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) { boolean actualNewSynchronization = newSynchronization && !TransactionSynchronizationManager.isSynchronizationActive(); if (actualNewSynchronization) { TransactionSynchronizationManager.setActualTransactionActive(transaction != null); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel( (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ? new Integer(definition.getIsolationLevel()) : null); TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly()); TransactionSynchronizationManager.setCurrentTransactionName(definition.getName()); TransactionSynchronizationManager.initSynchronization(); } return new DefaultTransactionStatus( transaction, newTransaction, actualNewSynchronization, definition.isReadOnly(), debug, suspendedResources); } /** * Determine the actual timeout to use for the given definition. * Will fall back to this manager's default timeout if the * transaction definition doesn't specify a non-default value. * @param definition the transaction definition * @return the actual timeout to use * @see org.springframework.transaction.TransactionDefinition#getTimeout() * @see #setDefaultTimeout */ protected int determineTimeout(TransactionDefinition definition) { if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { return definition.getTimeout(); } return this.defaultTimeout; } /** * Suspend the given transaction. Suspends transaction synchronization first, * then delegates to the doSuspend template method. * @param transaction the current transaction object * (or null to just suspend active synchronizations, if any) * @return an object that holds suspended resources * (or null if neither transaction nor synchronization active) * @see #doSuspend * @see #resume */ protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException { if (TransactionSynchronizationManager.isSynchronizationActive()) { List suspendedSynchronizations = doSuspendSynchronization(); try { Object suspendedResources = null; if (transaction != null) { suspendedResources = doSuspend(transaction); } String name = TransactionSynchronizationManager.getCurrentTransactionName(); TransactionSynchronizationManager.setCurrentTransactionName(null); boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); TransactionSynchronizationManager.setActualTransactionActive(false); return new SuspendedResourcesHolder( suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel); } catch (TransactionException ex) { // doSuspend failed - original transaction is still active... doResumeSynchronization(suspendedSynchronizations); throw ex; } } else if (transaction != null) { // Transaction active but no synchronization active. Object suspendedResources = doSuspend(transaction); return new SuspendedResourcesHolder(suspendedResources, null, null, false, null); } else { // Neither transaction nor synchronization active. return null; } } /** * Resume the given transaction. Delegates to the doResume * template method first, then resuming transaction synchronization. * @param transaction the current transaction object * @param resourcesHolder the object that holds suspended resources, * as returned by suspend (or null to just * resume synchronizations, if any) * @see #doResume * @see #suspend */ protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) throws TransactionException { Object suspendedResources = resourcesHolder.suspendedResources; if (suspendedResources != null) { doResume(transaction, suspendedResources); } List suspendedSynchronizations = resourcesHolder.suspendedSynchronizations; if (suspendedSynchronizations != null) { TransactionSynchronizationManager.setActualTransactionActive(suspendedResources != null); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel); TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly); TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name); doResumeSynchronization(suspendedSynchronizations); } } /** * Suspend all current synchronizations and deactivate transaction * synchronization for the current thread. * @return the List of suspended TransactionSynchronization objects */ private List doSuspendSynchronization() { List suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations(); for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) { ((TransactionSynchronization) it.next()).suspend(); } TransactionSynchronizationManager.clearSynchronization(); return suspendedSynchronizations; } /** * Reactivate transaction synchronization for the current thread * and resume all given synchronizations. * @param suspendedSynchronizations List of TransactionSynchronization objects */ private void doResumeSynchronization(List suspendedSynchronizations) { TransactionSynchronizationManager.initSynchronization(); for (Iterator it = suspendedSynchronizations.iterator(); it.hasNext();) { TransactionSynchronization synchronization = (TransactionSynchronization) it.next(); synchronization.resume(); TransactionSynchronizationManager.registerSynchronization(synchronization); } } /** * This implementation of commit handles participating in existing * transactions and programmatic rollback requests. * Delegates to isRollbackOnly, doCommit * and rollback. * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() * @see #doCommit * @see #rollback */ public final void commit(TransactionStatus status) throws TransactionException { if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; if (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); } processRollback(defStatus); return; } if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); } processRollback(defStatus); // Throw UnexpectedRollbackException only at outermost transaction boundary // or if explicitly asked to. if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } return; } processCommit(defStatus); } /** * Process an actual commit. * Rollback-only flags have already been checked and applied. * @param status object representing the transaction * @throws TransactionException in case of commit failure */ private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { boolean beforeCompletionInvoked = false; try { triggerBeforeCommit(status); triggerBeforeCompletion(status); beforeCompletionInvoked = true; boolean globalRollbackOnly = false; if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) { globalRollbackOnly = status.isGlobalRollbackOnly(); } if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Releasing transaction savepoint"); } status.releaseHeldSavepoint(); } else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } doCommit(status); } // Throw UnexpectedRollbackException if we have a global rollback-only // marker but still didn't get a corresponding exception from commit. if (globalRollbackOnly) { throw new UnexpectedRollbackException( "Transaction silently rolled back because it has been marked as rollback-only"); } } catch (UnexpectedRollbackException ex) { // can only be caused by doCommit triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); throw ex; } catch (TransactionException ex) { // can only be caused by doCommit if (isRollbackOnCommitFailure()) { doRollbackOnCommitException(status, ex); } else { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); } throw ex; } catch (RuntimeException ex) { if (!beforeCompletionInvoked) { triggerBeforeCompletion(status); } doRollbackOnCommitException(status, ex); throw ex; } catch (Error err) { if (!beforeCompletionInvoked) { triggerBeforeCompletion(status); } doRollbackOnCommitException(status, err); throw err; } // Trigger afterCommit callbacks, with an exception thrown there // propagated to callers but the transaction still considered as committed. try { triggerAfterCommit(status); } finally { triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } } finally { cleanupAfterCompletion(status); } } /** * This implementation of rollback handles participating in existing * transactions. Delegates to doRollback and * doSetRollbackOnly. * @see #doRollback * @see #doSetRollbackOnly */ public final void rollback(TransactionStatus status) throws TransactionException { if (status.isCompleted()) { throw new IllegalTransactionStateException( "Transaction is already completed - do not call commit or rollback more than once per transaction"); } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; processRollback(defStatus); } /** * Process an actual rollback. * The completed flag has already been checked. * @param status object representing the transaction * @throws TransactionException in case of rollback failure */ private void processRollback(DefaultTransactionStatus status) { try { try { triggerBeforeCompletion(status); if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Rolling back transaction to savepoint"); } status.rollbackToHeldSavepoint(); } else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback"); } doRollback(status); } else if (status.hasTransaction()) { if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug( "Participating transaction failed - marking existing transaction as rollback-only"); } doSetRollbackOnly(status); } else { if (status.isDebug()) { logger.debug( "Participating transaction failed - letting transaction originator decide on rollback"); } } } else { logger.debug("Should roll back transaction but cannot - no transaction available"); } } catch (RuntimeException ex) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw ex; } catch (Error err) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw err; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); } finally { cleanupAfterCompletion(status); } } /** * Invoke doRollback, handling rollback exceptions properly. * @param status object representing the transaction * @param ex the thrown application exception or error * @throws TransactionException in case of rollback failure * @see #doRollback */ private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) throws TransactionException { try { if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction rollback after commit exception", ex); } doRollback(status); } else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) { if (status.isDebug()) { logger.debug("Marking existing transaction as rollback-only after commit exception", ex); } doSetRollbackOnly(status); } } catch (RuntimeException rbex) { logger.error("Commit exception overridden by rollback exception", ex); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw rbex; } catch (Error rberr) { logger.error("Commit exception overridden by rollback exception", ex); triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); throw rberr; } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); } /** * Trigger beforeCommit callbacks. * @param status object representing the transaction */ protected final void triggerBeforeCommit(DefaultTransactionStatus status) { if (status.isNewSynchronization()) { if (status.isDebug()) { logger.debug("Triggering beforeCommit synchronization"); } TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly()); } } /** * Trigger beforeCompletion callbacks. * @param status object representing the transaction */ protected final void triggerBeforeCompletion(DefaultTransactionStatus status) { if (status.isNewSynchronization()) { if (status.isDebug()) { logger.debug("Triggering beforeCompletion synchronization"); } TransactionSynchronizationUtils.triggerBeforeCompletion(); } } /** * Trigger afterCommit callbacks. * @param status object representing the transaction */ private void triggerAfterCommit(DefaultTransactionStatus status) { if (status.isNewSynchronization()) { if (status.isDebug()) { logger.debug("Triggering afterCommit synchronization"); } TransactionSynchronizationUtils.triggerAfterCommit(); } } /** * Trigger afterCompletion callbacks. * @param status object representing the transaction * @param completionStatus completion status according to TransactionSynchronization constants */ private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) { if (status.isNewSynchronization()) { List synchronizations = TransactionSynchronizationManager.getSynchronizations(); if (!status.hasTransaction() || status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Triggering afterCompletion synchronization"); } // No transaction or new transaction for the current scope -> // invoke the afterCompletion callbacks immediately invokeAfterCompletion(synchronizations, completionStatus); } else { // Existing transaction that we participate in, controlled outside // of the scope of this Spring transaction manager -> try to register // an afterCompletion callback with the existing (JTA) transaction. registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations); } } } /** * Actually invoke the afterCompletion methods of the * given Spring TransactionSynchronization objects. *

To be called by this abstract manager itself, or by special implementations * of the registerAfterCompletionWithExistingTransaction callback. * @param synchronizations List of TransactionSynchronization objects * @param completionStatus the completion status according to the * constants in the TransactionSynchronization interface * @see #registerAfterCompletionWithExistingTransaction(Object, java.util.List) * @see TransactionSynchronization#STATUS_COMMITTED * @see TransactionSynchronization#STATUS_ROLLED_BACK * @see TransactionSynchronization#STATUS_UNKNOWN */ protected final void invokeAfterCompletion(List synchronizations, int completionStatus) { TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus); } /** * Clean up after completion, clearing synchronization if necessary, * and invoking doCleanupAfterCompletion. * @param status object representing the transaction * @see #doCleanupAfterCompletion */ private void cleanupAfterCompletion(DefaultTransactionStatus status) { status.setCompleted(); if (status.isNewSynchronization()) { TransactionSynchronizationManager.clear(); } if (status.isNewTransaction()) { doCleanupAfterCompletion(status.getTransaction()); } if (status.getSuspendedResources() != null) { if (status.isDebug()) { logger.debug("Resuming suspended transaction"); } resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources()); } } //--------------------------------------------------------------------- // Template methods to be implemented in subclasses //--------------------------------------------------------------------- /** * Return a transaction object for the current transaction state. *

The returned object will usually be specific to the concrete transaction * manager implementation, carrying corresponding transaction state in a * modifiable fashion. This object will be passed into the other template * methods (e.g. doBegin and doCommit), either directly or as part of a * DefaultTransactionStatus instance. *

The returned object should contain information about any existing * transaction, that is, a transaction that has already started before the * current getTransaction call on the transaction manager. * Consequently, a doGetTransaction implementation will usually * look for an existing transaction and store corresponding state in the * returned transaction object. * @return the current transaction object * @throws org.springframework.transaction.CannotCreateTransactionException * if transaction support is not available * @throws TransactionException in case of lookup or system errors * @see #doBegin * @see #doCommit * @see #doRollback * @see DefaultTransactionStatus#getTransaction */ protected abstract Object doGetTransaction() throws TransactionException; /** * Check if the given transaction object indicates an existing transaction * (that is, a transaction which has already started). *

The result will be evaluated according to the specified propagation * behavior for the new transaction. An existing transaction might get * suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction * might participate in the existing one (in case of PROPAGATION_REQUIRED). *

The default implementation returns false, assuming that * participating in existing transactions is generally not supported. * Subclasses are of course encouraged to provide such support. * @param transaction transaction object returned by doGetTransaction * @return if there is an existing transaction * @throws TransactionException in case of system errors * @see #doGetTransaction */ protected boolean isExistingTransaction(Object transaction) throws TransactionException { return false; } /** * Return whether to use a savepoint for a nested transaction. *

Default is true, which causes delegation to DefaultTransactionStatus * for creating and holding a savepoint. If the transaction object does not implement * the SavepointManager interface, a NestedTransactionNotSupportedException will be * thrown. Else, the SavepointManager will be asked to create a new savepoint to * demarcate the start of the nested transaction. *

Subclasses can override this to return false, causing a further * call to doBegin - within the context of an already existing transaction. * The doBegin implementation needs to handle this accordingly in such * a scenario. This is appropriate for JTA, for example. * @see DefaultTransactionStatus#createAndHoldSavepoint * @see DefaultTransactionStatus#rollbackToHeldSavepoint * @see DefaultTransactionStatus#releaseHeldSavepoint * @see #doBegin */ protected boolean useSavepointForNestedTransaction() { return true; } /** * Begin a new transaction with semantics according to the given transaction * definition. Does not have to care about applying the propagation behavior, * as this has already been handled by this abstract manager. *

This method gets called when the transaction manager has decided to actually * start a new transaction. Either there wasn't any transaction before, or the * previous transaction has been suspended. *

A special scenario is a nested transaction without savepoint: If * useSavepointForNestedTransaction() returns "false", this method * will be called to start a nested transaction when necessary. In such a context, * there will be an active transaction: The implementation of this method has * to detect this and start an appropriate nested transaction. * @param transaction transaction object returned by doGetTransaction * @param definition TransactionDefinition instance, describing propagation * behavior, isolation level, read-only flag, timeout, and transaction name * @throws TransactionException in case of creation or system errors */ protected abstract void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException; /** * Suspend the resources of the current transaction. * Transaction synchronization will already have been suspended. *

The default implementation throws a TransactionSuspensionNotSupportedException, * assuming that transaction suspension is generally not supported. * @param transaction transaction object returned by doGetTransaction * @return an object that holds suspended resources * (will be kept unexamined for passing it into doResume) * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException * if suspending is not supported by the transaction manager implementation * @throws TransactionException in case of system errors * @see #doResume */ protected Object doSuspend(Object transaction) throws TransactionException { throw new TransactionSuspensionNotSupportedException( "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); } /** * Resume the resources of the current transaction. * Transaction synchronization will be resumed afterwards. *

The default implementation throws a TransactionSuspensionNotSupportedException, * assuming that transaction suspension is generally not supported. * @param transaction transaction object returned by doGetTransaction * @param suspendedResources the object that holds suspended resources, * as returned by doSuspend * @throws org.springframework.transaction.TransactionSuspensionNotSupportedException * if resuming is not supported by the transaction manager implementation * @throws TransactionException in case of system errors * @see #doSuspend */ protected void doResume(Object transaction, Object suspendedResources) throws TransactionException { throw new TransactionSuspensionNotSupportedException( "Transaction manager [" + getClass().getName() + "] does not support transaction suspension"); } /** * Return whether to call doCommit on a transaction that has been * marked as rollback-only in a global fashion. *

Does not apply if an application locally sets the transaction to rollback-only * via the TransactionStatus, but only to the transaction itself being marked as * rollback-only by the transaction coordinator. *

Default is "false": Local transaction strategies usually don't hold the rollback-only * marker in the transaction itself, therefore they can't handle rollback-only transactions * as part of transaction commit. Hence, AbstractPlatformTransactionManager will trigger * a rollback in that case, throwing an UnexpectedRollbackException afterwards. *

Override this to return "true" if the concrete transaction manager expects a * doCommit call even for a rollback-only transaction, allowing for * special handling there. This will, for example, be the case for JTA, where * UserTransaction.commit will check the read-only flag itself and * throw a corresponding RollbackException, which might include the specific reason * (such as a transaction timeout). *

If this method returns "true" but the doCommit implementation does not * throw an exception, this transaction manager will throw an UnexpectedRollbackException * itself. This should not be the typical case; it is mainly checked to cover misbehaving * JTA providers that silently roll back even when the rollback has not been requested * by the calling code. * @see #doCommit * @see DefaultTransactionStatus#isGlobalRollbackOnly() * @see DefaultTransactionStatus#isLocalRollbackOnly() * @see org.springframework.transaction.TransactionStatus#setRollbackOnly() * @see org.springframework.transaction.UnexpectedRollbackException * @see javax.transaction.UserTransaction#commit() * @see javax.transaction.RollbackException */ protected boolean shouldCommitOnGlobalRollbackOnly() { return false; } /** * Perform an actual commit of the given transaction. *

An implementation does not need to check the "new transaction" flag * or the rollback-only flag; this will already have been handled before. * Usually, a straight commit will be performed on the transaction object * contained in the passed-in status. * @param status the status representation of the transaction * @throws TransactionException in case of commit or system errors * @see DefaultTransactionStatus#getTransaction */ protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException; /** * Perform an actual rollback of the given transaction. *

An implementation does not need to check the "new transaction" flag; * this will already have been handled before. Usually, a straight rollback * will be performed on the transaction object contained in the passed-in status. * @param status the status representation of the transaction * @throws TransactionException in case of system errors * @see DefaultTransactionStatus#getTransaction */ protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException; /** * Set the given transaction rollback-only. Only called on rollback * if the current transaction participates in an existing one. *

The default implementation throws an IllegalTransactionStateException, * assuming that participating in existing transactions is generally not * supported. Subclasses are of course encouraged to provide such support. * @param status the status representation of the transaction * @throws TransactionException in case of system errors */ protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { throw new IllegalTransactionStateException( "Participating in existing transactions is not supported - when 'isExistingTransaction' " + "returns true, appropriate 'doSetRollbackOnly' behavior must be provided"); } /** * Register the given list of transaction synchronizations with the existing transaction. *

Invoked when the control of the Spring transaction manager and thus all Spring * transaction synchronizations end, without the transaction being completed yet. This * is for example the case when participating in an existing JTA or EJB CMT transaction. *

The default implementation simply invokes the afterCompletion methods * immediately, passing in "STATUS_UNKNOWN". This is the best we can do if there's no * chance to determine the actual outcome of the outer transaction. * @param transaction transaction object returned by doGetTransaction * @param synchronizations List of TransactionSynchronization objects * @throws TransactionException in case of system errors * @see #invokeAfterCompletion(java.util.List, int) * @see TransactionSynchronization#afterCompletion(int) * @see TransactionSynchronization#STATUS_UNKNOWN */ protected void registerAfterCompletionWithExistingTransaction(Object transaction, List synchronizations) throws TransactionException { logger.debug("Cannot register Spring after-completion synchronization with existing transaction - " + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); invokeAfterCompletion(synchronizations, TransactionSynchronization.STATUS_UNKNOWN); } /** * Cleanup resources after transaction completion. *

Called after doCommit and doRollback execution, * on any outcome. The default implementation does nothing. *

Should not throw any exceptions but just issue warnings on errors. * @param transaction transaction object returned by doGetTransaction */ protected void doCleanupAfterCompletion(Object transaction) { } //--------------------------------------------------------------------- // Serialization support //--------------------------------------------------------------------- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization; just initialize state after deserialization. ois.defaultReadObject(); // Initialize transient fields. this.logger = LogFactory.getLog(getClass()); } /** * Holder for suspended resources. * Used internally by suspend and resume. */ protected static class SuspendedResourcesHolder { private final Object suspendedResources; private final List suspendedSynchronizations; private final String name; private final boolean readOnly; private final Integer isolationLevel; private SuspendedResourcesHolder( Object suspendedResources, List suspendedSynchronizations, String name, boolean readOnly, Integer isolationLevel) { this.suspendedResources = suspendedResources; this.suspendedSynchronizations = suspendedSynchronizations; this.name = name; this.readOnly = readOnly; this.isolationLevel = isolationLevel; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy