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块里,代码继续执行,因此事务也不会提交。
但走这种邪门歪路还是有风险的,老老实实新开一个事务比较好。