https://blog.youkuaiyun.com/minghao0508/article/details/124374637 文章源地址
3.3 事务回滚
@Transactional默认只能回滚RuntimeException和RuntimeException下面的子类抛出的异常,不能回滚Exception异常;如果需要支持回滚Exception异常,需要显示的指明,如@Transactional(rollbackFor = Exception.class);
3.4 失效场景
介绍几种常见的事务实效的场景,这里引用一张图来说明;
3.4.1 打了@Transactional但是事务不生效
(1)@Transactional注解未打在public方法上
Java的访问权限主要有四种:private、default、protected、public;如果事务方法定义了错误的访问权限(非public方法),会导致事务失效;
(2)目标方法用final修饰
某个方法不想被子类重写,可以将该方法定义成final的;如果将事务方法定义成final,会导致事务失效;
原因:Spring事务基于Spring AOP,通过JDK动态代理或者CGlib代理,在代理类中实现的事务功能;但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法;同样,static修饰的方法,同样无法通过动态代理,变成事务方法;
(3)同一个类中的方法直接内部调用
原因:方法被事务管理是因为Apring AOP为其生成代理了对象,但是直接this调用同类方法,调用的是目标类对象的方法,而非代理类方法,因此,在同类中的方法直接内部调用,会导致事务失效;
如果有些场景,确实想在同一个类的某个方法中,调用当前类的另外一个事务方法,该怎么办呢?
方法1:新写一个Service,把事务方法挪过去,在当前类注入新的Service
方法2:在当前Service注入自己;可能有些人可能会有这样的疑问:这种做法会不会出现循环依赖问题?不会,Spring的Bean初始化流程中使用三级缓存解决循环依赖问题,可参考我的文章《Spring——循环依赖&三级缓存【建议收藏】》;
方法3:在当前Service类中使用AopContext.currentProxy()获取当前类的代理对象,相比方法2更加直观;代码示例如下:
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
(4)事务方法所在的类未被Spring管理
使用Spring事务的前提是:对象要被Spring IOC容器管理,需要创建bean实例;打了注解,但是忘了在当前类加@Service注解,导致事务不生效,也是小白常见的编码错误;
(5)多线程调用
如果两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务;如果看过Spring事务源码,能会知道Spring的事务是通过数据库连接Connection来实现的;当前线程中保存了一个map,key是数据源,value是数据库连接;
我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚;如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务;
(6)存储引擎不支持事务
如MYSQL的myisam存储引擎不支持事务,有些老项目中,可能还在用它;在开发的过程中,如果发现某张表的事务一直都没有生效,可以检查下那张表的存储引擎,看是否支持事务;
(7)未开启事务
这个原因极其容易被忽略;
如果你使用的是Springboot项目,那么你很幸运,因为Springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务,只需要配置数据源spring.datasource相关参数即可;
但如果你使用的还是传统的老Spring项目,则需要在applicationContext.xml文件中,手动配置事务相关参数;
3.4.2 事务未回滚
(1)使用了错误的传播特性,如新开启了一个事物,可能导致新事物和原事务不会一起回滚;
(2)自己吞了异常,忘记抛出了;或者抛出了非运行时异常,但又没有配置到注解上;因为Spring事务默认情况下只会回滚RuntimeException(运行时异常)和Error(错误);
3.5 使用建议
-
要知道@Transactional注解里面每个属性的含义,@Transactional注解属性就是来控制事务属性的,通过这些属性来生成事务;
-
要明确我们添加的@Transactional注解会不会起作用;如@Transactional注解在外部调用的函数上才有效果,直接内部调用无效;
-
显示的指定rollbackFor注解属性,即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数,因为如果使用默认值,一旦程序抛出了非运行时的其他Exception,事务不会回滚,这会出现很大的bug;
-
要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,还是沿用之前的事务;稍不注意就会抛UnexpectedRollbackException异常;
-
不要滥用事务,避免大事务,事务会影响数据库的读写性能,非必要场景不建议使用;适当的对方法里面的实务操作拆分执行;