嵌套事务未回滚解决方案(^_^)

首先想到的是事务的传播行为

这里的Propagation是事务的传播行为,默认是REQUIRED,意思是如果当前没有事务,就开启一个事务,如果已经存在一个事务,就加入到这个事务中;

REQUIRES_NEW,新建事务,如果当前存在事务,把当前事务挂起;意思是这里执行到child()方法时,parent所在的事务就会挂起,方法child就会起一个新的事务,等待方法child的事务完成以后,方法parent才继续执行。如下代码:

嵌套事务未回滚解决方案(^_^)_第1张图片

主方法parent()里调child()方法,当child()抛出异常时,parent()和child()均未回滚。背景先介绍到这里,你可以先想想为什么没回滚,下面由浅入深讲解。

一、场景分析
 场景A:

嵌套事务未回滚解决方案(^_^)_第2张图片

嵌套事务未回滚解决方案(^_^)_第3张图片

 这里是分别执行了两个事务,执行的结果是两个方法都可以插入数据!

场景B:
修改上述代码如下:

嵌套事务未回滚解决方案(^_^)_第4张图片

注:这里的Propagation是事务的传播行为,默认是REQUIRED,意思是如果当前没有事务,就开启一个事务,如果已经存在一个事务,就加入到这个事务中;REQUIRES_NEW是说,新建事务,如果当前存在事务,把当前事务挂起;意思是这里执行到child()方法时,parent所在的事务就会挂起,方法child就会起一个新的事务,等待方法child的事务完成以后,方法parent才继续执行。

执行的结果是两个方法都可以插入数据!

场景A和场景B都是正常的执行,期间没有发生任何的回滚,假如child()方法中出现了异常! 

场景C:
修改child()的代码如下所示,其他代码和场景B一样:

嵌套事务未回滚解决方案(^_^)_第5张图片

会出现异常,并且数据全都没有插入进去:

疑问1:场景C中child()抛出了异常,但是parent()没有抛出异常,按道理是不是应该parent()提交成功而child()回滚?
可能有的小伙伴要说了,child()抛出了异常在parent()没有进行捕获,造成了parent()也是抛出了异常了的!所以他们两个都会回滚?

场景D:
按照上述小伙伴的疑问这个时候,如果对parent()方法修改,捕获child()中抛出的异常,其他代码和场景C一样:

嵌套事务未回滚解决方案(^_^)_第6张图片

 然后再次执行,结果是两个都插入了数据库:

 

看到这里很多小伙伴都可能会问,按照我们的逻辑来想的话child()中抛出了异常,parent()没有抛出并且捕获了child()抛出了异常!执行的结果应该是child()回滚,parent()提交成功的啊!

疑问2:场景D为什么不是child()回滚和parent()提交成功哪? 

二、问题本质所在

我们知道Spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述parent()方法调用child()方法的时候造成了child()方法中的事务失效!简单的来说,在场景D中parent()方法调用child()方法的时候,child()方法的事务是不起作用的,此时的child()方法像一个没有加事务的普通方法,其本质上就相当于下边的代码:

场景C的本质:嵌套事务未回滚解决方案(^_^)_第7张图片

场景D的本质:

嵌套事务未回滚解决方案(^_^)_第8张图片

 正如上述的代码,我们可以很轻松的解释疑问1和疑问2,因为动态代理的特性造成了场景C和场景D的本质如上述代码。在场景C中,child()抛出异常没有捕获,相当于parent事务中抛出了异常,造成parent()一起回滚,因为他们本质是同一个方法;在场景D中,child()抛出异常并进行了捕获,parent事务中没有抛出异常,parent()和child()同时在一个事务里边,所以他们都成功了;
看到这里,那么动态代理的这个特性到底是什么才会造成Spring事务失效呐?

你可能感兴趣的:(mysql)