Spring中事务的嵌套

原文链接

事务传播属性

  1. propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。

  2. propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

  3. propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

  4. propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

  5. propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  6. propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

  7. propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring事务实现原理

Spring本身是没有事务的,只有数据库才会回有事务,而Spring的事务是借助AOP,通过动态代理的方式,在我们要操作数据库的是时候,实际是Spring通过动态代理进行功能扩展,在我们的代码操作数据库之前通过数据库客户端打开数据库事务,如果代码执行完毕没有异常信息或者是没有Spring要捕获的异常信息时,再通过数据库客户端程序提交事务,如果有异常信息或者是有Spring要捕获的异常信息,再通过数据库客户端程序回滚事务,从而达到控制数据库事务的目的。

@Transactional 默认情况下的传播属性:

嵌套方式一

同一个类中事务嵌套

答案是会回滚的,原因是什么呢?接着看下边的示意图

原因是因为代理的时候,直接把没有事务的方法包在了有事务的代理方法里边了自然就有事务了。

嵌套方式二

同一个类中事务嵌套

答案:这时候调用test02,test01有异常发生时是不会回滚的

嵌套方式三

同一个类中事务嵌套

如果调用test02 的话,当程序发生异常,test01会不会回滚呢?

答案是会回滚的 原因和嵌套方式一中的情况类似

综上,同一个类中事务嵌套,最终结果取决于最外层的方法事务的传播特性

嵌套方式四

不同类中事务嵌套

在TestService3中调用TestService4中的没有事务的方法,会发生什么呢?

TestService4中的test41方法发生异常会回滚吗?

答案 会的

嵌套方式五

不同类中事务嵌套

TestService3中的方法是有事务的,TestService4中的方法是以非事务的方式运行,如果存在事务就挂起事务,那么自然就没有事物了

所以上述的情况就是,如果TestService4中的方法报出异常,则TestService3中的数据可以回滚,但是TestService4中操作的数据是不会回滚的

不同类的事务嵌套,外层的方法按照外层的事务传播属性执行,内层的传播属性按照内层的传播属性执行

Spring事务失效情况

  1. 事务方法访问修饰符不是public,导致事务失效

    其实如果是JDK的动态代理 就不允许这种情况,因为JDK动态代理需要有接口,而接口中是不能有 私有方法的,如果是CGLIB动态代理的话也是不允许的代理private方法的

  2. 如果事务方法是static、final的,同样无法通过动态代理,事务也是不会生效的。

    Spring的声明式事务是基于动态代理实现的,我们无法重写final修饰的方法;不管是JDK动态代理还是Cglib的动态代理,就是要通过代理的方式获取到代理的具体对象,而static方法修饰的方法是属于类对象的,不属于任何实例对象,所以static方法不能被重写也就是说static方法也不被进行动态代理。

  3. 操作的数据库表如果本身不支持事务,那么配置的Spring事务也会失效。

  4. 代码中的异常被 catch 住,而且没有异常再次抛出,也会让Spring事务失效。

    想要避免上述失效的话,可以在 catch 住后再次抛出Spring事务支持的异常。

  5. 多线程的调用导致事务的失效,例如 下边的案例 会导致 test31的事务失效

原因:test31中操作数据要回滚,需要有异常抛出才可以回滚,但是由于线程异步运行这种写法,test31和test41分别是两个独立的事务,他们的数据库链接都可能是不同的,要想回滚则test31和test41都应该有异常抛出才可以。

但是test31所在的线程没有捕获到test41所在的子线程的异常所以没有回滚,之所以没有捕获到test41所在的线程的异常是因为test41相对test31是异步运行,可能test31已经运行完了,但是test41还没有运行完,所以在写这种代码的时候大家可以使用thread.setUncaughtExceptionHandler方法来处理线程内部的异常,也就是当thread报出异常的时候可以在这个方法中再次抛出异常这样可以触发外部事务的回滚,或者也可以采用如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test31() throws ExecutionException, InterruptedException {
Map<String,Object> map=new HashMap<>();
map.put("id",3);
map.put("quantity",300);
int i = testDao.updateStock(map);

FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
testService4.test41();
return "ok";
}
});
Thread thread = new Thread(task);

thread.start();
//等待执行结果,当有异常的时候在这里就可以获取到异常传递给test31触发他的回滚
Object ok = task.get();
System.out.println("=============OK");
}
0%