spring中12种@Transactional的失效场景(小结)

目录
  • 一、失效场景集一:代理不生效
  • 二、失效场景集二:框架或底层不支持的功能
  • 三、失效场景集三:错误使用@Transactional
  • 四、总结

数据库事务是后端开发中不可缺少的一块知识点。Spring为了更好的支撑我们进行数据库操作,在框架中支持了两种事务管理的方式: 编程式事务声明式事务

日常我们进行业务开发时,基本上使用的都是声明式事务,即为使用@Transactional注解的方式。

常规使用时,Spring能帮我们很好的实现数据库的ACID (这里需要注意哦,Spring只是进行了编程上的事务,最终数据上的事务还是有数据库实现的) 。

但是,只要是人写的代码,就一定会有Bug。

如果我们不了解@Transactional的失效场景或者说踩坑点,那么在业务开发的过程中总是会出现一些匪夷所思的Bug。

同样它也是面试时高频的考点哦!

本文将罗列@Transactional的失效场景,并分析其失效原因。

一、失效场景集一:代理不生效

Spring中对注解解析的尿性都是基于代理的,如果目标方法无法被Spring代理到,那么它将无法被Spring进行事务管理。

Spring生成代理的方式有两种:

  • 基于接口的JDK动态代理,要求目标代理类需要实现一个接口才能被代理
  • 基于实现目标类子类的CGLIB代理

Spring在2.0之前,目标类如果实现了接口,则使用JDK动态代理方式,否则通过CGLIB子类的方式生成代理。

而在2.0版本之后,如果不在配置文件中显示的指定spring.aop.proxy-tartget-class的值,默认情况下生成代理的方式为CGLIB,如下图

顺着代理的思路,我们来看看哪些情况会因为代理不生效导致事务管控失败。

(1)将注解标注在接口方法上

@Transactional是支持标注在方法与类上的。一旦标注在接口上,对应接口实现类的代理方式如果是CGLIB,将通过生成子类的方式生成目标类的代理,将无法解析到@Transactional,从而事务失效。

这种错误我们还是犯得比较少的,基本上我们都会将注解标注在接口的实现类方法上,官方也不推荐这种。

(2)被final、static关键字修饰的类或方法

CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰后,无法继承父类与父类的方法。

(3)类方法内部调用

事务的管理是通过代理执行的方式生效的,如果是方法内部调用,将不会走代理逻辑,也就调用不到了。

例如

createUser中调用了内部方法createUser1,并且createUser1方法上设置了事务传播策略为:REQUIRES_NEW,但是因为是内部直接调用,createUser1不能不代理处理,无法进行事务管理。在createUser1方法抛出异常后就插入数据失败了。

但是这种操作在我们业务开发的过程中貌似还挺常见的,怎么样才能保证其成功呢?

方式1:新建一个Service,将方法迁移过去,有点麻瓜。

方式2:在当前类注入自己,调用createUser1时通过注入的userService调用

方式3:通过AopContext.currentProxy()获取代理对象

道理类似于方式2,就是为了通过代理来访问内部方法

(4)当前类没有被Spring管理

这个没什么好说的,都没有被Spring管理成为IOC容器中的一个bean,更别说被事务切面代理到了。

这种Bug看上去比较蠢,但没准真的有人犯错。

二、失效场景集二:框架或底层不支持的功能

这类失效场景主要聚焦在框架本身在解析@Transactional时的内部支持。如果使用的场景本身就是框架不支持的,那事务也是无法生效的。

(1)非public修饰的方法

我们在标有@Transactional的任意方法上打个断点,在idea内能看到事务切面点如下图所示

点击去这个方法,在开头有这么一个调用

继续进去

就能看到这么一句话了

不支持非public修饰的方法进行事务管理。

(2)多线程调用

跟上面一样的的操作,我们能够逐层进入到TransactionAspectSupport.prepareTransactionInfo方法。

注意看以下这段话

从这里我们得知,事务信息是跟线程绑定的。

因此在多线程环境下,事务的信息都是独立的,将会导致Spring在接管事务上出现差异。

这个场景我们要尤其注意!

给大家举个例子

主线程A调用线程B保存Id为1的数据,然后主线程A等待线程B执行完成再通过线程A查询id为1的数据。

这时你会发现在主线程A中无法查询到id为1的数据。因为这两个线程在不同的Spring事务中,本质上会导致它们在Mysql中存在不同的事务中。

Mysql中通过MVCC保证了线程在快照读时只读取小于当前事务号的数据,在线程B显然事务号是大于线程A的,因此查询不到数据。

(3)数据库本身不支持事务

比如Mysql的Myisam存储引擎是不支持事务的,只有innodb存储引擎才支持。

这个问题出现的概率极其小,因为Mysql5之后默认情况下是使用innodb存储引擎了。

但如果配置错误或者是历史项目,发现事务怎么配都不生效的时候,记得看看存储引擎本身是否支持事务。

(4)未开启事务

这个也是一个比较麻瓜的问题,在Springboot项目中已经不存在了,已经有DataSourceTransactionManagerAutoConfiguration默认开启了事务管理。

但是在MVC项目中还需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

三、失效场景集三:错误使用@Transactional

注意啦注意啦,下面这几种都是高频会出现的Bug!

(1)错误的传播机制

Spring支持了7种传播机制,分别为:

上面不支持事务的传播机制为:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。

如果配置了这三种传播方式的话,在发生异常的时候,事务是不会回滚的。

(2)rollbackFor属性设置错误

默认情况下事务仅回滚运行时异常和Error,不回滚受检异常(例如IOException)。

因此如果方法中抛出了IO异常,默认情况下事务也会回滚失败。

我们可以通过指定@Transactional(rollbackFor = Exception.class)的方式进行全异常捕获。

(3)异常被内部catch

UserService

UserService1

如上代码UserService调用了UserService1中的方法,并且捕获了UserService1中抛出的异常。

你将能看到控制台出现这样一个报错:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

默认情况下标注了@Transactional注解的方法的事务传播机制是REQUIRED,它的特性是支持当前事务,也就说加入当前事务。我们在UserService中开始事务,然后再UserService1中抛出异常回滚UserService中的事务,将其标记为只读。

但是在UserSevice中我们捕获了异常,此时UserService上的事务认为正常提交事务。最后在提交时发现事务只读,已经被回滚,则抛出了上述异常。

因此这里如果需要对特定的异常进行捕获处理,记得再次将异常抛出,让最外层的事务感知到。

(4)嵌套事务

上面是我想同时回滚UserService与UserService1。但是也会有这种场景只想回滚UserService1中报错的数据库操作,不影响主逻辑UserService中的数据落库。

有两种方式可以实现上述逻辑:

1.直接在UserService1内的整个方法用try/catch包住

2.在UserService1使用Propagation.REQUIRES_NEW传播机制

四、总结

本文为大家分析@Transactional注解使用过程中失效的12种场景

最后,@Transactional注解虽香,但是复杂业务逻辑下,为了更好的管理事务与把控业务处理时事务的细粒度,我还是推荐大家使用编程式事务。

到此这篇关于spring中12种@Transactional的失效场景(小结)的文章就介绍到这了,更多相关spring @Transactional 失效场景 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring事务注解@Transactional失效的八种场景分析

    首先说一下最近自己遇到的一个坑: @Transactional service A(){ try{ insert(); serviceB.update(); }catch(){ throw new RunTimeException(); } } serviceB(){ @Transactional update(){ try{ mapperB.update(); }catch(){ throw new RunTimeException(); } } } mapperB (){ try{ //do

  • Spring @Transactional注解失效解决方案

    这篇文章主要介绍了Spring @Transactional注解失效解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚.后来终于找到了原因. 如果你也出现了这种情况,可以从下面开始排查. 一.特性 先来了解一下@Transactional注解事务的特性吧,可以更好排查问题 1.service类标签(一般不建议在接口上)上添加@Transactional,

  • spring中12种@Transactional的失效场景(小结)

    目录 一.失效场景集一:代理不生效 二.失效场景集二:框架或底层不支持的功能 三.失效场景集三:错误使用@Transactional 四.总结 数据库事务是后端开发中不可缺少的一块知识点.Spring为了更好的支撑我们进行数据库操作,在框架中支持了两种事务管理的方式: 编程式事务声明式事务 日常我们进行业务开发时,基本上使用的都是声明式事务,即为使用@Transactional注解的方式. 常规使用时,Spring能帮我们很好的实现数据库的ACID (这里需要注意哦,Spring只是进行了编程上

  • go语言中五种字符串的拼接方式(小结)

    目录 +拼接方式 sprintf函数 Join函数 buffer.Builderbuffer.WriteString函数 buffer.Builder函数 ps:直接使用运算符 主要结论 +拼接方式 这种方式是我在写golang经常用的方式,go语言用+拼接,php使用.拼接,不过由于golang中的字符串是不可变的类型,因此用 + 连接会产生一个新的字符串对效率有影响. func main() { s1 := "hello" s2 := "word" s3 :=

  • 基于Spring中的事务@Transactional细节与易错点、幻读

    目录 为什么要使用事务? 如何使用事务? 事务的传播带来的几种结果 两个特例 事务传播属性propagation 数据库隔离级别 1.未提交读(会有脏读的现象) 2.已提交读 3.可重复读 (有可能覆盖掉其他事务的操作) 4.串行化(没有并发操作) Spring事务隔离级别比数据库事务隔离级别多一个default ACID,事务内的一组操作具有 原子性 .一致性.隔离性.持久性. Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束

  • Redis中5种数据结构的使用场景介绍

    一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String--字符串 Hash--字典 List--列表 Set--集合 Sorted Set--有序集合 下面我们就来简单说明一下它们各自的使用场景: 1. String--字符串 String 数据结构是简单的 key-

  • spring 中事务注解@Transactional与trycatch的使用

    spring事务注解@Transactional与trycatch 在项目中 @service层中 我们会经常在做一些增删改操作的方法上看到 spring 的事务注解 @transaction 已知@transaction 是让spring 帮我们实现事务的控制. 但是在项目中会经常看到 有的方法中 会存在trycatch块包括的方法上注解着@transaction eg: @Override @Transactional public Json addOrder(TOrderAddReq tO

  • 职业生涯中12种最致命的想法

    1.总觉得自己不够好 这种人虽然聪明.有历练,但是一旦被提拔,反而毫无自信,觉得自己不胜任.此外,他没有往上爬的野心,总觉得自己的职位已经太高,或许低一两级可能还比较适合. 这种自我破坏与自我限制的行为,有时候是无意识的.但是,身为企业中.高级主管,这种无意识的行为却会让企业付出很大的代价. 2.非黑即白看世界 这种人眼中的世界非黑即白.他们相信,一切事物都应该像有标准答案的考试一样,客观地评定优劣.他们总是觉得自己在捍卫信念.坚持原则.但是,这些原则,别人可能完全不以为意.结果,这种人总是孤军

  • Java中两种基本的输入方式小结

    目录 两种基本的输入方式 1.使用Scanner类 2.使用System.in.read();方法 输入与输出的使用讲解 1.输入 2.输出 3.输入输出实例 两种基本的输入方式 1.使用Scanner类 需要java.util包 构造Scanner类的对象,附属于标准输入流System.in,之后通过其中的方法获得输入. 常用的方法:nextLine();(字符串),nextInt();(整型数),nextDouble();(双精度型数)等等. 结束时使用close();方法关闭对象. 例子:

  • 我总结的几种@Transactional失效原因说明

    目录 总结几种@Transactional失效原因 非public方法 自调用问题 异常相关问题 抛出非运行时异常 传播机制配置错误 @Transactional事务失效场景类内部调用实测 demo1 demo2 demo3 demo4 总结几种@Transactional失效原因 非public方法 spring事务是通过动态代理的方法来实现的,有两种实现动态代理的方式,jdk动态代理方式是将目标对象放入代理对象内部,通过代理对象来访问目标对象:cglib字节码生成是通过生成目标对象的子类,通

  • Spring中propagation的7种事务配置及说明

    Spring propagation7种事务配置 1.简述 在声明式的事务处理中,要配置一个切面,其中就用到了propagation,表示打算对这些方法怎么使用事务,是用还是不用,其中propagation有七种配置,REQUIRED.SUPPORTS.MANDATORY.REQUIRES_NEW.NOT_SUPPORTED.NEVER.NESTED.默认是REQUIRED. 2.Spring中七种Propagation类的事务属性详解: REQUIRED:支持当前事务,如果当前没有事务,就新建

  • Spring事务@Transactional注解四种不生效案例场景分析

    目录 背景 示例代码 1. 类内部访问 2. 私有方法 3. 异常不匹配 4. 多线程 父线程抛出异常 子线程抛出异常 源码解读 @Transactional 执行机制 private 导致事务不生效原因 异常不匹配原因 背景 在我们工作中,经常会用到 @Transactional 声明事务,不正确的使用姿势会导致注解失效,下面就来分析四种最常见的@Transactional事务不生效的 Case: 类内部访问:A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Tra

随机推荐