Spring AOP learning -- Introduction and principle of spring transaction

Transaction introduction

The previous article introduced spring AOP. Spring transaction is a typical application of spring AOP.

Transaction is the database transaction, which means that the reading and writing of data in the same batch are either all successful or all failed to ensure the consistency of data. It is the core function of the system database. In programming, you can manually commit a transaction by setting it, and then choose to commit the transaction or roll back the transaction according to the situation.

Transaction usage in database:
BEGIN;# Start transaction
update table_name set name=’XXX’ where id=’XXX’;# Perform database operations
COMMIT; # Submit
ROLLBACK;# RollBACK

Java jdbc uses the following transactions

Connection connection = DriverManager.getConnection(url, username, password);
connection.setAutoCommit(false);
boolean error = doSomething...;
if (error){
	connection.commit();
} else {
	connection.rollback();
}

Programming and declarative transactions

Spring transactions are the encapsulation of jdbc transactions. As can be seen from the above jdbc transactions, all transactions are fixed except doSmoething.
Spring encapsulates the common part of transactions, encapsulates jdbc details, and provides programming or AOP methods to use transactions, namely programming transactions and declarative transactions. Declarative transactions are the focus of this film, and programming transactions are not explored too much

1. Programming transaction: Spring provides template code to wrap the user business code and commit or rollback the transaction according to the execution result of the user code.
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
//doSomething...
}
});
Alternatively, you can directly use the Spring transaction manager to obtain transactions, and then manually commit or rollback them in the code as needed.

2. Declarative transaction: declare the use transaction through configuration or annotation, which is used to tell spring where the transaction needs to be used. Spring will generate an aop aspect for it. Later, when executing the corresponding method, spring commits or rolls back the transaction through the aspect configuration and the execution results of user code.
@Transactiona
public void doSomething(){}

Transaction principle

Declarative transactions are based on aop. When using declarative transactions, it is necessary to introduce the Advisor of transaction aspect configuration.
The old version is based on xml. In the configuration phase, you need to configure Advisor, pointcut, etc. Spring 3 Version 1 provides the enable transaction management annotation to start Spring transactions.
Using this annotation will import the ProxyTransactionManagementConfiguration configuration class.

Let's see what this class contains

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource);
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource);
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}

}

You can see that three bean s are defined
BeanFactoryTransactionAttributeSourceAdvisor
TransactionAttributeSource -> AnnotationTransactionAttributeSource
TransactionInterceptor

Transaction configuration

Recall the content of spring AOP:
"Spring AOP learning - Introduction and principles of spring AOP"

To define AOP by directly defining Advisor, you need to define
Advisor,Advice,PointCut

How do the above three beans correspond to the beans needed to define AOP?
BeanFactoryTransactionAttributeSourceAdvisor == Advisor
TransactionInterceptor = Advice
TransactionAttributeSource ≈ PointCut

Why is TransactionAttributeSource approximately equal to PointCut?
PointCut is actually defined in BeanFactoryTransactionAttributeSourceAdvisor as TransactionAttributeSourcePointcut,
The TransactionAttributeSource pointcut contains TransactionAttributeSource

public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
	...
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		@Nullable
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};
	...
}

	
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
	...
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		TransactionAttributeSource tas = getTransactionAttributeSource();
		return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
	}
	...
}

As an Advisor, BeanFactoryTransactionAttributeSourceAdvisor includes transaction Advise interceptor, TransactionInterceptor and PointCut pointcut collection filter TransactionAttributeSourcePointcut.
The AOP process can be generated and executed when the necessary conditions for AOP profile configuration are met. The specific transaction processing logic is in the interceptor transaction interceptor.
So far, the transaction AOP configuration has been determined

Transaction interceptor

Let's take a look at how the interceptor TransactionInterceptor works. As an interceptor, TransactionInterceptor provides an invoke method for dynamic proxy objects (such as CglibAopProxy). The invoke method calls its parent method transactionaspectsupport invokeWithinTransaction

TransactionInterceptor.invoke -> TransactionInterceptor.invokeWithinTransaction

1. Get the attribute of the transaction corresponding to the current bean and configure the TransactionAttributeSource. The TransactionAttributeSource will collect the transactionattributes of all AOP proxy beans in advance in the proxy phase. Here, it is just fetching the cache

2. Obtain a transaction manager according to the TransactionAttribute. If there is no special configuration, it will be obtained according to the type (TransactionManager.class). The default is DataSourceTransactionManager

3. Non CallbackPreferringPlatformTransactionManager transaction manager
Execute createTransactionIfNecessary to create (or get) transaction TransactionInfo

4. Execute methodinvocation Proceed: call the invoke of the proxy object to execute the target method, record the return value of the target method, try - > catch - > finally, and process the transaction according to the result

5. when an exception is called, completeTransactionAfterThrowing is invoked in catch to handle transaction rollback (and possibly submitted), and the exception is thrown again, and the end of the section ends.

6. When there is no exception, call commitTransactionAfterReturning to process the transaction submission

7. Return the return value of the target method

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
      final InvocationCallback invocation) throws Throwable {

   // If the transaction attribute is null, the method is non-transactional.
   TransactionAttributeSource tas = getTransactionAttributeSource();
   final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
   final TransactionManager tm = determineTransactionManager(txAttr);

   if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
      ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
         if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
            throw new TransactionUsageException(
                  "Unsupported annotated transaction on suspending function detected: " + method +
                  ". Use TransactionalOperator.transactional extensions instead.");
         }
         ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
         if (adapter == null) {
            throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                  method.getReturnType());
         }
         return new ReactiveTransactionSupport(adapter);
      });
      return txSupport.invokeWithinTransaction(
            method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
   }

   PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
   final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

   if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
      TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

      Object retVal;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }

      if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
         // Set rollback-only in case of Vavr failure matching our rollback rules...
         TransactionStatus status = txInfo.getTransactionStatus();
         if (status != null && txAttr != null) {
            retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
         }
      }

      commitTransactionAfterReturning(txInfo);
      return retVal;
   }

   else {
      Object result;
      final ThrowableHolder throwableHolder = new ThrowableHolder();

      // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
      try {
         result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
            TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
            try {
               Object retVal = invocation.proceedWithInvocation();
               if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                  // Set rollback-only in case of Vavr failure matching our rollback rules...
                  retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
               }
               return retVal;
            }
            catch (Throwable ex) {
               if (txAttr.rollbackOn(ex)) {
                  // A RuntimeException: will lead to a rollback.
                  if (ex instanceof RuntimeException) {
                     throw (RuntimeException) ex;
                  }
                  else {
                     throw new ThrowableHolderException(ex);
                  }
               }
               else {
                  // A normal return value: will lead to a commit.
                  throwableHolder.throwable = ex;
                  return null;
               }
            }
            finally {
               cleanupTransactionInfo(txInfo);
            }
         });
      }
      catch (ThrowableHolderException ex) {
         throw ex.getCause();
      }
      catch (TransactionSystemException ex2) {
         if (throwableHolder.throwable != null) {
            logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
            ex2.initApplicationException(throwableHolder.throwable);
         }
         throw ex2;
      }
      catch (Throwable ex2) {
         if (throwableHolder.throwable != null) {
            logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
         }
         throw ex2;
      }

      // Check result state: It might indicate a Throwable to rethrow.
      if (throwableHolder.throwable != null) {
         throw throwableHolder.throwable;
      }
      return result;
   }
}

The focus is on the creation and execution of transactions

Transaction creation

Execute createtransaction ifnecessary to create a transaction, and use platform transaction manager Gettransaction transaction transaction manager gets the transaction
PlatformTransactionManager.getTransaction is an interface method implemented by AbstractPlatformTransactionManager.
Whether to create or use an existing transaction depends on whether there is a transaction and the configured transaction propagation line. During this period, exceptions may be thrown or transactions may be suspended.

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

	// Use defaults if no transaction definition given.
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();

	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior to find out how to behave.
		return handleExistingTransaction(def, transaction, debugEnabled);
	}

	// Check definition settings for new transaction.
	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
	}

	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		if (debugEnabled) {
			logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
		}
		try {
			return startTransaction(def, transaction, debugEnabled, suspendedResources);
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		// Create "empty" transaction: no actual transaction, but potentially synchronization.
		if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + def);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
	}
}

Transaction execution

After the original object method is executed through reflection, execute exception handling in case of exception, otherwise execute transaction submission.

The rollback committed here is not absolute. It can be rolled back without exception, and it can be committed with exception.

Firstly, the commit method also has the possibility of rollback. The normal execution is naturally commit. If the application sets the rollback flag LocalRollbackOnly, the execution of the commit method will also be rolled back.

@Override
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, false);
		return;
	}

	if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
		if (defStatus.isDebug()) {
			logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
		}
		processRollback(defStatus, true);
		return;
	}

	processCommit(defStatus);
}

Similarly, when an exception occurs, execute completeTransactionAfterThrowing. First, match the exception type to determine whether to roll back. If the exception thrown by the program does not match the exception specified by the transaction, it will not roll back, but execute the commit operation

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
	if (txInfo != null && txInfo.getTransactionStatus() != null) {
		if (logger.isTraceEnabled()) {
			logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
					"] after exception: " + ex);
		}
		if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
			try {
				txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by rollback exception", ex);
				throw ex2;
			}
		}
		else {
			// We don't roll back on this exception.
			// Will still roll back if TransactionStatus.isRollbackOnly() is true.
			try {
				txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
			}
			catch (TransactionSystemException ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				ex2.initApplicationException(ex);
				throw ex2;
			}
			catch (RuntimeException | Error ex2) {
				logger.error("Application exception overridden by commit exception", ex);
				throw ex2;
			}
		}
	}
}

One thing to note about exception matching is that it will not only match the exception specified in @ Transactional(rollbackFor = Exception.class), but also execute super Rollbackon means that the runtime exception and its subclasses will be rolled back directly. When we need a specific exception to roll back, we should customize the exception and cannot inherit RuntimeException

@Override
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

Transactional communication

@One important configuration in Transactional is Propagation, which specifies transaction Propagation
The corresponding value is defined in the TransactionDefinition

Propagation refers to multiple transactions, that is, when there is a new transaction configuration in the case of existing transactions. When a transaction needs to be created, the propagation setting determines how to deal with the old and new transactions. When there is no transaction at present, propagation is mainly used to judge whether a new transaction needs to be created!
In each case, the transmissibility of the transaction is as follows:

What are the communicable factors

REQUIRED: supports the current transaction. If it does not exist, a new transaction will be created

REQUIRES_NEW: create a new transaction and suspend the current transaction (if any)

NESTED: if there is a current transaction, it is executed in a NESTED transaction. Otherwise, like REQUIRED, a NESTED transaction means that a child transaction will not affect the parent transaction, but will be affected by the parent transaction, which will be described in detail later

SUPPORTS: SUPPORTS the current transaction. If it does not exist, it will be executed in a non transactional manner (different points of REQUIRED)

MANDATORY: supports the current transaction. If there is no exception, an exception will be thrown

NOT_SUPPORTED: executed in a non transactional manner. If there is a transaction, suspend the current transaction. Suspended transaction refers to temporarily removing the database connection object from the thread synchronizer. The connection cannot be committed and rolled back.

NEVER: execute in a non transactional manner. If there is a transaction, an exception will be thrown

Why do we need communication

In order to solve how to deal with new transactions in the case of multiple transactions, various communication behaviors diversify transaction applications and increase the flexibility of transaction use, which can solve various difficult and miscellaneous needs.
By default, the transaction propagation is REQUIRED, that is, the whole process is a shuttle. If there is a problem, it will be rolled back. This kind of processing can solve most transaction needs.

How to achieve

Communicability is created for transactions and is mainly implemented in transactionaspectsupport createTransactionIfNecessary->AbstractPlatformTransactionManager. In gettransaction.

The main logic is:
1. Create a transaction object doGetTransaction
2. Judge whether there is a transaction isExistingTransaction, and handle the handleExistingTransaction according to the existing transaction
3. If there is no transaction, create a new transaction startTransaction, REQUIRED and REQUIRED_ Only when new and NESTED configurations are used can transactions be created

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
		throws TransactionException {

	// Use defaults if no transaction definition given.
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();

	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior to find out how to behave.
		return handleExistingTransaction(def, transaction, debugEnabled);
	}

	// Check definition settings for new transaction.
	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
	}

	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
		throw new IllegalTransactionStateException(
				"No existing transaction found for transaction marked with propagation 'mandatory'");
	}
	else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
		SuspendedResourcesHolder suspendedResources = suspend(null);
		if (debugEnabled) {
			logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
		}
		try {
			return startTransaction(def, transaction, debugEnabled, suspendedResources);
		}
		catch (RuntimeException | Error ex) {
			resume(null, suspendedResources);
			throw ex;
		}
	}
	else {
		// Create "empty" transaction: no actual transaction, but potentially synchronization.
		if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
			logger.warn("Custom isolation level specified but no actual transaction initiated; " +
					"isolation level will effectively be ignored: " + def);
		}
		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
	}
}

The core of the transaction is jdbc connection. Spring will put the jdbc connection of the transaction being implemented into the thread variable of the transaction synchronizer (only one jdbc connection can be stored at the same time).
doGetTransaction will try to take out the connection and put it into the created transaction object.

protected Object doGetTransaction() {
	DataSourceTransactionObject txObject = new DataSourceTransactionObject();
	txObject.setSavepointAllowed(isNestedTransactionAllowed());
	ConnectionHolder conHolder =
			(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
	txObject.setConnectionHolder(conHolder, false);
	return txObject;
}

isExistingTransaction will judge whether this connection exists for the transaction object created by doGetTransaction and activate it.

protected boolean isExistingTransaction(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

startTransaction will create a jdbc connection using the data source, put it into the transaction object and mark it as active.

The core code of the communication implementation is in handleExistingTransaction. You can see:
1.TransactionDefinition. PROPAGATION_ When NEVER, NEVER does not allow transactions. If transactions exist, errors will be reported directly
2.TransactionDefinition.PROPAGATION_NOT_SUPPORTED,NOT_ Supported means that transactions are not supported, and existing transactions will be suspended
3.TransactionDefinition.PROPAGATION_REQUIRES_NEW,REQUIRES_ New suspends an existing transaction and a new transaction
4.TransactionDefinition.PROPAGATION_NESTED, NESTED supports NESTED transactions and creates JDBC 3.0 for the current transaction 0Savepoint
5. The default is transactiondefinition PROPAGATION_ Requirements, use the current transaction

Where are the two remaining types?
1.TransactionDefinition. PROPAGATION_ The mandatory supports the current transaction and will report an error when there is no transaction, so it is not in the handleExistingTransaction, but in the processing code where there is no transaction.
2.TransactionDefinition.PROPAGATION_SUPPORTS supports the current transaction. If it doesn't exist, it will be executed in a non transaction way. Where can I find it temporarily...

/**
 * 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 prepareTransactionStatus(
				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 {
			return startTransaction(definition, transaction, debugEnabled, suspendedResources);
		}
		catch (RuntimeException | Error beginEx) {
			resumeAfterBeginException(transaction, suspendedResources, beginEx);
			throw beginEx;
		}
	}

	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 =
					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.
			return startTransaction(definition, transaction, debugEnabled, null);
		}
	}

	// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
	if (debugEnabled) {
		logger.debug("Participating in 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);
}

extend

In the transaction definition, not only all communicability are listed, but also some other things, such as:
Isolation level
Timeout
Readonly (read only)
These three are all at the database level, that is, Spring transactions mainly provide configuration, which will be passed to the database for specific implementation and processing. However, these configurations will also be used to make some checksum judgments during Spring transaction processing.

Here is a description of the timeout. The timeout is not only the timeout during the execution of the database, but also the self-test of Spring. If the timeout occurs between the start of the transaction and the sending of SQL execution using Connection, Spring will prompt the timeout, that is, it will timeout when it arrives at the database. If the timeout is found during the execution of the database, an error will be reported. After the execution is returned, the timeout will not work, That is, after the database operation is completed, the running time of the thread will not cause the transaction timeout.

Transaction isolation level

Transactions have four characteristics: atomicity, isolation, consistency and persistence.

Why is isolation level required

Isolation refers to the independent execution of multiple transactions without interference with each other. When two independent transactions operate on one piece of data at the same time, there must be a conflict:
Dirty read: data that has not been committed by another transaction is read, and this data may be rolled back and revoked

Non repeatability: the same piece of data is read multiple times in a transaction, and the read value changes. Because the data is modified and committed by another transaction, it cannot be read repeatedly.

Phantom reading: the same data item is read in the transaction, and the read results are inconsistent.

The database provides different solutions to the above problems, forming an isolation level.

What are the isolation levels

Adding, deleting and modifying query in database operation. The query does not involve multi transaction conflict, but involves whether the current transaction can see the modification of data by other transactions. The modification of the same data by multiple transactions directly causes conflicts and can only be queued for execution. Therefore, the isolation level is mainly for data query, that is, whether you can read the modification of other transactions.

The isolation level defined in the Spring transaction is consistent with the database isolation level:
ISOLATION_ READ_ Uncommitted: one transaction can read the update results that are not committed by another transaction.

ISOLATION_ READ_ Committed: the update result of a transaction can only be read by another transaction after the transaction is committed.

ISOLATION_ REPEATABLE_ Read: the default level of mysql. During the whole transaction, the reading result of the same data is the same, no matter whether other transactions are updating the shared data or whether the update is committed or not.

ISOLATION_ Serializable: all transaction operations are executed sequentially. Physically, it is strictly isolated with poor performance. Serialization means queuing one by one.

Transaction suspension and synchronization

Transaction pending

As mentioned earlier, what is a transaction suspension?
If the newly opened transaction does not want to affect the existing transaction, it needs to be queued for execution. First execute the newly created transaction and put the existing transaction aside for waiting.
That is, first take out the database connection of the current transaction, empty the thread jdbc connection, and then put in the jdbc connection of the new transaction. After the execution is completed, restore it, and plug back the previously taken out.

//Hang
protected Object doSuspend(Object transaction) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	txObject.setConnectionHolder(null);
	return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

//recovery
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
	TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}

Here, a thread variable ThreadLocal is used to implement. key is the data source object and value is the jdbc connection
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

I think the function of suspending is that when there are existing transactions (multiple jdbc connections have been created), the newly created transactions can get the correct jdbc connection if they need to share the jdbc connection of the current transaction.

Transaction synchronization

Suspension is an application of transaction synchronization. What is transaction synchronization?
Save the current transaction. When creating a new transaction, you can get it to judge whether there is a transaction and get the jdbc connection if necessary.
After a new transaction is created and started, the transaction is saved using the TransactionSynchronizationManager, which mainly saves the following contents:

//jdbc connection
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
//Are there any actual transactions currently active
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
//Transaction isolation level
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
		definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
				definition.getIsolationLevel() : null);
//Is the transaction read-only
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
//Transaction name
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());

After the suspension and execution of transactions, Chengdu will clear these to prepare for the arrival of new transactions.

SpringBoot transaction automatic assembly principle

AOP dependency is introduced into SpringBootStarter, and TransactionAutoConfiguration is included in the configuration of automatic assembly, so it is not necessary to introduce dependency separately

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
		DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {
	...
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBean(TransactionManager.class)
	@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
	public static class EnableTransactionManagementConfiguration {

		@Configuration(proxyBeanMethods = false)
		@EnableTransactionManagement(proxyTargetClass = false)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
				matchIfMissing = false)
		public static class JdkDynamicAutoProxyConfiguration {

		}

		@Configuration(proxyBeanMethods = false)
		@EnableTransactionManagement(proxyTargetClass = true)
		@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
				matchIfMissing = true)
		public static class CglibAutoProxyConfiguration {

		}

	}
}

When there is a PlatformTransactionManager, the transaction will be automatically assembled. The transaction management configuration Bean: ProxyTransactionManagementConfiguration is introduced through @ enabletransactionmanagement - > @ import (transactionmanagementconfigurationselector. Class)

1. The proxytransactionmanagementconfiguration transaction management configuration class contains three beans:
BeanFactoryTransactionAttributeSourceAdvisor
TransactionAttributeSource -> AnnotationTransactionAttributeSource
TransactionInterceptor

Relationship sorting:
Advisor: Spring transaction advisor, including Advice and transaction metadata
Advice: notification interceptor (aspect enhancement processor), including enhanced logic and enhanced logic entry points
AttributeSource: transaction metadata

The bean that provides transaction support is the advisor, that is, beanfactorytransactionattributesource Advisor (which implements the advisor interface). This bean contains Advice(TransactionInterceptor) and AttributeSource(TransactionAttributeSource)
The TransactionAttributeSource and TransactionInterceptor required for input parameters are also defined in ProxyTransactionManagementConfiguration and will be injected automatically when instantiated

	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {

		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource);
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}
	
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource);
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}

2. When spring bean initialization needs to execute transaction aop proxy enhancement, it needs to obtain the enhanced processor (also known as interceptor, facet processor, or Advisor)
At this time, the transaction interceptor, the actual executor of the transaction, will be found and used to generate a proxy object. The default is Cglib and CglibAopProxy proxy is used

Find logic:
Perform bean post-processing according to the corresponding aop BeanPostProcessor to generate proxy objects

The proxy object is generated by aop's BeanPostProcessor, abstractautoproxycreator Wrapifnecessary performs post-processing agent enhancement.
AbstractAutoProxyCreator is an abstract class. In actual execution, there must be an instantiatable subclass
The corresponding actual post processor is: infrastructure advisor autoproxycreator (AspectJ is not supported). Where does this class come from?
First find all that have implemented advisor Class, and then filter according to the current bean matching to find the available enhanced processor of the confirmed bean. The enhanced processor of aspectJ agent will be ignored.

Here's the point: how to filter out the available Advisor according to the type. This piece uses the tool class AopUtils:
1. Traverse all the advisors to find the available advisors of beanClass.
Method entry: findadvisors that can apply (candidate advisors, bean class)
Traversal execution matching method: can apply (candidate, clazz, has Introductions)

2. Execute transactionannotationparser of TransactionAttributeSource (annotation TransactionAttributeSource) (spring transactionannotationparser - > created in the construction method when automatically installing TransactionAttributeSource)

3.SpringTransactionAnnotationParser.parseTransactionAnnotation gets the method annotated with Transactional. If it does not return, it ends and does not need to be enhanced by the agent

	public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false, false);
		if (attributes != null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null;
		}
	}

3. When the bean with transaction executes the method with transaction, it will first execute the specific intercept in CglibAopProxy Intercept (e.g. DynamicAdvisedInterceptor.intercept) method,
This method is used to intercept the request, find the enhancement processor, and execute the enhancement code before and after the execution of the original target method,
The transaction here is roughly to obtain the transaction first, then execute the original destination method, and try, catch and finally. Once an exception occurs, the transaction is rolled back in catch, and there is no exception to commit the transaction. Regardless of whether an exception occurs or not, the transaction is cleared in finally

CglibAopProxy. proceed -> ReflectiveMethodInvocation. proceed -> TransactionInterceptor. invoke -> TransactionAspectSupport. Invokewithintransaction - > execute target method and transaction rollback or commit

TransactionAspectSupport.invokeWithinTransaction(){
		...
		try {
			// This is an around advice: Invoke the next interceptor in the chain.
			// This will normally result in a target object being invoked.
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// target invocation exception
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			cleanupTransactionInfo(txInfo);
		}
		...
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}

Keywords: Spring AOP Transaction

Added by ctsiow on Sun, 30 Jan 2022 14:06:36 +0200