首页 » java

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

   发表于:java评论 (0)   热度:1363

最近查看了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捕获异常;

 

 

(。・v・。)
喜欢这篇文章吗?欢迎分享到你的微博、QQ群,并关注我们的微博,谢谢支持。