73_面试题:有哪些场景会导致Spring事务失效?
开篇
大家好,到这里为止,Spring事务这块的内容我们就全部讲完了,相信大家对@Transactional注解的工作原理有了更深的认识。
那么大家认为只要在方法上加了@Transactional注解,那么事务就一定会生效吗?
如果你真这样认为,那么就 Too young,too naive了。
这节作为Spring事务的最后一讲,就来给大家,介绍几种常见的,就算加了@Transactional注解,事务依然会失效的几种场景。
首先说明的就是,事务失效的场景有非常非常多,这里主要介绍几种常见的事务失效场景。
ok,那这里先来介绍一下本节的主要内容,首先我们会简单重现下面试场景,作为我们的前菜,帮助我们进入状态,接下来就会介绍几种常见的事务失效场景,主要如下:
- 没有被Spring管理
- 事务方法不是public修饰
- 方法内部调用事务方法
- 异常被偷吃掉
- rollbackFor属性配置错误
- 数据库引擎不支持事务
面试场景再现
ok,在介绍事务失效场景之前,我们还是简单再现一下面试场景,假设你正在参加一场面试,然后聊到Spring事务这块的时候,你给面试官喷各种AOP的原理和@Transactional注解的原理,给面试官整得服服帖帖的。
然后面试官突然问道:“小伙子,很不错啊,看来你对Spring事务这块掌握的非常扎实,那你在工作中,遇到过即使加了@Transactional注解,但是事务依然失效的场景吗?”
由于这个问题呢,你之前没有深入思考过,所以被突然问道的时候,心里慌的一逼,然后你支支吾吾的回答:“呃,在工作中基本没有遇到过事务失效的场景”
此时面试官接着问:“那你思考一下,@Transactional可能会在哪些场景下失效呢?可以临时思考一下”
此时你心里更慌了,心里想:“我擦,我都说了基本没遇到过事务失效的场景,为啥你还接着问呢?!!”
不过,毕竟在面试蛮,还是要回答的,此时你进入了思考模式,突然灵光一闪,回答:“在使用@Transactional时,如果我们使用try catch将异常吞掉的话,就会导致事务失效,因为Spring就感知不到异常了”
面试官:“嗯,不错,还能想到其他的事务失效的场景吗?”
然后你尴尬的回答:“呃,我就想到这一种事务失效的场景。。。”
好了,刚才我们简单再现了一下面试场景,大家来感受下,本来面试官已经被你深厚的源码功力给折服了,但是可能因为这一个简单的问题,影响到面试官对你的打分。
为了避免大家在面试时,遇到这样尴尬的情况,那么今天呢,我们会介绍几种常见的事务失效场景,掌握了这些事务失效的场景后,大家再也不怕在面试中,被问事务失效了。
没有被Spring管理
首先我们来看最简单的一种事务失效场景,如下图:
如果我们像上图一样,将OrderServiceImpl类中的@Service注解给注释掉的话,那么这个OrderServiceImpl就不会交给Spring管理,那么此时就会导致事务失效!
那么为什么会失效呢?
我们知道,@Transactional是基于AOP这一套来玩儿的,说白了就是在从IOC容器获取bean的时候,为目标类创建动态代理,从而支持事务的,对应的源码如下:
正是由于@Transactional是需要基于AOP代理来实现事务管理的,那如果此时我们把@Service注解干掉的话,那么上边的OrderServiceImpl类,就不会交给Spring来管理,说白了就不会为OrderServiceImpl创建代理对象了,那这个时候事务当然就会失效啦。
事务方法不是public
好,接着我们再来看一个事务失效的场景,那就是方法不是public修饰的,我们看下边的代码:
大家可以看到,上边的submitOrder()方法现在是由protected来修饰的,那么这个时候就会导致事务生效。
而这种情况下,事务失效的原因,其实我们之前也分析过,对应的源码如下图:
通过上图,大家可以看到,在获取事务属性时,如果目标方法不是由public修饰的,那么这里就会直接返回null,表示目标方法所在的类不需要被代理,从而导致目标类匹配不到BeanFactoryTransactionAttributeSourceAdvisor这个Advisor。
而一旦目标类匹配不到BeanFactoryTransactionAttributeSourceAdvisor增强,那么就不会为目标类创建AOP代理了,对应的源码如下:
所以如果方法不是public修饰的,最终会导致事务失效,大家工作中使用时,需要注意一下。
方法内部调用,没有通过AOP代理
ok,我们接着来看下一个事务失效的场景,我们看下边的代码:
这个时候呢,submitOrder()方法上面没有加 @Transactional 注解,但是它内部调用加了@Transactional注解的 realSubmitOrder() 方法。
那么此时在submitOrder()方法内部来调用realSubmitOrder()的场景中,realSubmitOrder() 方法上的事务生效吗?
其实答案是:不生效!
因为在这个场景中,发生了自身内部调用,它是直接调用了该类自己内部的方法,而不是通过Spring代理类来进行调用的。
我们来简单梳理一下,既然@Transactional是基于AOP代理来实现的,那么在调用事务方法时,当然需要通过AOP代理对象来调用啦,这样才能将事务代理的逻辑添加上去,这点大家没疑问吧?
ok,这点达成共识后,下边就好说了。
那么如果我们在类内部,直接调用加了@Transactional的内部事务方法,那这个时候大家来思考一下,到底是谁在调用的这个内部事务方法?
其实简单思考一下,我们就知道,此时调用内部事务方法的其实是this对象,也就是说此时是由this对象来完成调用的,而不是通过AOP代理来调用的,那你调用都没有通过AOP代理,那不是活该你事务不生效吗?就这么简单
异常被偷偷吃掉了
好,我们接着来看,下边这个场景就非常简单了,其实就是异常被偷吃的场景,如下图:
通过上图,大家可以看到,这里呢,做了try catch处理,说白了就是把异常给偷偷吃掉了,那这个时候就非常尴尬了,因为这个时候由于异常被偷吃了,所以Spring是感知不到报错的,人家Spring还认为你是正常执行完毕了呢,不仅不会回滚事务,还会进一步的提交事务!
至于原因呢,其实很简单,我们来看一段之前分析过的源码,如下图:
可以看到,其实上边的代码呢,就是控制事务的核心逻辑,大家可以看到,人家Spring这里做了try catch处理,说白了就是,人家只有在捕获到异常时,才会进行事务回滚。
所以如果你想让Spring帮你回滚事务,那么就在捕获到异常后,自己在做完一些日志打印和监控打点后,你要把异常给人家Spring抛出来才行,要不Spring一脸懵逼,还以为你执行成功了呢!
rollbackFor属性配置错误
ok,刚才我们刚讲过,如果我们要做try catch处理的话,在catch语句块中,做完一些常规处理后,比如异常日志记录和监控打点,那么一定要记得抛出来异常,这样Spring才能感知到发生异常了,这样Spring才能帮你回滚事务。
那这里的rollbackFor属性配置错误,是什么意思呢?
其实啊,在默认情况下,Spring只会对RuntimeException和Error回滚事务,而对于其他异常,是不会触发回滚事务的,比如通过throw new Exception("我是一个异常")来抛出一个Exception异常,此时Spring是不会回滚事务的!
那这个时候就很尴尬了,有的时候,我们就需要抛出Exception异常或者我们自己的业务异常,那这个时候怎么办呢?
其实这个时候,我们可以通过配置事务属性来达到目的,比如rollbackFor属性,这个rollbackFor属性很容易理解,说白了就是指定触发回滚事务的异常,比如这里我们可以将rollbackFor指定为Exception异常或者我们自己的业务异常,这样事务就生效了。
这里需要注意的就是,rollbackFor属性一旦设置错误的话,就会导致一些异常,不能触发回滚,从而导致事务失效,所以大家配置rollbackFor属性时一定要细心点。
其实除了这个rollbackFor属性,还有其他的属性也会导致事务失效,比如noRollbackFor属性和propagation属性,这里我们就不展开细讲了。
我们这里主要是做一个抛砖引玉的作用,这样当大家遇到事务失效的时候,知道大概思考方向就可以了。
数据库引擎不支持事务
当然除了Spring内部的一些原因导致事务失效外,其实也有可能是依赖的外部资源不支持事务导致的,比如我们使用的数据库引擎,不过这种情况出现的概率太低了,但是大家要了解一下。
我们知道,对于大家常用的mysql来说,常用的引擎,无非就是支持事务的innodb引擎和不支持事务的myisam引擎了,那如果我们使用了不支持事务的myisam引擎,那么此时事务从根本上就失效了,Spring当然也就无法支持事务了。
总结
好了,今天的知识点,我们就讲到这里了,我们来总结一下吧。
今天呢,作为Spring事务章节的最后一讲,我们简单聊了下事务失效的几种场景,大家需要知道的是,事务失效的场景还是有很多的,我们这里就抛砖引玉,介绍了几种常见的事务失效场景。
其实啊,大家可以发现,事务失效的场景,绝大部分的原因,要不就是不满足人家Spring的要求,导致最终没有生成AOP代理;要不就是生成了AOP代理了,但是又没有通过AOP代理来调用事务方法;要不就是调用了事务方法,但是自己偷吃了异常。。。
可以发现,这些事务失效的场景,其实涵盖了Spring事务代理的创建和执行流程,所以如果大家对AOP和Spring事务源码都非常熟悉的话,那么上边这些事务失效的场景对你来说so easy,因此掌握好Spring核心源码是非常有必要的。
最后希望大家,在学习本专栏的同时,可以自己调试一遍Spring的核心源码!
ok,到这里,我们整个Spring核心源码专栏就结束了,我们江湖再见!
结于2023/3/24