• @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确实是万全之策。

  • 相关阅读:
    [架构之路-237]:目标系统 - 纵向分层 - 网络通信 - DNS的递归查询和迭代查询
    Kaggle比赛:成人人口收入分类
    【编程题】【Scratch一级】2022.09 猫捉老鼠
    Bug系列路径规划算法原理介绍(三)——Tangent BUG算法
    VMware创建Win10操作系统虚拟机
    【QEMU中文文档】1.3 弃用功能
    web网页设计期末课程大作业——汉中印象旅游景点介绍网页设计与实现19页面HTML+CSS+JavaScript
    【C/PTA——7.数组1】
    c++基础(十七)——职工管理系统实现
    火山引擎云原生存储加速实践
  • 原文地址:https://www.cnblogs.com/process-h/p/16777472.html