@Transactional
注解是 Spring 开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。
但使用 @Transactional
注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。
一、@Transactional 注解使用
@Transactional 可以作用在接口、类、类方法
作用于类:当把
@Transactional
注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。作用于方法:当类配置了
@Transactional
,方法也配置了@Transactional
,方法的事务会覆盖类的事务配置信息。作用于接口:不推荐这种使用方法,因为一旦标注在 Interface 上并且配置了 Spring AOP 使用 CGLib 动态代理,将会导致
@Transactional
注解失效
@Transactional 注解的属性
propagation 属性
propagation
代表事务的传播行为,默认值为 Propagation.REQUIRED
,其他的属性信息如下:
Propagation.REQUIRED
:若当前存在事务,则加入该事务,若不存在事务,则创建一个新的事务。Propagation.SUPPORTS
:若当前存在事务,则加入该事务;若不存在事务,则以非事务的方式继续运行。Propagation.MANDATORY
:若当前存在事务,则加入该事务;若不存在事务,则抛出异常。Propagation.REQUIRES_NEW
:重新创建一个新的事务,若当前存在事务,暂停当前的事务。Propagation.NOT_SUPPORTED
:以非事务的方式运行,若当前存在事务,暂停当前的事务。Propagation.NEVER
:以非事务的方式运行,若当前存在事务,则抛出异常。
isolation 属性
isolation
:事务的隔离级别,默认值为 Isolation.DEFAULT
。
- Isolation.DEFAULT:使用底层数据库默认的隔离级别。
- Isolation.READ_UNCOMMITTED
- Isolation.READ_COMMITTED
- Isolation.REPEATABLE_READ
- Isolation.SERIALIZABLE
timeout 属性
timeout
:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 属性
readOnly
:指定事务是否为只读事务,默认值为 false。
若一次执行多条查询语句,且多条查询 SQL 需保证整体的读一致性时,设置为 true;否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态。
rollbackFor 属性
rollbackFor
:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor 属性
noRollbackFor
:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
二、@Transactional 失效场景
Spring 中对注解的解析都是基于代理的,如果目标方法无法被 Spring 代理到,那么它将无法被 Spring 进行事务管理。
1、@Transactional 应用在非 public 修饰的方法上
如果 @Transactional
注解应用在非public
修饰的方法上,Transactional将会失效。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取 @Transactional
的属性配置信息。
注意:protected
、private
修饰的方法上使用 @Transactional
注解,虽然事务无效,但不会有任何报错。
2、被final、static关键字修饰的类或方法
CGLib 是通过生成目标类子类的方式生成代理类的,被 final、static 修饰后,无法继承父类与父类的方法。
3、@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
PROPAGATION_SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
4、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor
可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查unchecked
异常(继承自 RuntimeException
的异常)或者 Error
才回滚事务;其他异常不会触发回滚事务。
如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor
属性。
5、同一个类中方法调用,导致 @Transactional 失效
在同一个类中有 A、B 两个方法,A 方法调用 B 方法。若 A 未声明注解事务,B 无论是否声明注解事务,是否为 public,方法 B 的事务是不会起作用的。
那为啥会出现这种情况?其实这还是由于使用 Spring AOP
代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由 Spring
生成的代理对象来管理。
6、异常被 catch 导致 @Transactional 失效
7、数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb
引擎。一旦数据库引擎切换成不支持事务的myisam
,那事务就从根本上失效了。