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块。java
通常来说,顺着去理解,既然异常被try住了,那么就不会引发事务回滚。但抛异常的时候仍是义无反顾的回滚。bash
拔屌无情。spa
ServiceB周围若是没有那个try块,回滚就很好理解了。因此为何呢?代理
只能回头去查配置。两个类、事务、嵌套…… ……唔,多半和这个有关code
<tx:method name="*" propagation="REQUIRED" />复制代码
propagation,此参名为事务传播方式,除“REQUIRED”外,还存在“REQUIRES_NEW”等,共7种传播方式。其它的不提,由于本次问题只与这两种方式有关。cdn
“REQUIRED”:若是当前没有事务,就新建一个事务,若是已经存在一个事务中,加入到这个事务中。
xml
“REQUIRES_NEW”:新建事务,若是当前存在事务,把当前事务挂起。
blog
那末,就是由于配置了“REQUIRED”的关系,致使ServiceA与ServiceB共享一个事务,一块儿提交,一块儿回滚,因此当ServiceB抛异常时,即使被try住,仍是带着ServiceA一块儿回滚了。当我把配置改为“REQUIRES_NEW”,ServiceB与ServiceA就各玩各的。ServiceB的异常被try住后,ServiceA的操做仍是能够继续执行,提交事务。
事务
ok,问题解决了。string
可,为何呢?
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块里,代码继续执行,所以事务也不会提交。
但走这种邪门歪路仍是有风险的,老老实实新开一个事务比较好。