在日常工作中经常会用到这个注解,但只会简单的使用,知其然而不知其所以然。 因此写这篇文章对
@Transactional注解进行总结。
1、spring 事务管理
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理即使用代码手动的进行事务的提交和回滚等操作。
声明式事务管理是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
@Transactional注解就是一种声明式事务管理的常用实现方式。
2、@Transactional注解属性
Propagation
propagation 代表事务的传播行为,即多个嵌套事务的情形下,如何管理事务。共有以下6种传播行为:
Propagation.REQUIRED:是传播行为的默认值,如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。*(使用@Transactional(propagation = Propagation.SUPPORTS)和不加@Transactional 有什么区别?)Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。Propagation.REQUIRES_NEW: 重新创建一个新的事务,如果当前存在事务,就将这个事务挂起,直到新的事务提交。(Spring事务传播机制Propagation.REQUIRES_NEW详解及测试)Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。Propagation.NESTED:和 Propagation.REQUIRED 效果一样。
isolation
事务的隔离界别,是指若干个并发的事务之间的隔离程度,包括以下:
Isolation.DEFAULT:默认值,表示使用数据库默认隔离级别。(mysql repeatable;sqlserver readcommit)Isolation.READ_UNCOMMITTED: 读未提交,表示事务可以读取其他事务修改但还未提交的数据,该隔离级别可能会造成脏读。Isolation.READ_COMMITTED:读已提交,表示一个事务只能读取另一个事务已经提交的数据,可以防止脏读,但可能会出现幻读和不可重复读。Isolation.REPEATABLE_READ:可重复读,该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该隔离级别可以防止脏读和不可重复读,但可能会出现幻读。Isolation.SERIALIZABLE:最高隔离界别,所有事务顺序执行,可以防止脏读、不可重复读、幻读。但性能也最差。 | 隔离级别 | 脏读 | 不可重复读 | 幻影读 | | :--------------: | :--: | :--------: | :----: | | READ-UNCOMMITTED | √ | √ | √ | | READ-COMMITTED | × | √ | √ | | REPEATABLE-READ | × | × | √ | | SERIALIZABLE | × | × | × |
这里只对
isolation属性的几种隔离级别进行简单介绍,想了解隔离级别详细内容,请原理可以其他文章。
timeout
事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
默认情况下,只对非受检异常(
RunTimeException、Error等))进行回滚,受检异常(包括SQLException)不进行回滚。
noRollbackFor
与rollbackFor属性相反,制定不触发事务回滚的异常类型。
附Transactional注解源码(spring 5.2.8):
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
2、使用注意事项
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,几种类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然@Transactional 使用很简便,但日常使用中,还是需要注意一些细节,不然会导致@Transactional 失效,这里总结常见几种@Transactional 失效的情形:
2.1 常见的几种失效情形
@Transactional 应用在非public 修饰的方法上
原因:spring通过 AOP代理对事务进行管理时,会检查方法是否为public修饰符,不是 public则不会获取@Transactional 的属性配置信息。
同一个类中, 一个nan-transactional的方法去调用transactional的方法, 事务会失效
原因:Spring采用动态代理(AOP)实现对事务的管理,在初始化扫描时,会为带有Transactional注解的类动态生成一个代理对象,当带有Transactional注解的方法被调用时,实际是通过代理类,但只有在代理对象之间进行调用时,可以触发切面逻辑。
而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。
ps: 同一个class中,调用方法带有
Transactional注解,被调用方法不带用Transactional注解,事务是可以生效的
public void doTheThing() {
// ... actuallyDoTheThing(); // Noncompliant
}
@Transactional
public void actuallyDoTheThing() {
// ...
}
解决办法:
- 1、同一个类中,调用方法带上
@Transactional注解 - 2、通过代理对象调用
try catch 块中,异常没有被再次抛出
常见的一种失效情景,异常被catch了,没有抛出到切面层。
@Transactional
public void doSomething(){
try{
//
}catch(exception){
// 没有再次抛出异常
}
}
rollbackFor 属性设置错误
@Transactional注解默认只对非受检异常进行回滚,SqlException等异常属于受检异常,如果不在 rollbackFor属性中指定,当抛出受检异常时,事务也不会回滚。