1. Prepare knowledge

1.1 JDBC Transactions

To process transactions, disable the AutoCommit function of the Connection, perform service operations, and then manually commit or rollback.

Connection con = ...
// Turn off auto commit
con.setAutoCommit(false);
// Business processingaDao.insert(...) ; bDao.insert(...) ;try{
    / / submit
    con.commit();
}catch (Exception e){
    / / rollback
    con.rollback();
}
// Resume automatic commit
con.setAutoCommit(true);
Copy the code

1.2 the Spring AOP

When adding methods to Spring’s AOP proxy classes JdkDynamicAopProxy and ObjenesisCglibAopProxy, there are several steps:

  • 1 callDefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdviceTo get aorg.aopalliance.intercept.MethodInterceptorThe list;
  • Create with the list of the MethodInterceptorReflectiveMethodInvocationObject and call the proceed() method;
  • 3 TransactionInterceptor is one of the methodinterceptors on the list, used to execute transactions.

For a concrete implementation of Spring AOP, see the Spring AOP source code implementation step by step parsing.

2 Spring transaction control

  • Spring’s transaction control uses AOP to retrieve the Connection via DataSource before the business method executes, and setAutoCommit(false); Commit or rollback after the service method is executed.
  • The transaction in TransactionInterceptor. Invoke in;
  • The parent class of the TransactionInterceptor callThe TransactionAspectSupport invokeWithinTransactionMethod for specific transaction control.

2.1 Several important classes

2.1.1 TransactionAttribute

Transaction property, a subclass of TransactionDefinition

  • TransactionDefinition default implementation classDefaultTransactionDefinition;
  • TransactionAttribute implementation classes are inherited DefaultTransactionDefinition;
  • TransactionDefinition defines transaction-related attributes:
// Transaction propagation mode
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;

/ / TRANSACTION isolation level, using Java. SQL. Connection. The TRANSACTION * definition
int ISOLATION_DEFAULT = -1; // By default, the database isolation level is used
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; // Read uncommitted
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; // Read submitted
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; // Repeatable
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; / / the serialization
Copy the code
  • DefaultTransactionDefinition key attributes:
	private int propagationBehavior = PROPAGATION_REQUIRED; // Transaction propagation mode
	private int isolationLevel = ISOLATION_DEFAULT; // Transaction isolation level
	private int timeout = TIMEOUT_DEFAULT; // The timeout period
Copy the code
  • Implementation class:
    • RuleBasedTransactionAttribute
    • DefaultTransactionAttribute

2.1.2 PlatformTransactionManager

Transaction manager

  • Methods:
    • TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; // Create TransactionStatus according to TransactionDefinition
    • void commit(TransactionStatus status) throws TransactionException; / / submit
    • void rollback(TransactionStatus status) throws TransactionException; / / rollback
  • Implementation class:
    • DataSourceTransactionManager
    • AbstractPlatformTransactionManager

2.1.3 TransactionStatus

Action class for transaction state

  • SavepointManagerA subclass of
  • Various states and Savepoint creation/rollback/release etc
  • Implementation class:
    • AbstractTransactionStatus
    • DefaultTransactionStatus

2.1.4 TransactionInfo

  • Class of transaction information, including transaction manager, transaction attribute, etc.
  • Through createTransactionIfNecessary create;
  • Important attributes:
    • PlatformTransactionManager
    • TransactionAttribute
    • JoinpointIdentification: cut method, such as com. Xx. Yy. The MockService. MockInsert
    • TransactionStatus

2.2 Transaction execution process

2.2.1 invokeWithinTransaction

InvokeWithinTransaction is the main flow that executes the transaction

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

        // 1. Get transaction attributes, if not, it is not currently in a transaction
		/* TransactionAttributeSource: Transaction attribute source, used to obtain transaction attributes, Such as org. Springframework. Transaction. The annotation. AnnotationTransactionAttributeSource TransactionAttribute: transaction attribute, Such as org. Springframework. Transaction. The interceptor. RuleBasedTransactionAttribute * / 
		TransactionAttributeSource tas = getTransactionAttributeSource();
		finalTransactionAttribute txAttr = (tas ! =null ? tas.getTransactionAttribute(method, targetClass) : null);
		
		/ / 2. Obtain PlatformTransactionManager, such as DataSourceTransactionManager.
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		
		/ / 3. Perform cut method, such as com. Xx, yy. MockService. MockInsert
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null| |! (tminstanceof CallbackPreferringPlatformTransactionManager)) {
		     
		   // 4. If the transaction does not exist or is not a programed transaction, try to create or obtain the transaction and perform commit/rollback.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				// 5 Execute the business code that is the method in the service
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// Rollback to SavePoint if there is SavePoint.
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
			// 6 Run Connection.com MIT ()
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
		    / / TransactionTemplate. Execute using TransactionCallback back in the mode of transaction processing
			// omit the code}}Copy the code

2.2.2 create TransactionAttribute

When creating TransactionInterceptor Bean ProxyTransactionManagementConfiguration, Through the new AnnotationTransactionAttributeSource created TransactionAttributeSource (), and set into the interceptor.

AnnotationTransactionAttributeSource. GetTransactionAttribute by multi-layer calls, call to determineTransactionAttribute method finally, Use the analytical AnnotatedElement SpringTransactionAnnotationParser;

SpringTransactionAnnotationParser.parseTransactionAnnotation:

  • AnnotatedElementUtils. FindMergedAnnotationAttributes AnnotabledElement and Transactional analysis to AnnotationAttributes;
  • Parsing AnnotationAttributes, setting propagation types, isolation levels, and so on. Returns the RuleBasedTransactionAttribute.
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
		RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
		Propagation propagation = attributes.getEnum("propagation");
		rbta.setPropagationBehavior(propagation.value());
		Isolation isolation = attributes.getEnum("isolation");
		rbta.setIsolationLevel(isolation.value());
		rbta.setTimeout(attributes.getNumber("timeout").intValue());
		rbta.setReadOnly(attributes.getBoolean("readOnly"));
		rbta.setQualifier(attributes.getString("value"));
		ArrayList<RollbackRuleAttribute> rollBackRules = newArrayList<>(); Class<? >[] rbf = attributes.getClassArray("rollbackFor");
		for(Class<? > rbRule : rbf) { RollbackRuleAttribute rule =new RollbackRuleAttribute(rbRule);
			rollBackRules.add(rule);
		}
		String[] rbfc = attributes.getStringArray("rollbackForClassName");
		for (String rbRule : rbfc) {
			RollbackRuleAttribute rule = newRollbackRuleAttribute(rbRule); rollBackRules.add(rule); } Class<? >[] nrbf = attributes.getClassArray("noRollbackFor");
		for(Class<? > rbRule : nrbf) { NoRollbackRuleAttribute rule =new NoRollbackRuleAttribute(rbRule);
			rollBackRules.add(rule);
		}
		String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
		for (String rbRule : nrbfc) {
			NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
			rollBackRules.add(rule);
		}
		rbta.getRollbackRules().addAll(rollBackRules);
		return rbta;
	}
Copy the code

Then get PlatformTransactionManager

  • PlatformTransactionManager already declared on your system when Windows starts up Bean;
  • DetermineTransactionManager method, passdefaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);To obtain.

2.2.4 create TransactionInfo

TransactionInfo objects in TransactionAspectSupport. CreateTransactionIfNecessary created:

  • throughPlatformTransactionManager.getTransactionCreate a TransactionStatus;
  • With the created TransactionStatus and other properties, passprepareTransactionInfoMethod to create a TransactionInfo object.
    / / create TransactionInfo
    protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, String joinpointIdentification,
			@Nullable TransactionStatus status) {

		TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
		if(txAttr ! =null) {
			// We need a transaction for this method...
			if (logger.isTraceEnabled()) {
				logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
			// The transaction manager will flag an error if an incompatible tx already exists.
			txInfo.newTransactionStatus(status);
		}
		else {
			// The TransactionInfo.hasTransaction() method will return false. We created it only
			// to preserve the integrity of the ThreadLocal stack maintained in this class.
			if (logger.isTraceEnabled())
				logger.trace("Don't need to create transaction for [" + joinpointIdentification +
						"]: This method isn't transactional.");
		}

		// We always bind the TransactionInfo to the thread, even if we didn't create
		// a new transaction here. This guarantees that the TransactionInfo stack
		// will be managed correctly even if no transaction was created by this aspect.
		/* Bind TransactionStatus to the current ThreadLocal and back up the old TransactionStatus. * /
		txInfo.bindToThread();
		return txInfo;
	}
Copy the code

2.2.5 create TransactionStatus

By AbstractPlatformTransactionManager. GetTransaction created

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
        
        / * 1. Obtain DataSourceTransactionObject; DataSourceTransactionManager. DoGetTransaction method if allows nested transactions, allows setting the Savepoint, in support of the nested transactions. * /
		Object transaction = doGetTransaction();

		// Cache debug flag to avoid repeated checks.
		boolean debugEnabled = logger.isDebugEnabled();

		if (definition == null) {
			/ / 2. If the previous did not create TransactionAttribute (TransactionDefinition subclass), is to create a default value DefaultTransactionDefinition here.
			definition = new DefaultTransactionDefinition();
		}

        . / / transaction exists, through DataSourceTransactionObject txObject. GetConnectionHolder () isTransactionActive judgment ()
		if (isExistingTransaction(transaction)) {
			/ * 3. If there is, according to the definition, getPropagationBehavior () different treatment: (1) PROPAGATION_NEVER: throw new IllegalTransactionStateException (2) PROPAGATION_NOT_SUPPORTED : Suspends the current transaction, the final is performed doSuspend method and implementation for DataSourceTransactionObject setConnectionHolder (null), and bind the DataSource. (3) PROPAGATION_REQUIRES_NEW: Suspend the current transaction, create a new DefaultTransactionStatus, and specify the doBegin method. Create a new transaction. (4) PROPAGATION_NESTED: If nestedTransactionAllowed = = false (the default is false, the open need to manually specify), then throw new NestedTransactionNotSupportedException. Create a new DefaultTransactionStatus and execute createAndHoldSavepoint() to create SavePoint. * /
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}

        // The transaction does not exist
		if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
		}

		// Propagation behavior is PROPAGATION_MANDATORY, and an exception needs to be thrown according to the behavior definition.
		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) {
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + definition.getName() + "]." + definition);
			}
			try {
				booleannewSynchronization = (getTransactionSynchronization() ! = SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction,true, newSynchronization, debugEnabled, suspendedResources);
				// Execute setAutoCommit, etc
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throwex; }}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

2.2.6 doBegin

DataSourceTransactionManager.doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			if(! txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {// 1. Obtain the Connection from a DataSource
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				// 2. Set the ConnectionHolder for later use
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			// 3. Obtain Connection
			con = txObject.getConnectionHolder().getConnection();

			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			// 4. Set readOnly and transactionIsolation for Connection.
			txObject.setPreviousIsolationLevel(previousIsolationLevel);

			if (con.getAutoCommit()) {
			    // AutoCommit must be restored after execution
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				// 4. Disable AutoCommit
				con.setAutoCommit(false);
			}

            / / prepareTransactionalConnection method, if it is (isEnforceReadOnly () && definition. The isReadOnly ()), 'stmt.executeUpdate("SET TRANSACTION READ ONLY"); `
			prepareTransactionalConnection(con, definition);
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if(timeout ! = TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }// Bind the connection holder to the thread.
			if(txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }}catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null.false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); }}Copy the code