@Transactional注解真的有必要声明rollbackFor属性吗?

@Transactional注解真的有必要声明rollbackFor属性吗?

​ 今天在看spring的事务底层源码时,想到一个问题,@Transactional注解真的有必要声明rollbackFor属性吗?因为之前有许多资料,包括公司的java编码规范上也有提及到这一点。

​ 不知道读者们有没想过这个问题,但我看完源码后,个人觉得是有必要的。见解不到位的话,希望读者能指明。

异常:如下图所示,我们都知道Exception分为运行时异常RuntimeException和非运行时异常(检查时异常)。

那么spring默认会对如上的哪些异常进行回滚呢?

答案:RuntimeException、Error.

spring源码如下说明:

​ spring在执行方法抛出异常后,会调用completeTransactionAfterThrowing方法,也在该方法中会去判断并执行是回滚还是提交操作。

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);
			}

			// transactionAttribute的实现类为RuleBasedTransactionAttribute,父类为DefaultTransactionAttribute
			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;
				}
			}
		}

我们看到这个if分支进行分析,如果该if满足,则会进行回滚。

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex))

查看txInfo.transactionAttribute.rollbackOn(ex)方法,其中的this.rollbackRules就是我们在@Transactional注解上配置的rollbackFor属性,

public boolean rollbackOn(Throwable ex) {
   RollbackRuleAttribute winner = null;
   int deepest = Integer.MAX_VALUE;

   if (this.rollbackRules != null) {
      // 遍历所有的RollbackRuleAttribute,判断现在抛出的异常ex是否匹配RollbackRuleAttribute中指定的异常类型的子类或本身
      for (RollbackRuleAttribute rule : this.rollbackRules) {
         int depth = rule.getDepth(ex);
         if (depth >= 0 && depth < deepest) {
            deepest = depth;
            winner = rule;
         }
      }
   }

   // User superclass behavior (rollback on unchecked) if no rule matches.
   if (winner == null) {
      return super.rollbackOn(ex);
   }

   // ex所匹配的RollbackRuleAttribute,可能是NoRollbackRuleAttribute,如果是匹配的NoRollbackRuleAttribute,那就表示现在这个异常ex不用回滚
   return !(winner instanceof NoRollbackRuleAttribute);
}

我这里简单配了个ServiceException。如下图

根据一:在rollbackFor属性元素遍历时,会根据getDepth方法去找抛出的异常,是不是就是我们声明的rollbackFor属性中的异常,如果是,判断是不是NoRollbackRuleAttribute类型,是的话就不回滚,否则就回滚。

再看getDepth方法,会根据抛出的异常,判断异常名字是否跟我们声明的异常是否相同,相同则返回,不同则递归抛出异常的父类,直到遍历到Throwable.class顶类。

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);
}

根据二:如果getDepth找不到对应的异常类,就从默认实现类DefaultTransactionAttribute.rollbackOn(Throwalbe x)方法进行判断。

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

DefaultTransactionAttribute判断抛出的异常是RuntimeException或者Error就会进行回滚。说明spring没有对非运行时异常(检查时异常)进行处理,这是因为非运行时异常在编码时,是需要我们开发人员手动去进行try catch进行处理的,也不允许抛出非运行时异常,比如IOException,不然编译器编译都不通过,更别谈运行程序了。

根据三:除了IOException等需要try catch的异常,还有一些不需要try catch处理但是也不属于RuntimeException的异常,比如:SQLException,尽管JdbcTemplate会将sqlException转为BadSqlGrammarException,具体可以参考translateException(String task, @Nullable String sql, SQLException ex)方法,也可以亲自试一下。

Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [insert into user(username) value ('hyza', 'xx')]; nested exception is java.sql.SQLException: Column count doesn't match value count at row 1

除了SQLException,可能还有其它Exception的非RuntimeException的子类,但是不确定是否也会跟jdbcTemplate一样转为RuntimeException类型,所以@Transactional注解指定rollbackFor=Exception.calss确实是万全之策。

总结:尽管spring默认帮我们回滚RuntimeException和Error的异常事务,但@Transactional注解指定rollbackFor=Exception.calss确实是万全之策。

posted @ 2022-10-10 21:32  寻找的路上  阅读(811)  评论(3编辑  收藏  举报