• Spring事务传播属性介绍(一).required 和 reuqires_new


    Mandatory、Never、Not_Support传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10260030.html

      Nested传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10260066.html

      我的Spring事务传播属性介绍比较传送门:https://files.cnblogs.com/files/lvbinbin2yujie/Spring_Tx_Note.rar

    最近查看了Spring事务源码,是4.2.x的版本还是4.3.x的版本,简单了解了一些事务的概念,介绍下我对Spring事务源码的分析.

    Spring一共七种事务传播属性,本文先来作为开篇介绍。

      REQUIRED事务,Spring Transactional注解默认的事务,需要该方法在有事务情况下运行,如果当前没有事务就新建一个事物;

      REQUIRES_NEW事务,当前方法运行没有事务,新建一个事物,当前方法有事务将当前事务挂起,新事物执行完毕再恢复原有事务;

    这里提一点.,以前不明白什么是同一事务核心是什么,后来看到有位仁兄介绍,同一事务的事务信息、事务状态对象不同,但是底层是同一个Connection对象;这点我深以为.

    实验说明环节

    包结构:          

     

    Spring配置文件:  (简单介绍下, 定义了一个数据源、DataSourceTransactionManager;此外tx:annotation-driven作用是用来启用Transactional注解的)

         

    ServiceA.java文件

     

    ServiceB.java文件

     

    测试Main方法:

     

    PROPAGATION_REQUIRED

    说明: 默认的事务级别, 需要事务;如果当前没有事务,则创建新的事务;如果有事务呢,就加入当前的事务;

    如果所有的Transactional标签都是默认的,REQUIRED时,方法里的提交、回滚都是一起的,要么所有都提交,要么所有都回滚;一荣俱荣,一损俱损;

    查看源码时候打印事务的日志:

     

    查看输出结果:  可以看到 确实提交了,并且加入了之前的事务,加入之前事务就是共用的一个Connection对象;

     

     情景二:  修改ServiceB的addUser方法  来模拟调用其他业务方法时候执行抛出运行时异常;  (同时将数据库之前测试结果 删除,便于观察)

     

       先查看结果:  艾尼路记录添加进来又回退了, 查看日志:

     

    说明:  addUser抛出类型为RuntimeException类型的异常,callB捕获该异常之后,发现addUser在事物callB中,于是将事务状态DefaultTransactionStatus对戏中的事务对象DataSourceTransactionObject中的ConnectionHolder对象,即持有底层Connection的对象,将ConnectionHolder标记为rollbackOnly为true,然后将异常抛给调用addUser的callB方法;

    在callB调用时根据Spring事务回滚规则,决定回滚操作;因为在情景二同一个事务中ConnectionHolder中持有的Connection对象是同一个,所以callB方法整个回滚了;

     

     

    情景三: callB方法中手动try-catch来捕获异常会发生什么呢?

             修改ServiceA的callB方法(为了便于观察,不输出异常,只是简单打印)

     

    直接查看输出日志(最后一行抛出的异常排版原因未截全): 查看日志,记录确实添加了但是又回滚过了,数据库里没有记录。

    异常信息:

    Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

     

     

    说明: callB作为事务的开始,addUser作为事务的一部分,addUser抛出异常以后将ConnectionHolder对象的rollbackOnly设置为true,标记为需要回滚,但是执行完callB方法,没有抛出异常,就认为应该正常提交; 提交之前会校验两次,有一次是 Global transaction is marked as rollback-only but transactional code requested commit;

    意思是全局事务需要回滚操作,但是事务代码现在要提交,这时候Spring还是会回滚;(通俗点就是addUser方法需要回滚,但是callB方法没有发生预料之外的异常,因为异常自己手动捕获,这时候Spring还是会回滚);

             callB和ServiceB的事务信息对象TransactionInfo是不一样的,其属性事务状态TransactionStatus也不是同一个对象,TransactionStatus的底层事务对象transaction也不是同一个对象,transaction持有ConnectionHolder对象的Connection确实同一个,这样就保证了可以使用Connection对象来保持事务的一致性,一起提交、一起回滚; 保证两个Connection是同一个对象的是ThreadLocal,TransactionSynchronizationManager的resources里存放了着;

     

    总结:  REQUIRED不建议手动捕获异常,会破坏Spring的事务规则;try-catch需要结合传播类型,再决定使用与否;

    PROPAGATION_REQUIRES_NEW

    说明: 创建新的事务并执行,如果当前有事务,那将当前事务挂起,新建一个事物;

     

    给ServiceA类callB方法修改下:

    ServiceB类添加方法addUserFail,事务属性设置为REQUIRE_NEW

     

    正常情况下查看输出日志:

    可以发现,当进入REQUIRED_NEW事务里的方法时,挂起了原来的事务,事务执行完毕恢复了事务;并且外层事务和addUserFail事务是分别提交的;

     

     

    情景三.

    ServiceA方法:

     

    ServiceB方法:

    测试类方法:

     

     

    说明:callB2方法调用了ServiceB的两个事务方法,ddUserSuccess方法是REQUIRED事务,addUserFail方法是REQUIRED_NEW事务,按照之前分析的,REQUIRED_NEW的方法是一个新的事务,那我抛出异常自己就会回滚,不应该干扰到callB2方法的回滚;

    执行结果: addUserSuccess方法被带着一起回滚了,即外层事务也被带着一起回滚了;

     

    查看源码发现: addUserFail的确新建了事务,然后抛出异常之后,着手回滚,回滚完成后将异常throw了,异常被throw那就会丢给调用addUserFail的地方,没错,丢到了callB2方法里,那callB2也着火了,发生异常,这时候callB2的事务也被认定为执行失败应当回滚,那callB2的事务就开始回滚,callB2内部事务addUserSuccess回滚, 所以一条记录都没有写进去.

    (想了下,REQUIRED_NEW事务的方法就在调用他的地方手动捕获异常,不让异常向上传递了,这样就能达到目的,且不会像REQUIRED一样报异常)

     

    简单画了如下例子,外层事务为REQUIRED类型;外层事务有两个方法,A、B;Spring默认回滚规则为RuntimeException或Error类型,下面例子抛出异常也是RumtimeException或Error类型,且没有手动try-catch捕获异常;

     

     

  • 相关阅读:
    关于.NET Reflector
    Windows Debugging之九
    在IA32如何将程序计数器的值放入到整数寄存器中?
    [陆续添加]计算机网络最最基础的基本概念
    ASCII表
    [翻译文章]我们是如何做到的: 提高SharePoint.Microsoft.com站点的性能
    Windows API是什么?
    寄存器使用惯例
    阅读笔记 了解ASP.NET底层架构 之一
    汇编程序中的返回值
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10259897.html
Copyright © 2020-2023  润新知