项目场景:
在循环里面使用try-catch去捕获异常的时候,并且try里面调的方法它也使用了事务注解 @transactional或者用了事务切面AOP去实现方法事务注入
问题描述
我这里的业务需求是A方法里面调了B方法,并且A和B都有事务,当B出现异常的时候我想获取B的具体报错信息,但是catch这个B方法的时候发生了嵌套事务异常,A方法继续往上抛出导致Transaction rolled back because it has been marked as rollback-only(事务回滚,因为它已被标记为仅回滚)
示例代码如下:
@transactional
public void A(){
try{
B();
}catch(Exception e){
e.printStackTrace();
}
}
@transactional
public static void B(){
System.out.println("测试");
}
原因分析:
@Transactional的默认事务传播机制是REQUIRED ,并且B方法抛出异常的时候会标记rollback-only为true,然后A准备提交事务的时候发现rollback-only为true也会进行回滚并且把B抛出的异常信息给吞了。也就是我想要B的异常信息没给,只给了一个rollback-only信息给前端。
事务传播级别 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就创建一个事务,如果已经存在事务,就加入到这个事务。当前传播机制也是spring默认传播机制 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,就抛出异常。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
解决方案:
1、删除try-catch(不推荐)
这个没什么好说的,就是不进行try-catch,但是很多业务就是需要自己捕获异常,不让用户看到这个异常信息,继续维持服务器的运行。
2、修改事务传播级别
网上最为流传的解决方法就是根据业务设置不同的事务传播级别,将B方法的注解改为REQUIRES_NEW(A和B不需要同一个事务,把事务分开)或者NESTED(A和B同一个事务,但是B回滚的时候A不会回滚),即:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void B(){
System.out.println("测试");
}
REQUIRED
- 假设在A⽅法存在⼀个当前事务,B⽅法的事务传播机制为REQUIRED,则B⽅法会合并到A⽅法的事务⾥执⾏。
- A、B任意⼀个⽅法异常(默认是RuntimeException和Error)都会导致A、B的操作被回滚。
- Spring事务管理器不会吞异常。
B异常后会抛给A,A如果没有catch这个异常,会继续向上抛。如果A catch住了,Spring事务管理器会替A向上抛⼀个
UnexpectedRollbackException。总之,⼀旦A、B回滚,A的调⽤⽅⼀定能收到⼀个异常感知到回滚。(问题所在)
REQUIRES_NEW
- 如果B发⽣异常,B事务⼀定回滚,B的异常随后会抛给A,如果A catch住了这个异常,A不会回滚,否则A也会回滚。
- 如果A发⽣异常,则只会回滚A,不会回滚B。
NESTED
- 如果B异常,B⼀定回滚,B的异常随后会抛给A,如果A catch住了这个异常,A不会回滚,否则A也会回滚。这种情况和REQUIRES_NEW⼀样。
- 如果A发⽣异常,则A、B都会回滚。
根据业务需求,后面两个传播级别是不符合我的需求的,因为会导致A或B的事务不回滚,第一个传播级别REQUIRED才符合但有问题,即第一个方法对我无效,大家可以先试试。
3、手动回滚(推荐)
这个是我在逛了无数的论坛和提问才发现的解决方案,在catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常
示例代码如下:
@Transactional
public void A(){
try{
B();
}catch(Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
}
@Transactional
public void B(){
System.out.println("测试");
}
参考文章:
事务传播级别(7种)
Spring事务嵌套引发的血案—Transaction rolled back because it has been marked as rollback-only