前言
声明式事务是 Spring 功能中最爽之一, 可是有些时候, 我们在使用声明式事务并未生效, 这是为什么呢?
今天陈某带大家来聊一聊声明事务的几种失效场景. 本文将会从以下两个方面来说一下事务为什么会失效?
@Transactional 介绍
@Transactional 失效场景
@Transactional 介绍
@Transactional 是声明式事务的注解, 可以被标记在类上, 接口, 方法上.
该注解中有很多值得深入了解的几种属性, 我们来看一下.
transactionManager
指定事务管理器, 值为 bean 的名称, 这个主要用于多事务管理器情况下指定. 比如多数据源配置的情况下.
isolation
事务的隔离级别, 默认是 Isolation.DEFAULT.
几种值的含义如下:
Isolation.DEFAULT: 事务默认的隔离级别, 使用数据库默认的隔离级别.
Isolation.READ_UNCOMMITTED
: 这是事务最低的隔离级别, 它充许别外一个事务可以看到这个事务未提交的数据. 这种隔离级别会产生脏读, 不可重复读和幻读.
Isolation.READ_COMMITTED
: 保证一个事务修改的数据提交后才能被另外一个事务读取. 另外一个事务不能读取该事务未提交的数据. 这种事务隔离级别可以避免脏读出现, 但是可能会出现不可重复读和幻读.
Isolation.REPEATABLE_READ
: 这种事务隔离级别可以防止脏读, 不可重复读. 但是可能出现幻读.
Isolation.SERIALIZABLE
: 这是花费最高代价但是最可靠的事务隔离级别. 事务被处理为顺序执行. 除了防止脏读, 不可重复读外, 还避免了幻读.
propagation
代表事务的传播行为, 默认值为
- Propagation.REQUIRED
- .
- Propagation.REQUIRED
: 如果存在一个事务, 则支持当前事务. 如果没有事务则开启一个新的事务. 比如 A 方法内部调用了 B 方法, 此时 B 方法将会使用 A 方法的事务.
Propagation.MANDATORY
: 支持当前事务, 如果当前没有事务, 就抛出异常.
Propagation.NEVER: 以非事务方式执行, 如果当前存在事务, 则抛出异常.
Propagation.NOT_SUPPORTED
: 以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起.
Propagation.REQUIRES_NEW
: 新建事务, 如果当前存在事务, 把当前事务挂起. 比如 A 方法使用默认的事务传播属性, B 方法使用 REQUIRES_NEW, 此时 A 方法在内部调用 B 方法, 一旦 A 方法出现异常, A 方法中的事务回滚了, 但是 B 方法并没有回滚, 因为 A 和 B 方法使用的不是同一个事务, B 方法新建了一个事务.
Propagation.NESTED: 支持当前事务, 新增 Savepoint 点, 也就是在进入子事务之前, 父事务建立一个回滚点, 与当前事务同步提交或回滚. 子事务是父事务的一部分, 在父事务还未提交时, 子事务一定没有提交. 嵌套事务一个非常重要的概念就是内层事务依赖于外层事务. 外层事务失败时, 会回滚内层事务所做的动作. 而内层事务操作失败并不会引起外层事务的回滚.
timeout
事务的超时时间, 单位为秒.
readOnly
该属性用于设置当前事务是否为只读事务, 设置为 true 表示只读, false 则表示可读写, 默认值为 false. 如果一个事务只涉及到只读, 可以设置为 true.
rollbackFor 属性
用于指定能够触发事务回滚的异常类型, 可以指定多个异常类型.
默认是在 RuntimeException 和 Error 上回滚.
noRollbackFor
抛出指定的异常类型, 不回滚事务, 也可以指定多个异常类型.
@Transactional 失效场景
声明式事务失效的场景有很多, 陈某这里只是罗列一下几种常见的场景.
底层数据库引擎不支持事务
如果数据库引擎不支持事务, 则 Spring 自然无法支持事务.
在非 public 修饰的方法使用
@Transactional 注解使用的是 AOP, 在使用动态代理的时候只能针对 public 方法进行代理, 源码依据在
AbstractFallbackTransactionAttributeSource
类中的
computeTransactionAttribute
方法中, 如下:
- protected TransactionAttribute computeTransactionAttribute(Method method,
- Class<?> targetClass) {
- // Don't allow no-public methods as required.
- if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
- return null;
- }
此处如果不是标注在 public 修饰的方法上并不会抛出异常, 但是会导致事务失效.
异常被 "踹死了"
这种情况小白是最容易犯错的, 在整个事务的方法中使用 try-catch, 导致异常无法抛出, 自然会导致事务失效. 伪代码如下:
- @Transactional
- public void method(){
- try{
- // 插入一条数据
- // 更改一条数据
- }catch(Exception ex){
- return;
- }
- }
方法中调用同类的方法
简单的说就是一个类中的 A 方法 (未标注声明式事务) 在内部调用了 B 方法(标注了声明式事务), 这样会导致 B 方法中的事务失效.
代码如下:
- public class Test{
- public void A(){
- // 插入一条数据
- // 调用 B 方法
- B();
- }
- @Transactional
- public void B(){
- // 插入数据
- }
- }
为什么会失效呢?: 其实原因很简单, Spring 在扫描 Bean 的时候会自动为标注了 @Transactional 注解的类生成一个代理类(proxy), 当有注解的方法被调用的时候, 实际上是代理类调用的, 代理类在调用之前会开启事务, 执行事务的操作, 但是同类中的方法互相调用, 相当于 this.B(), 此时的 B 方法并非是代理类调用, 而是直接通过原有的 Bean 直接调用, 所以注解会失效.
如何解决呢?: 这就涉及到注解失效的原因了, 后续文章会介绍到, 这里不过多介绍了.
rollbackFor 属性设置错误
很容易理解, 指定异常触发回滚, 一旦设置错误, 导致一些异常不能触发回滚, 此时的声明式事务不就失效了吗.
noRollbackFor 属性设置错误
这个和 rollbackFor 属性设置错误类似, 一旦设置错误, 也会导致异常不能触发回滚, 此时的声明式事务会失效.
propagation 属性设置错误
事务的传播属性在上面已经介绍了, 默认的事务传播属性是
Propagation.REQUIRED
, 但是一旦配置了错误的传播属性, 也是会导致事务失效, 如下三种配置将会导致事务失效:
- Propagation.SUPPORTS
- Propagation.NOT_SUPPORTED
- Propagation.NEVER
原始 SSM 项目, 重复扫描导致事务失效
在原始的 SSM 项目中都配置了
context:component-scan
并且同时扫描了 service 层, 此时事务将会失效.
按照 Spring 配置文件的加载顺序来说, 会先加载 Springmvc 的配置文件, 如果在加载 Springmvc 配置文件的时候把 service 也加载了, 但是此时事务还没加载, 将会导致事务无法成功生效.
解决方法很简单, 把扫描 service 层的配置设置在 Spring 配置文件或者其他配置文件中即可.
总结
事务失效的原因很多, 但是千万不要做到一知半解, 只有深入理解了, 才能在面试过程中对答如流.
今天的文章就到此结束了, 如果觉得陈某写得不错, 有所收获的, 关注在看来一波, 你们的鼓励, 将会是我写作的动力, 谢谢支持!!!
来源: https://www.cnblogs.com/Chenjiabing/p/12730907.html