第一次离职面试时,面试官开放式聊天,问到如果有一个新人,我怎么给他讲解事务的注意事项,从而让他少踩一些坑,当时没转过来思路,觉得配置对了就好了嘛,所以就聊了一下事务的传播行为和隔离级别,后来新的工作中同事遇到了@Async@Transactional无效的情况,借由这个想起了上述经历,分享一下
首先我们来了解@Transactional这个注解:
@Transactional注解事务的特性:
1 .添加位置
1)接口实现类或接口实现方法上,而不是接口类中。因为启动事务会增加线程开销,所以基本都写在接口实现方法上
2)访问权限:
public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
3)只读事务
@Transactional(propagation=Propagation.NOT_SUPPORTED.readOnly=true)只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。
4).异常
Spirng会对unchecked异常进行事务回滚;如果是checked异常则不回滚。
什么是unchecked异常,什么是checked异常?
java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常成为unchecked异常,其他继承自java.lang.Exception的异常统称为checked Exception,如IOException、TimeOutException等
通俗点讲就是,编译器能够检测到的是chedked异常,检测不到的是unchecked异常
几个失效场景(前三个容易采坑)
1:同一个接口非事务方法中调用了一个事务方法
之前有个同事在同一个接口的一个非事务方法中调用了一个事务方法,即接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效
原因:spring 在扫描bean的时候会扫描方法上是否包含@Async,@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以我们看到的现象就是@Async,@Transactional注解无效。
2:接口中异常(运行时异常)在catch块里面被捕获而没有被抛出。
很多人为了避免一个批量任务抛出异常导致后面的任务被中断从而不执行喜欢在catch里面记录log同时把异常“吃掉”。默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务, 也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚), 而抛出 checked 异常则不会导致事务回滚 。
如果想让checked异常也回滚,在注解上面写明异常类型即可:
@Transactional(rollbackFor=Exception.class)
3:事务中出现多线程也不生效
多线程不属于spring,所以默认不支持事务管理,即在一个事务方法中,开启了多线程的方法出现异常是不会回滚的
因为事务信息是存储在threadLocal变量中,而此变量是单个线程独有,不会被其他线程可见
解决办法,在多线程方法中调用事务方法即可。
4.是否开启了对注解的解析:
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5.spring是否扫描到你使用注解事务的这个类所在的包:
<context:component-scan base-package="com.xxx.xxx" ></context:component-scan>
6.数据库引擎要支持事务
如果是Mysql,注意表要使用支持事务的引擎,比如innaodb,如果是myisam,事务是不起作用的