别再跟我说你不理解 @Transactional 原理了!省流版解读
- 前言
- user 表初始数据
- 隔离级别:NESTED 案例一
- 隔离级别:NESTED 案例一省流版答案解读
- 隔离级别:NESTED 案例一省流版答案源码入口
- 隔离级别 Propagation.NESTED 省流版源码剖析
- 隔离级别:NESTED 案例二
- 隔离级别:NESTED 案例二省流版答案解读
- 其他隔离级别:REQUIRED、REQUIRES_NEW
- 码字还是比较耗时的,后续发布对应的源码级别讲解视频到公众号上
- 本文对应代码地址(包含SQL)
精读版:全面解读spring注解事务失效场景,伪代码+图文深度解析spring源码运行过程
前言
老早前精读过 @Transactional 的源码,现在有时间提炼提炼一下里面的精华出一期,从源码角度剖析 @Transactional 是怎么开启事务的?@Transactional 回滚流程又是怎样的?的角度出发,帮助大家工作中合理的使用 @Transactional 这个注解。
user 表初始数据
隔离级别:NESTED 案例一
- nestedMethodA 将 id 为 1 的数据的 username 修改 nestedMethodA
- nestedMethodB 将 id 为 2 的数据的 username 修改 nestedMethodB
- nestedMethodC 将 id 为 3 的数据的 username 修改 nestedMethodC
- 最后主事务 将 id 为 4 的数据的 username 修改 mainplus
思考:最后谁能更新成功?????
/**
* A,B成功,C失败抛出异常进入 catch 逻辑
*/
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
try {
tabUserService.nestedMethodA();
tabUserService.nestedMethodB();
tabUserService.nestedErrorMethodC();
tabUserService.update(new LambdaUpdateWrapper<TabUser>()
.eq(TabUser::getId, "4")
.set(TabUser::getUsername, "mainplus"));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
隔离级别:NESTED 案例一省流版答案解读
只有 nestedMethodA、nestedMethodB 方法能更新成功
原因 NESTED 是基于回滚点回滚事务的,生成的回滚点的位置,如下,当 nestedErrorMethodC 方法抛出异常后,异常进入到 catch 代码块中,导致
tabUserService.update(new LambdaUpdateWrapper<TabUser>()
.eq(TabUser::getId, "4")
.set(TabUser::getUsername, "mainplus"))
这段代码没有执行,因此 id 为 4 的数据没有更新。由于 catch 代码块中并没有接着向上抛出此异常,因此没有触发回滚主事务的操作,同时因为 nestedErrorMethodC 抛出异常,触发了回滚到回滚点 C 的逻辑,因此回滚点 C 以下的代码全部被回滚了。而 nestedMethodA、nestedMethodB 事务不受影响,因此
只有 nestedMethodA、nestedMethodB 方法能更新成功
/**
* A,B成功,C失败抛出异常进入 catch 逻辑
*/
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
//主事务
try {
//回滚点 A
tabUserService.nestedMethodA();
//回滚点 B
tabUserService.nestedMethodB();
//回滚点 C
tabUserService.nestedErrorMethodC();
tabUserService.update(new LambdaUpdateWrapper<TabUser>()
.eq(TabUser::getId, "4")
.set(TabUser::getUsername, "mainplus"));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
隔离级别:NESTED 案例一省流版答案源码入口
@Transactional 注解封装了如下三大逻辑
- 生成切面,(被 @Transactional 标注的方法的所在类会被生成一个代理对象)
- 开启事务,(执行完目标方法前,会先执行被 Spring 封转好的开启事务的逻辑)
- 提交事务,(执行完目标方法后,会执行被 Spring 封转好的提交事务的逻辑)
- 回滚事务
就拿下面这段代码举例来说,首先会为 TabUserService 生成一个代理对象,@Transactional 注解封装了如下三大逻辑中的点一。
@Service
public class TabUserServiceImpl extends ServiceImpl<TabUserMapper, TabUser> implements TabUserService {
@Autowired
private TabUserService tabUserService;
/**
* A,B成功,C失败抛出异常进入 catch 逻辑
*/
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
try {
//回滚点 A
tabUserService.nestedMethodA();
//回滚点 B
tabUserService.nestedMethodB();
//回滚点 C
tabUserService.nestedErrorMethodC();
tabUserService.update(new LambdaUpdateWrapper<TabUser>()
.eq(TabUser::getId, "4")
.set(TabUser::getUsername, "mainplus"));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
@Transactional 注解封装了如下三大逻辑中的 点二源码入口 如下 DataSourceTransactionManager.doBegin(),省流版不多逼逼,大家自行去 debug,debug 下面的这段代码你将获得如下问题的全套解析:
- ThredLocal 在 @Transactional 源码中是如何应用的?
- @Transactional(propagation = Propagation.NESTED) Spring 事务中的每个隔离级别是如何生效的?
- @Transactional 注解设计的思想
@Transactional 注解封装了如下三大逻辑中的 点三、点四源码入口 如下 TransactionAspectSupport.invokeWithinTransaction。最新的 @Transactional 源码里面用到了一些函数式接口,用于回掉用的,不清楚这方面的小伙伴,可以先去了解下函数式接口。
隔离级别 Propagation.NESTED 省流版源码剖析
回滚点是如何被保存的?源码入口如下:
AbstractPlatformTransactionManager.handleExistingTransaction()
如何根据回滚点回滚,源码入口如下
AbstractTransactionStatus.rollbackToHeldSavepoint()
隔离级别:NESTED 案例二
nestedMethodA、nestedMethodB、nestedErrorMethodC、主事务均更新失败。
/**
* A,B成功,C失败抛出异常进入 catch 逻辑
*/
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
//主事务
try {
//回滚点 A
tabUserService.nestedMethodA();
//回滚点 B
tabUserService.nestedMethodB();
//回滚点 C
tabUserService.nestedErrorMethodC();
tabUserService.update(new LambdaUpdateWrapper<TabUser>()
.eq(TabUser::getId, "4")
.set(TabUser::getUsername, "mainplus"));
} catch (Exception e) {
System.err.println(e.getMessage());
//异常抛到主事务
throw e;
}
}
隔离级别:NESTED 案例二省流版答案解读
NESTED 隔离级别下的事务,会和主事务共用一个数据库连接(源码入口如下),虽然 nestedErrorMethodC 方法也是 NESTED 隔离级别,只会回滚自己,但是异常被 catch 捕获后,接着向上抛,抛到了主事务中,导致了主事务回滚了,因为 nestedMethodB、nestedMethodA 与主事务共用一个数据库连接,因此一块回滚了。导致nestedMethodA、nestedMethodB、nestedMethodC连体回滚更新失败。主事务更新失败是因为代码没有得到执行。
其他隔离级别:REQUIRED、REQUIRES_NEW
使用上,就字面意思,有时间接着举几个对应的例子,持续更新中~~~~
码字还是比较耗时的,后续发布对应的源码级别讲解视频到公众号上
大家可以先关注我一波,或者关注一下公众号,这段时间上班有点忙,后续有时间更新,敬请期待~~~~~~~~
本文对应代码地址(包含SQL)
代码仓库里面自行查找下载即可
https://gitcode.net/users/qq_42875345/projects