Spring-奇怪的回滚

273 阅读2分钟

上周我遇到了一个奇怪的问题,异常被try住的情况下,事务仍然回滚了。

public class ServiceA{
    public void methodA(){
        ...; // 执行插入
        try{
            serviceB.methodB(); // 这一行抛了异常
        } catch (Exception e){}
    }
}

public class ServiceB{
   public void methodB(){
      ...;// 执行插入
   } 
}

ServiceA与ServiceB都由Spring来管理事务,ServiceB在ServiceA的方法中被调用,而且ServiceB周围有个try块。

一般来讲,顺着去理解,既然异常被try住了,那么就不会引起事务回滚。但抛异常的时候还是义无反顾的回滚。

拔屌无情。


ServiceB周围如果没有那个try块,回滚就很好理解了。所以为什么呢?

只能回头去查配置。两个类、事务、嵌套…… ……唔,多半和这个有关

<tx:method name="*" propagation="REQUIRED" />

propagation,此参名为事务传播方式,除“REQUIRED”外,还存在“REQUIRES_NEW”等,共7种传播方式。其它的不提,因为本次问题只与这两种方式有关。

“REQUIRED”:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。

“REQUIRES_NEW”:新建事务,如果当前存在事务,把当前事务挂起。


那末,就是因为配置了“REQUIRED”的关系,导致ServiceA与ServiceB共享一个事务,一起提交,一起回滚,所以当ServiceB抛异常时,即便被try住,还是带着ServiceA一起回滚了。当我把配置改成“REQUIRES_NEW”,ServiceB与ServiceA就各玩各的。ServiceB的异常被try住后,ServiceA的操作还是可以继续执行,提交事务。

ok,问题解决了。


可,为什么呢?

Spring是怎么管理事务的?

 AOP


那这个东西说的简单点,就是Spring会为每一个它管理的类,都生成一个代理类,并且对外只提供代理类,操作也都靠代理类来完成。

这是原来的

public class ServiceB{
   public void methodB(){
      ...;// 执行插入
   } 
}

代理完后基本上变成这样

public class ServiceBProxy{
    private ServiceB serviceB;
    
    public void methodBProxy(){
        try{
            beginTransaction();

            serviceB.methodB();

            commit();
        }catch(Exception e){
            rollback();
        }
    }
}

那么整个流程的伪码,最后是不是就长这样(当然我这个伪码还是很伪的,而且有一部分猜测在里面)


所以当事务传播方式为“REQUIRED”时,serviceA内写try块也没用,serviceB产生的异常,已经先一步被serviceB代理类的try块捕获,导致事务回滚。

其实到这一步,再动动脑子就发现了,改一下try块的粒度,就可以做到在“REQUIRED”条件下,即便抛异常也提交事务。


在serviceB方法内部加try块的话,异常直接捕获,不会逃离到serviceB代理类的try块里,代码继续执行,因此事务也不会提交。

但走这种邪门歪路还是有风险的,老老实实新开一个事务比较好。