你真的会用@Transactional吗?

今天跟大家讲一讲 @Transactional,这个注解相信大家应该都用过,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用 @Transactional 注解时需要注意许多的细节,虽然它看起来简单,但不知道底层原理的话,用起来往往达不到我们想要的效果。
事务分为编程式事务声明式事务两种。编程式事务指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。声明式事务是基于 AOP 面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,声明式事务也有两种实现方式,一种是基于 TX 和 AOP 的 xml 配置文件方式,二种就是基于 @Transactional 注解了,实际开发中 @Transactional 用的比较多。
@Transactional 可以作用在类上,当作用在类上的时候,表示所有该类的 public 方法都配置相同的事务属性信息。@Transactional 也可以作用在方法上,当方法上也配置了 @Transactional,方法的事务会覆盖类的事务配置信息。
今天我们通过看 @Transactional 的源码来和大家重新认识一下 @Transactional 的用法。

源码

我们从 @Transactional 点进去,看到里面有很多的属性,我们来依次分析一下。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};}

transactionManager 和 value 是一组: 大多数项目只需要一个事务管理器,但是在有些项目中为了提高效率、或者有多个完全不同又不相干的数据源,所以会有多个事务管理器,这里填的就是你想用的事务管理器的 Bean 的 id。
isolation属性: 是事务的隔离级别,默认值为 Isolation.DEFAULT。这里有四个隔离级别,在Innodb里面默认用的是 RR 级别,具体这四个级别是什么意思,等后面写 LBCC 和 MVCC 的时候再和大家介绍。

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

timeout属性: 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly属性 : 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor属性 : 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

现在我们来看个例子,来加深下这个属性的用法。这里我写了一个转账的方法,里面抛了一个InterruptedException 异常,此时数据库的事务会回滚吗?
![在这里插入图片描述](https://img-blog.csdnimg.cn/e4aadf7449eb45838093685b1ce69d9e.png#pic_center


执行一下,报错了

我们来看下数据库,金额产生变化了,之前是 1000 和 0 的,现在变成了 900 和 100,这和我们预期的不符啊,我们想的是发生异常了,事务将进行回滚,怎么还能执行成功呢?

抛出一个运行时异常。

再执行一下

我们看下数据库还是 1000 和 0,完好无损,说明回滚成功了。

那为什么两个异常,一个异常回滚成功了,一个异常回滚失败呢,现在我们来看下源码。看到这里小伙伴明白了吗,原来回滚是只判断异常类型是 RuntimeException 和 Error 的才会进行回滚,别的异常类型是不会回滚的。

看到这里小伙伴可能有点懵,什么是 RuntimeException 类型和 Error 类型呢,这里我用 idel 的工具画了一个 UML 类图,小伙们可以看下,RuntimeException 和 Error 我也用红框圈出来了。所以小伙伴如果想要捕获所有的异常,这时候就可以用 @Transactional(rollbackFor = Exception.class),这是一个偷懒的写法,当然这里也可以指定多个,可以指定自己的业务异常等等。

noRollbackFor属性: 这个就是和上面相反的了,抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

最后说下propagation属性,这个也是本篇文章的核心重点,它一共有七种传播行为,我们来分别看下。

传播行为 说明
Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
Propagation.SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
Propagation.MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
Propagation.REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
Propagation.NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务。
Propagation.NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常。
Propagation.NESTED 和 Propagation.REQUIRED 效果一样。

当我们不指定的时候默认使用的是 Propagation.REQUIRED,另外两个常用的是 Propagation.REQUIRES_NEW 和 Propagation.NESTED,而另外四种我们基本是不会去使用的,所以小伙伴也没必要去了解,那么这几个传播行为到底是什么意思,我们光看概念可能一头雾水,不知所云,没关系,接下来我将用实战演练的方式来带大家领略下到底怎么用法。

实战演练

实验一

首先我们写三个 service 方法

UserMoneyService 里面有一个总的方法,上面加了一个 @Transactional,里面干两件事,一个是记录转账日志,一个是进行转账操作。

我们把 logService 点进去,里面有个记录转账日志方法,方法上也加上一个 @Transactional,并且在方法内部抛出一个异常。

我们把 moneyService 点进去,里面有个转账操作方法,方法上也加了一个 @Transactional。

现在大家逻辑关系都搞清楚了吧,一个大的 @Transactional 里面有两个小的 @Transactional。此时我运行一下,大家猜下结果会是怎样,有哪些是执行成功,哪些是执行失败,还是都成功还是都失败?

思考一分钟,我们继续往下走。执行一下,报错了

我们看下数据库,用户表金额没有变

转账日志表也没有插入数据


看来是一样都没有执行成功,这个是什么原因呢?我们来分析下,我这里画了个伪代码,在 logService.addChangeLog 中报错了,然后被捕捉到,然后进行事务的回滚,回滚完之后干了一个重要的事情,那就是 throw ex,把异常又向外抛出了,此时被外层的 try catch 给捕捉到了,所以代码自然也不会向下执行,moneyService.changeUserMoney 没有被执行到,所以数据库里的值都没有产生变化。


小伙伴看到我画的伪代码,可能认为我是在一派胡言,没关系,我给大家看下源码,它确实是这么干的。

实验二

现在我们在记录转账日志的方法上做一个小改变,把传播行为改成 REQUIRES_NEW,此时是开启了一个新的事务,此时小伙伴再猜想下,表里的数据将会做如何的变化?

思考一分钟,执行一下,继续报错

看下结果,数据库里两张表的数据都没有产生变化


不知道小伙伴有没有猜对,其实这个原理和上面是一样的,不管是 REQUIRES 还是 REQUIRES_NEW ,在 logService.addChangeLog 捕获到异常后都会向外抛出异常,外层捕获到异常后代码将不会向下执行。

实验三

此时 logService.addChangeLog 上的传播属性还是 REQUIRES_NEW

moneyService.changeUserMoney 操作使用的是默认传播行为,此时把方法异常移动到了这里面了,现在大家再猜下数据库的数据又将做如何变化?

思考一分钟,执行一下,依然报错

我们在来看数据库,此时日志表已经有记录了,用户表里的金额还是没有变化,这个大家可以理解吧,因为记录转账日志表在前面已经先执行了,而且日志表开启了一个新的事务,所以在执行完直接提交数据库成功了,而 moneyService.changeUserMoney 报错了,回滚然后抛出异常,自然不会影响上面的代码,所以结果就是日志表记录了,用户表数据没有变化。

实验四

现在我把 logService.addChangeLog,moneyService.changeUserMoney 传播属性都改为 REQUIRES_NEW,并且在转账操作中抛异常,此时数据库又该做如何变化?


执行一下,直接看数据库,日志表记录了,转账记录没有变化,小伙伴答对了吗。此时相当于开了三个事务连接,各干各的,原理同上面一样,日志表的先行提交,转账记录报错不影响它。

实验五

现在 logService.addChangeLog,moneyService.changeUserMoney 的传播属性还是REQUIRES_NEW,只是这次我把异常移到日志操作里来了,此时数据又该做如何变化?


执行一下,在看数据库,日志表和用户表都没有变化,你们答对了吗,还是实验二的原理,不管是 REQUIRES 还是 REQUIRES_NEW,logService.addChangeLog 在捕获到异常后都会向外抛出异常,外层异常捕获到后代码将不会往下走,异常后面的不管你十个八个都不会执行。



那么碰到这种情况怎么办,很简单,加一个 try catch,我们在很多源码中也看到空的 catch ,里面什么事情都没有做,目的就是自己的异常自己吃掉,不向外抛,不影响上层建筑,也就是不影响别的事务。


到这里小伙伴不知道有没有被我的连环炮击给绕晕,没关系,只要理解我讲的原理以后,不管你怎么变还是那么多套路,最后我们来看一下嵌套事务。

实验六

嵌套事务其实是没开启事务,效果跟 REQUIRED 一样,只是多了一个 addPoint 记录切入点。此时我把 logService.addChangeLog,moneyService.changeUserMoney 传播行为都改成了 NESTED,并且在转账操作中抛出一个异常,此时小伙伴在猜下,这是最后一道题了,争取答对哦!



思考半分钟,执行一下



嵌套事务也叫寄生事务,里面两个事务和最外面的事务其实用的是同一个数据库连接。所以当moneyService.changeUserMoney 报错的时候,向外抛出异常,外层捕捉到后由于使用的是同一个连接,所以将这个连接上干的所有事情都进行回滚,从而数据库里一样也没有执行成功。
通过这六个实验,小伙们应该对传播行为有了更深的了解了吧,如果你还记不住,或者搞不清楚,没关系,像我一样做实验,光说不练假把式,光看你说能看会吗?

失效场景

到这里我们差不多也要接近尾声了,最后我们来说下 @Transactional 的失效场景。

1、@Transactional 应用在非 public 修饰的方法上,@Transactional 将会失效。protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

2、@Transactional 注解属性 propagation 设置错误。PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER上面这三种我都没有讲到,因为用了它们事务是不会发生回滚,加了等于没加。

3、@Transactional 注解属性 rollbackFor 设置错误,这个上面演示给大家看了,默认的只会对 RuntimeException 类型和 Error 类型的才进行回滚。如果在事务中抛出其他类型的异常,却希望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。

4、同一个类中方法调用,导致 @Transactional 失效。这个我在上面做实验的时候已经演示过了,总方法调子方法的时候,要放在不同的 service 里面,如果放在一个类里面,事务调用是不会生效的。

5、异常被吃了,这个在实验五里面也说了,吃掉以后虽然不影响其他事务,但是其他数据的 commit 会造成数据不一致,所以有些时候 try catch 反倒会画蛇添足。

6、数据库引擎不支持事务,这一点很简单,myisam 引擎是不支持事务的,innodb 引擎支持事务。

7、数据源没有配置事务管理器,这个也很简单,要使用事务肯定要配事务管理器。Hibernate 用的是HibernateTransactionManager,JDBC 和 Mybatis 用的是 DataSourceTransactionManager。

8、最后就是一个方法内多数据库(多数据源)的情况下会失效,这个也是我同事做实验得出来的。

总结:@Transactional 注解的看似简单易用,但如果对它的用法一知半解,还是会踩到很多坑的。本文不一定总结得全,但是会了这么多足以应付工作中碰到的问题,最后原创不易,如果你觉得写的不错,请点一个赞!

你真的会用@Transactional吗?相关推荐

  1. Spring官方都推荐使用的@Transactional事务,为啥我不建议使用!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 事务管理在系统开发中是不可缺少的一部分,Spring提供了 ...

  2. 你的接口,真的能承受高并发吗?

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 作者:肥朝 出处:公众号[肥朝] 前言 本篇主要讲解的是前阵子的一个压测问题.那么就直接开门见山 ...

  3. Spring Transactional还能导致生产事故?

    在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启.提交.回滚操作.甚至很多人心里已经将Spring事务与@Trans ...

  4. Spring官方推荐的@Transactional还能导致生产事故?

    在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启.提交.回滚操作.甚至很多人心里已经将Spring事务与@Trans ...

  5. 【Java】注解 @Value 你真的会用么

    前言 对于从事java开发工作的小伙伴来说,spring框架肯定再熟悉不过了.spring给开发者提供了非常丰富的api,满足我们日常的工作需求. 如果想要创建bean实例,可以使用@Controll ...

  6. exception e 是泛类吗_Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们biaji一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: 注 ...

  7. springboot异步和切面_Spring异步编程 你的@Async就真的异步吗?异步历险奇遇记

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们biaji一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: 注 ...

  8. springboot异步和切面_Spring异步编程 | 你的@Async就真的异步吗 ☞ 异步历险奇遇记...

    引言有点长 前端的宝宝会用ajax,用异步编程到快乐的不行~ 我们java也有异步,用起来比他们还快乐~ 我们bia~ji~一个注(gǒupí)解(gāoyào),也是快乐风男... 且看下面的栗子: ...

  9. springboot异步和切面_Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记

    Spring异步编程 | 你的@Async就真的异步吗?异步历险奇遇记 点击上方"java进阶架构师",选择右上角"置顶公众号" 20大进阶架构专题每日送达 引 ...

最新文章

  1. 阿里巴巴开源技术汇总:115个软件(一)
  2. 服务器上tomcat修改内存,修改Tomcat运行内存
  3. 教育部发文:AI、算法等2018年进入全国高中课程!
  4. QT textedit 滚动条自动往下滚动
  5. c语言 char operator,C语言取模运算符(modulus operator)“%”的作用是什么
  6. Leetcode 79. 单词搜索 (每日一题 20210720 同类型题)
  7. 巨头垄断,Facebook直接封杀了一个国家!
  8. cnn验证码识别代码_中文项目:快速识别验证码,CNN也能为爬虫保驾护航
  9. python 爬取svg数据_抓取SVG图表
  10. 用栈和递归求解迷宫问题
  11. html网页设计大赛_HTML5网页设计大赛 || 决赛名单公布
  12. poj1113/hdu1348(凸包。。。两个网站上的输入输出有点出入)
  13. 后端开发如何设计数据库系列文章(一)设计传统系统表结构(Java开发)
  14. Kaggle 美女小姐姐自述:我是怎么成为竞赛中 Top 0.3% 的?
  15. leetcode题解102-翻转二叉树
  16. UI培训之零基础如何自学UI设计?
  17. redis技术分享ppt_精美PPT制作培训 | 技术二部内部分享
  18. 10月北京二手房交易量强势反弹 房价环比上涨5.2%
  19. 学习插画前期需要什么基础知识?插画师入门基础先学什么?
  20. Python海龟画图 画一个爱心 赶快给女朋友来一个

热门文章

  1. 如何化解总想快速崛起导致的焦虑
  2. 银联在线支付5.0.0版-仿真端
  3. 微信Mac 3.0.0内测版上线!终于可以用电脑刷朋友圈了!!
  4. 图解HTTP读书笔记.第八章
  5. 国内期刊投稿用 CTeX(CTeX_2.9.2.164_Full)
  6. CSS 中的 text-decoration 属性
  7. Springboot 系列(十)使用 Spring data jpa 访问数据库
  8. 如何加强幼儿园安全管理
  9. PyCharm提示 Backend Qt5Agg is interactive backend. Turning interactive mode on.
  10. J-Link各版本驱动的下载