spring事务(@Transactional)失效的情景及处理
@Transactional注解失效的场景
@Transactional依赖于spring对bean的调用,因为@Transactional注解本质上是一个AOP,事务的回滚,开启,提交等相当于是通知,而spring事务管理相当于是切面,被注解的业务逻辑方法是切入点(这里起始不算是严格意义的AOP,但由于注解是声明式的标记而非高侵入式的代码,所以也可以算是AOP)
Spring事务时依赖于数据库事务的,在Spring事务是一个拦截器,在定义的事务方法开始前会向数据库申请一个事务,然后执行事务方法,如果出现问题就向数据库申请回滚,如果正常执行完就申请提交
在某些情况下@Transactional注解会起不到应有的作用,具体分为四大类
代理机制未生效
自调用
由于@Transactional注解本质上是一个AOP,所以他生效是需要依赖于spring的事务管理的,所以如果一个方法调用本类的事务方法(被加了@Transactional注解),那么这个注解是无法生效的,因为直接调用本类的方法是先于spring的,也就相当于跳过了spring的事务管理,那么这个AOP就没有生效.
例如:
public class Example01 {public void tryA(){tryB();}@Transactionalpublic void tryB(){Example02 example02=new Example02();System.out.println("引用Example02类里的方法C");example02.tryC();System.out.println("引用Example02类里的方法D");example02.tryD();}
}
在A方法中引用本类的方法B,其中B方法中引用方法C和D且定义为事务,但由于A中直接引用本类方法B所以在A中B方法的事务性不生效,比如执行A方法,在执行过程中如果CD方法之间发生了异常,方法不会触发回滚,即已经执行的C方法不会回滚撤回,也就是@Transactional注解失效.
解决方法:
在本类定义为一个bean,在本类中自动装配一个本类bean,用这个bean来往A方法中引入B方法
@Service
public class Example01 {@Autowiredprivate Example01 example01;public void tryA(){example01.tryB();}@Transactionalpublic void tryB(){Example02 example02=new Example02();System.out.println("引用Example02类里的方法C");example02.tryC();System.out.println("引用Example02类里的方法D");example02.tryD();}
}
用自动装配的本类bean来调用本类方法是通过spring对方法进行调用所以事务可以正常进行
非Spring管理的类
事务是一个AOP,依赖于spring,如果没有注解本类让spring管理这个类注解就无法生效
非public方法
spring的AOP代理在处理事务的时候是默认跳过非public方法的,所以如果不是public方法就无法被spring代理,代理机制就无法生效,事务注解无法生效
异常处理不当
@Transactional注解只会对未被捕获的非受检异常(RuntimeException和Error)生效,触发回滚,如果方法中捕获了异常且未重新抛出,事务不会回滚
关于异常处理不当到底是否算作@Transactional注解失效的问题其实在我看来有待商榷,目前大多认为@Transactional失效的场景包括异常处理不当这一项,但我个人的理解中@Transactional注解在正常运行
首先理解受检异常和非受检异常两个概念,受检异常和非受检异常
受检异常就是可以预料有可能会出问题的地方,这部分异常是在出现之前可以想到的,通常IDE会强制开发人员处理(捕获处理或抛出)这部分异常,这部分异常既然已经被处理就不会影响程序的正常执行,例如b方法中有c方法和d方法,c和d之间有一个受检异常e,而e既然已经被处理,那么就是说即使没有开启事务,当e异常发生的时候,c和d仍然还是正常执行的,而将这种情况判定为注解失效主要是根据功能来分的,即事务进行过程中发生异常但未触发回滚,因此判定未注解失效,但本质上这是一个正常执行的程序
非受检异常就是无法预料到的异常,比如1/0这种,运行过程会出现异常,但提前无法预料(无法预料是指程序无法预料,程序员根据自己的经验做出的异常预料和处理跟这个无关),IDE不会对这部分异常强制开发人员处理,而这部分异常分为两种,程序员预料到了并进行了处理或程序员未预料到未捕获,若程序员预料到并进行了处理,那事务注解就不会生效,这原理其实和受检异常类似,而如果程序员未预料到未捕获,那这个异常相当于未曾经过任何处理,会导致程序出错无法继续执行,这时候事务注解就会生效触发回滚
总的来说异常总共有三类,已经被处理(包括捕获处理和抛出)的受检异常,已经被处理的非受检异常,未被处理的非受检异常,前两种在未定义事务的情况下也不会阻断程序继续往下执行,而最后的未捕获的非受检异常在未定义事务的情况下会直接阻断程序往下执行,造成代码行为的不连贯,出现操作不同步,通俗来讲就是只有最后一种是bug,前两种只是程序的正常行为,所以事务只会在最后一种bug的情况生效
而异常处理不当之所以会被归为@Transactional注解失效的情况之一主要是从行为上看程序出现了异常而无法触发回滚,见仁见智
解决方法:
如果要扩大@Transactional注解管理的范围,可以通过 @Transactional(rollbackFor = Exception.class)
指定需要回滚的异常类型。
如果要让被捕获的非受检异常也能触发回滚可以捕获异常后重新抛出(如 throw new RuntimeException(e)
)
事务配置错误
事务传播行为(propagation)的配置比如NOT_SUPPORTED等会导致事务失效,这些配置相当于给了他们配置的方法一个免死金牌,允许他们在加入标记为事务的方法后也不加入事务中而是单独存在,propagation就不细讲了,具体参考我ssm的文章
底层不支持
1.数据库引擎不支持事务,由于@Transactional只能保证Spring层面的事务逻辑,最终还是依赖于数据库引擎的支持,所以如果数据库引擎不支持事务,那@Transactional也会失效
2.多线程调用,若在事务方法中开启新线程执行数据库操作,新线程的操作不会纳入当前的事务管理,因为事务上下文无法跨线程传递