Analysis of the execution process of spring transaction source code

The previous blog parsed the underlying code of the dynamic proxy object generated by the transaction. The simple summary is one sentence: If the transaction annotation @Transactional is added and the method is public, spring will generate a proxy object for the bean. As for whether it is jdk or cglib , It depends on whether the class corresponding to your transaction method implements the interface.
Next, let’s look at the execution process after the proxy object is generated.

Interceptor execution

org.springframework.transaction.interceptor.TransactionInterceptor

When I was studying, I used the cglib proxy, because I did not implement the interface, so I used the CGLIB proxy. When the target method is called, it will be called

org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept; 然后会调用到
	org.springframework.transaction.interceptor.TransactionInterceptor#invoke
	 org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

I cut a part of this method to distinguish between declarative transactions and programmatic transactions. Here we only focus on declarative transactions

@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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

	//下面是声明式事务的处理逻辑
	if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
		// Standard transaction demarcation with getTransaction and commit/rollback calls.
		//看是否有必要创建一个事务,根据事务的传播行为做判断
		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.
			//执行目标方法
			retVal = invocation.proceedWithInvocation();
		}
		catch (Throwable ex) {
			// target invocation exception
			//如果有异常就回滚事务,回滚时,根据配置的rollbackFor进行判断
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
		}
		finally {
			//清除事务信息
			cleanupTransactionInfo(txInfo);
		}
		//提交事务
		commitTransactionAfterReturning(txInfo);
		return retVal;
	}
}

In this method,

  • According to the propagation mechanism corresponding to the transaction annotation, determine whether to create a new transaction, or to run nested in the current transaction, etc.;
  • Then execute the target method
  • After executing the target method, if no exception occurs, commit the transaction
  • If an exception occurs, determine whether to rollback according to the configured rollbackFor and noRollbackFor, if there is no need to rollback, submit the transaction, otherwise, rollback the transaction

Therefore, we focus on two methods

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
和
completeTransactionAfterThrowing(txInfo, ex);

Transmission mechanism source code

I will add the source code of the transmission mechanism after finishing

Exception rollback source code

If an exception occurs during the execution of the target method, after catching it, it will enter this method for processing

/**
 * 这里是判断是否需要回滚的逻辑
 * 如果在事务注解上指定了回滚的异常类型、或者指定了不回滚的异常类型,就会在这里进行判断
 * 1.如果判断满足回滚的条件,就事务回滚
 * 		先判断开发人员指定的类型,如果业务代码抛出的异常符合指定的类型,就回滚
 * 		如果没有指定,就判断是否是错误(error)或者运行时异常(runTimeException),如果是,就回滚
 *    所以:如果程序员没有指定回滚的异常,默认情况下,如果是运行时异常或者是error(错误),也是会进行事务的回滚
 * 2.否则,就提交事务
 * @param txInfo
 * @param ex
 */
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);
		}
		/**
		 * 如果程序员有指定回滚或者不回滚的异常,就会进入
		 * 	org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn(java.lang.Throwable)进行判断
		 *
		 * 如果没有指定,就默认调用org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(java.lang.Throwable)
		 * 进行判断  
		 */
		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 {
      // 这里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 of the more important methods here is to judge exceptions and determine whether the exceptions thrown by the current business code meet the rollback requirements;

Insert picture description here

This method here is to determine whether the currently thrown exception conforms to the exception rollback information configured on the @Transactional annotation

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn
 
  @Override
	public boolean rollbackOn(Throwable ex) {
		if (logger.isTraceEnabled()) {
			logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
		}
		RollbackRuleAttribute winner = null;
		int deepest = Integer.MAX_VALUE;
  	// 这里的rollbackRules是在解析@Transactional注解的时候,保存的;parseTransactionAnnotation在这个方法中解析的注解;这里的rollbackRules既有rollbackException中配置的,也有noRollbackException中配置的
		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				/**
				 * depth:当前rule和ex的相似度
				 *
				 * deepest:和ex最相近的depth
				 * winner:相似度最近的RollbackRuleAttribute
				 * 如果当前rule,返回的depth比上一次返回的depth小,且大于0,就用当前这次异常
				 *
				 * 这里会保存和业务代码抛出异常最相似的rule
				 * 比如:
				 * 	我在代码中,抛出了一个java.lang.ArithmeticException: / by zero
				 *
				 * 	如果我在@Transactional(rollbackFor = {Exception.class,ArithmeticException.class})
				 * 	那肯定会返回ArithmeticException这个rule,因为ArithmeticException返回的depth是0
				 * 	Exception返回的的depth是2  下面解释为什么一个是0,一个是2
				 */
				int depth = rule.getDepth(ex);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;
				}
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
    // 如果没有匹配到最相似的异常、或者没有配置回滚异常类,就会执行这里,这里是调用父类的方法进行判断
		if (winner == null) {
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}
		/**
		 * 如果相似度最近的rule不是无需回滚的类型,就可以进行事务回滚
		 */
		return !(winner instanceof NoRollbackRuleAttribute);
	}
	// 这里就是父类对应的判断逻辑,也就是说:如果程序员单单加了一个@Transactional注解,那么在业务代码抛出运行时异常后者error的时候,还是会回滚的
	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}

Then let's look at the above method of judging abnormal similarity: int depth = rule.getDepth(ex);

Here I call it similarity, I don’t know if it is appropriate; here is the comparison between the name corresponding to the exception A thrown in the business code and the name corresponding to the exception B specified by the programmer. If it matches, it returns depth, if it matches If not, call recursively, compare the parent class corresponding to the A exception thrown in the business code with the name of the B exception, and call recursively in turn

/**
	 * 这里其实就是用业务代码抛出的异常和程序员在@Transactional注解中执行的异常信息进行对比
	 *
	 * 实际底层使用的是String.contains()方法
	 * exceptionName:就是程序员指定的异常对应的类名;比如:我指定的是rollbackFor = Exception.class,那这里的exceptionName就是:java.lang.Exception
	 *
	 * 1.如果当前抛出的异常和程序员指定的异常匹配不上,就依次递归调用抛出异常的父类和程序员指定的异常进行比较,
	 * 		1.1 直到匹配上,就返回当前的depth,depth每递归调用一次,就+1
	 * 		1.2	或者是到Throwable依旧没有比对上,这时,就表示我指定的异常和代码抛出的异常不匹配
	 *
	 * 	这两种场景也好验证:
	 * 		1.首先,我在业务代码中,加上这么一行代码:int i = 10/0;
	 * 		2.然后在@Transactional注解中加上rollbackException = Exception.class	或者是rollbackException = IoException.class
	 * 	    这两种异常,最后事务都会回滚,但是效果却是不一样的
	 * 	    如果我加的是rollbackException = Exception.class,这里会匹配上,返回的是depth是2
	 * 	    但是如果加的是rollbackException = IoException.class,这里返回的是-1
	 *
	 * 	    因为:如果是rollbackException = Exception.class;那这里在匹配的时候,会递归调用两次,
	 * 	    int i = 10/0;会抛出java.lang.ArithmeticException: / by zero
	 * 	    ArithmeticException的父类是RuntimeException;RuntimeException的父类是Exception;所以只有递归调用两次,才能匹配到我指定的Exception.class
	 *
	 * 	    但是,如果我指定的是IoException.class,那永远也匹配不上,因为IOException和ArithmeticException都继承了RuntimeException,是并行的关系,在最后
	 * 	    递归调用到父类Throwable的时候,就会返回-1(即使这里返回了-1,最后事务还是会回滚,为什么?因为在方法之后会判断,如果程序员指定的异常和当前业务代码抛出的异常不相似,那就会判断业务代码抛出的异常是否是运行时异常或者error)
	 *
	 * @param exceptionClass:当前业务代码抛出的异常/或者是抛出异常对应的父类
	 * @param depth:相似度/或者说是深度
	 * @return
	 */
	private int getDepth(Class<?> exceptionClass, int depth) {
		if (exceptionClass.getName().contains(this.exceptionName)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionClass == Throwable.class) {
			return -1;
		}
		return getDepth(exceptionClass.getSuperclass(), depth + 1);
	}

Therefore, when the business code throws an exception,

  1. It will judge the exception type and the exception type specified by the programmer, find the exception with the most similar matching degree, and finally determine whether the most similar exception is the exception class configured by noRollbackException. If it is, there is no need to roll back and submit the transaction.
  2. If there is no match, judge whether the thrown exception is Error or RuntimeException, if it is, it will also be rolled back

Guess you like

Origin blog.csdn.net/CPLASF_/article/details/108695188