1、相关基本知识
1.1事务的传播方式
//如果有事务, 那么加入事务, 没有的话新建一个(默认)
@Transactional(propagation=Propagation.REQUIRED)
//容器不为这个方法开启事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
//不管是否存在事务, 都创建一个新的事务, 原来的挂起, 新的执行完毕, 继续执行老的事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
//必须在一个已有的事务中执行, 否则抛出异常
@Transactional(propagation=Propagation.MANDATORY)
//必须在一个没有的事务中执行, 否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
//如果其他bean调用这个方法, 在其他bean中声明事务, 那就用事务, 如果其他bean没有声明事务, 那就不用事务
@Transactional(propagation=Propagation.SUPPORTS)
1.2事务的隔离级别
// 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// 读取已提交数据(会出现不可重复读和幻读) Oracle默认
@Transactional(isolation = Isolation.READ_COMMITTED)
// 可重复读(会出现幻读) MySQL默认
@Transactional(isolation = Isolation.REPEATABLE_READ)
// 串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
1.3其他属性
- timeout 事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
- readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
- rollbackFor 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
- noRollbackFor 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
2、事务失效场景
2.1未被Spring管理
事务方法所在类并没有被Spring管理,则Spring事务会失效。这种对于Springboot项目其实不会的,你把@Service注释掉,Controller中注入UserService是会报错的。
2.2方法不能被重写
这里的意思是带@Transactional注释的方法必须是可重写的,那我们知道private、static、final修饰的方法均不能被重写。
2.3同一个类中方法相互调用
这里我们在同一个类中方法A调用了方法B,在方法B上使用@Transactional,此时方法B中出现异常,我们是希望他能回滚的。
可惜并没有回滚事务
声明式事务实现原理是面向切面编程,通过cglib创建代理proxy,当我们访问带 @Transactional方法,如果通过spring容器获取bean,实际访问的是代理对象,代理对象已经在带 @Transactional方法前后增加了事务相关的逻辑。而当调用带 @Transactional方法的调用方是同类方法时,调用的是this对象的方法,没有通过spring容器获取bean,就无法访问到代理对象,事务也就没有生效。
我们把@Transactional注解放到方法A上就可以了。此时即便方法B是private修饰的也无所谓
2.4异常被catch了
基于2.3的例子,假如把方法B中的异常catch了却没有抛出异常,事务也是不会回滚的。
解决办法:通常我们会在Springboot项目中自定义一个异常,在catch语句块中抛出自定义异常即可。
2.5rollbackFor使用不当
rollbackFor默认值为UncheckedException,包括了RuntimeException和Error。
在使用@Transactional注解时不指定rollbackFor,Exception及其子类都不会触发回滚。
继承自Runtime Exception或 Error 的是非检查型异常,而继承自 Exception 的则是检查型异常。
2.6数据库引擎不支持事务
比如MySQL数据库选用MyISAM存储引擎,而MyISAM存储引擎本身不支持事务,事务肯定不会生效。
2.7多线程问题
如果你去面试,面试官问你多线程事务如何回滚,你要是回答用@Transactional注解,就可以直接回去了。Spring的事务是通过ThreadLocal来保证线程安全的,事务和当前线程绑定。