Transaction propagation behavior supported by Spring

Spring defines the transaction propagation behavior declaration in the TransactionDefinition, which has the following seven propagation behaviors:

  1. PROPAGATION_REQUIRED supports the current transaction, and if not, the transaction is created and run as a transaction
  2. PROPAGATION_SUPPORTS the current transaction, and if not, it runs non-transactionally
  3. PROPAGATION_MANDATORY Supports the current transaction, and throws an exception if there is no transaction currently
  4. PROPAGATION_REQUIRES_NEW Creates a new transaction, and suspends it if one currently exists
  5. PROPAGATION_NOT_SUPPORTED Does not support the current transaction and always runs non-transactionally
  6. PROPAGATION_NEVER does not support the current transaction, and throws an exception if a transaction exists
  7. Execute a nested transaction (JDBC only) if the current transaction exists

The propagation behavior above mainly revolves around whether the current transaction is supported, whether it needs to be created, suspended, nested, etc.

Let’s look at how the transaction propagation behavior is implemented.

Get a transaction

Transaction behavior mainly reflects on how to obtain the spread of a transaction, the code in AbstractPlatformTransactionManager. GetTransaction () :

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { Object Transaction = doGetTransaction(); . // No transaction definition, If (definition == null) {// Use defaults if no transaction definition gifie. definition = new DefaultTransactionDefinition(); If (isExistingTransaction(transaction)) {// ExistingTransaction found -> Check Propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); 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. }Copy the code

The main logic of the above code is:

  1. Gets the current transaction and sets the associated transaction definition properties
  2. If there is a transaction to process
  3. If no transaction is currently being processed

Let’s first look at step 3 without transaction, again in getTransaction() :

/ / judge a transaction if there is no direct throw an exception if (definition. GetPropagationBehavior () = = TransactionDefinition. PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException( "No existing transaction found for transaction marked with propagation 'mandatory'"); } / / start a new transaction else if (definition) getPropagationBehavior () = = TransactionDefinition. PROPAGATION_REQUIRED | | definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); } try { boolean newSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); doBegin(transaction, definition); prepareSynchronization(status, definition); return status; } catch (RuntimeException | Error ex) { resume(null, suspendedResources); throw ex; Else {// Create "empty" transaction: no actual transaction, but potentially synchronization. if (definition.getIsolationLevel() ! = TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { logger.warn("Custom isolation level specified but  no actual transaction initiated; " + "isolation level will effectively be ignored: " + definition); } boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); }Copy the code

The above code has three main branches:

  1. If it is PROPAGATION_MANDATORY, throw an exception if there is no transaction
  2. Create a transaction if it is PROPAGATION_REQUIRED/PROPAGATION_REQUIRES_NEW/PROPAGATION_NESTED
  3. Create an empty transaction with no actual transaction

The transaction logic currently exists

private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, Boolean debugEnabled) throws TransactionException {// PROPAGATION_NEVER The behavior cannot have a transaction, Direct throw exceptions if (definition. GetPropagationBehavior () = = TransactionDefinition. PROPAGATION_NEVER) {throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } // Not running in a transaction, Suspends the current transaction if (definition. GetPropagationBehavior () = = TransactionDefinition. PROPAGATION_NOT_SUPPORTED) {if (debugEnabled) { logger.debug("Suspending current transaction"); } Object suspendedResources = suspend(transaction); boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return prepareTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); } / / to create new things run the 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 { boolean newSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); doBegin(transaction, definition); prepareSynchronization(status, definition); return status; } catch (RuntimeException | Error beginEx) { resumeAfterBeginException(transaction, suspendedResources, beginEx); throw beginEx; }} / / nested transaction (will determine whether subclasses transaction manager support) 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, Implemented by TransactionStatus. // Usually uses JDBC 3.0 savepoints.never activates Spring synchronization. DefaultTransactionStatus status = prepareTransactionStatus(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. boolean newSynchronization  = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, null); doBegin(transaction, definition); prepareSynchronization(status, definition); return status; } } // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. if (debugEnabled) { logger.debug("Participating in existing transaction"); } / / PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED run in the existing transaction if (isValidateExistingTransaction ()) {if (definition.getIsolationLevel() ! = TransactionDefinition.ISOLATION_DEFAULT) { Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); if (currentIsolationLevel == null || currentIsolationLevel ! = definition.getIsolationLevel()) { Constants isoConstants = DefaultTransactionDefinition.constants; throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] specifies isolation level which is incompatible with existing transaction: " + (currentIsolationLevel ! = null ? isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : "(unknown)")); } } if (! definition.isReadOnly()) { if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] is not marked as read-only but existing transaction is"); } } } boolean newSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); }Copy the code

In the case of transactions, the above processing logic also confirms the description of each propagation behavior:

  1. An exception is thrown, PROPAGATION_NEVER, and the current transaction is not allowed.
  2. Suspend the current transaction and run non-transactionally, PROPAGATION_NOT_SUPPORTED.
  3. The judgment is PROPAGATION_REQUIRES_NEW, suspend the current transaction and create a new one.
  4. Determine whether the transaction manager supports the statement, PROPAGATION_NESTED, and throw an exception if it does not. (Savepoint for JDBC)
  5. Other TWO actions, PROPAGATION_SUPPORTS&PROPAGATION_REQUIRED, run in the current transaction. (Check if transaction propagation level is ISOLATION_DEFAULT, non-read transaction)