spring事务失效
事务失效的几种类型
- 数据库引擎不支持事务。
- 没有被 Spring 管理。
- 方法不是 public 的。
- 自身调用问题。
- 数据源没有配置事务管理器。
- 不支持事务。
- 异常被吃了。
- 异常类型错误。
事务失效类型:
数据库引擎不支持事务
这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
根据 MySQL 的官方文档:https://dev.mysql.com/doc/refman/8.0/en/storage-engine-setting.html
没有被 Spring 管理
Spring 中的事务基于 AOP 实现,则事务类必须被 Spring 管理,进行代理,才能支持事务。
方法不是 public 的
@Transaction 只对方法名为 public 的才会生效,其他的不生效。private,static,final 方法不能添加事务,添加了也不会生效。
自身调用问题
- service 类中调用本类自己的方法,由于没有经过 spring 代理,事务不会生效。
- 一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。这种情况,可以内部维护一个自己注入的 bean,使用这个属性来调用。或者利用 AOP 上下文来获取代理对象,利用代理对象调用。
- 有事务的调用有事务的被调用的不能新开启事务。被调用的开启的新事务不会生效。
- 有事务的调用无事务的会生效。
- 无事务的调用无事务的,这种情况就会没有事务。
事务是否生效主要看是否通过代理,没有通过代理就不会生效。
数据源没有配置事务管理器
数据源必须开启事务管理器:
- @EnableTransactionManagement // 启注解事务管理,等同于 xml 配置方式的 <tx:annotation-driven />
- @EnableTransactionManagement 在 springboot1.4 以后可以不写。框架在初始化的时候已经默认给我们注入了两个事务管理器的 Bean(JDBC 的 DataSourceTransactionManager 和 JPA 的 JpaTransactionManager ),其实这就包含了我们最常用的 Mybatis 和 Hibeanate 了。当然如果不是 AutoConfig 的而是自己自定义的,请使用该注解开启事务
不支持事务
Propagation 设置错误,Propagation 用于配置事务的传播行为。Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起。
异常被吃了
异常被捕获了,然后不进行抛出,那么无法认为有异常,事务就不会回滚。在 service 中不应该进行事务的捕获,而进行抛出,在 controller 中进行异常捕获,这样既支持事务也捕获了异常。
异常类型错误
Spring 的事务管理默认是针对 Error 异常和 RuntimeException 异常以及其子类进行事务回滚。对 runtimeException 并不需要抛出,error 需要抛出异常,并进行捕获。如果想对其他异常进行支持,则需要配置:@Transactional(rollbackFor = Exception.class)
业务和事务必须要在同一个线程中
不在同一个线程,则事务影响不到。
事务的隔离级别
事务会引起的问题:
脏读:
当 A 事务对数据进行修改,但是这种修改还没有提交到数据库中,B 事务同时在访问这个数据,由于没有隔离,B 获取的数据有可能被 A 事务回滚,这就导致了数据不一致的问题。
丢失修改:
当 A 事务访问数据 100,并且修改为 100-1=99,同时 B 事务读取数据也是 100,修改数据 100-1=99,最终两个事务的修改结果为 99,但是实际是 98。事务 A 修改的数据被丢失了。
不可重复读:
指 A 事务在读取数据 X=100 的时候,B 事务把数据 X=100 修改为 X=200,这个时候 A 事务第二次读取数据 X 的时候,发现 X=200 了,导致了在整个 A 事务期间,两次读取数据 X 不一致了,这就是不可重复读。
幻读:
幻读和不可重复读类似。幻读表现在,当 A 事务读取表数据时候,只有 3 条数据,这个时候 B 事务插入了 2 条数据,当 A 事务再次读取的时候,发现有 5 条记录了,平白无故多了 2 条记录,就像幻觉一样。
不可重复读的重点是修改: 同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了,重点在更新操作。
幻读的重点在于新增或者删除:同样的条件 , 第 1 次和第 2 次读出来的记录数不一样,重点在增删操作。
Spring 定义的隔离级别:
TransactionDefinition.ISOLATION_DEFAULT: 数据库的默认隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,未提交的不可读取,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次重复读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL 中通过 MVCC 解决了该隔离级别下出现幻读的可能。
TransactionDefinition.ISOLATION_SERIALIZABLE: 串行化隔离级别,该级别可以防止脏读、不可重复读以及幻读,但是串行化会影响性能。
Propagation,传播行为:
指多个方法调用时,事务对多个方法之间传播的影响。
PROPAGATION_REQUIRED: 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY: 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。