【Spring6源码・事务】事务核心源码解析
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
事务相关
在此之前简单温习一下:
事务的四大特性:
- 原子性(Atomicity:事务不可再分要么都执行要么都不执行。
- 一致性(Consistency:事务执行前后数据的完整性保持一致即修改前后数据总量是一样的大概。
- 隔离性(Isolation:一个事务执行过程中不会受到其他事务干扰。
- 持久性(Durability:事务一旦结束对数据库的影响是永久的。数据持久化到数据库中。
事务在并发场景下的常见问题:
- 脏读:当 A事务 读取 B事务 未提交的数据后B事务 回滚导致 A事务 读取到的数据为脏数据。
- 不可重复读:A事务 读取第一次读取数据后B事务 对该数据进行了修改并提交A事务 再去读取数据时前后数据结果不一致。
- 幻读:A事务 读取第一次读取数据后B事务 又插入或删除了新的数据并提交A事务 再去读取数据时前后结果不一致。
Spring事务的五个隔离级别:
- ISOLATION_DEFAULT:使用数据库默认的事务隔离级别。
- ISOLATION_READ_UNCOMMITTED:事务最低的隔离级别允许一个事务可以读取另一个事务未提交的数据。 会产生脏读、不可重复读和幻读。
- ISOLATION_READ_COMMITTED: 保证一个事务只能读取另一个事务修改并提交后的数据不能读取未提交的数据。防止脏读。
- ISOLATION_REPEATABLE_READ:保证一个事务不能更新另一个事务修改但尚未提交的数据。可以避免脏读和不可重复读。
- ISOLATION_SERIALIZABLE:序列化执行所有事务。都避免了但是效率极低。
Spring事务的七个传播行为
在同一个事务中:
- PROPAGION_REQUIRED(默认:支持当前事务不存在则创建一个新的事务。
- PROPAGION_SUPPORTS:支持当前事务不存在就以非事务方式运行。
- PROPAGION_MANDATORY:支持当前事务如果不存在抛出异常。
不同事务中:
- PROPAGIN_REQUIRES_NEW:当前存在事务则挂起创建一个新事务。
- PROPAGION_SUPPORTS:当前存在事务则挂起以非事务方式运行。
- PROPAGION_NEVER:当前存在事务抛出异常非事务方式运行。
- PROPAGION_NESTED:当前存在事务则嵌套事务执行。
源码解析
通过上一篇文章《【Spring6源码・事务】事务的代理对象的创建》我们知道事物的代理对象是如何创建的。最后构建出了代理对象:TransactionServiceImpl$$SpringCGLIB$$0@7736
但我们调用程序中的代码:可以看出此时的对象是cglib代理的对象。
步入该方法会进入cglib的拦截方法中:根据我们上一篇提到的advice生成对应的拦截器。
进而调用proceed()
方法。
不出所料我们只生成了一个拦截器就是事务相关的拦截器:TransactionInterceptor
步入proceed()方法:
步入invoke()方法:
步入invokeWithinTransaction方法注意这里还传了一个回调函数一会说。
这里首先是获取事务相关参数:事务的传播行为(propagation) 和 隔离级别(isolation)
没有进行额外设置就获取到了默认值:PROPAGATION_REQUIRED 和 ISOLATION_DEFAULT
接下来根据tm创建事务管理器 PlatformTransactionManager:
接下来就是事务的核心部分:
创建事务
首先是创建事务:步入createTransactionIfNecessary方法:
这里有两个核心步骤:第一个步骤是返回一个包含事务相关信息的事务状态第二个步骤是将事务信息设置进线程本地变量。
一个一个看一看:
步入tm.getTransaction(txAttr)
方法:
步入doGetTransaction()
方法步了个寂寞。
跳出来步入startTransaction方法:
步入这个都doBegin方法:
来到核心方法doBegin方法:
首先从数据源中获取Connection进而将其封装成ConnectionHolder
Connection newCon = obtainDataSource().getConnection();
...
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
因为Spring的事务是自动提交的所以这里关闭了自动提交:
con.setAutoCommit(false);
步出来到最外层:
步入prepareTransactionInfo方法:
构建事务信息里面还包含了该事务的状态。
步入640行代码txInfo.bindToThread();
将相关信息存进本地线程中。
就此我们的事务创建完成:
接下来是我们调用拦截器链及目标方法。
retVal = invocation.proceedWithInvocation();
步入proceedWithInvocation方法:
回到我们额回调方法中:
执行目标方法
最后会调到我们的目标方法:
这里遇到了异常代码:1/0
该异常会被捕获到:
捕获异常
捕获异常最终会抛到这里:
步入completeTransactionAfterThrowing方法:
因为我们的是个新事务里面会有一个字段进行标识。
步入doRollback方法:
首先取出Holder中的Connection最后用原生的rollback回滚。
最后调用cleanupAfterCompletion(status)方法还原事务相关状态和信息包括Connection、自动提交事务的打开、锁相关等。
最后回归到我们的大流程:
步入cleanupTransactionInfo()清除事务相关信息:
这里我们的流程就结束了因为我们catch中捕获的异常抛出上层处理了。
不会向下走此时我们删除我们的目标方法中的异常代码:
再来一遍:
ok。
清理事务信息
此时我们步入cleanupTransactionInfo方法:
最后还是这步6。
回到主流程:
方法返回提交事务
步入commitTransactionAfterReturning(txInfo)方法:
这里会提交事务当然在提交事务之前会判断这个是不是新事务如果是新事务则会提交事务如果不是新事务则会保存当前事务跳出当前事务后执行新事务一并提交。
这里怪我了我应该写两个sql操作的但是流程太过繁琐比如事务的隔离级别我并不能一一枚举出来而且我也没有写SpringBoot3的启动源码和自动装配源码就用SpringBoot环境来讲解是有一些麻烦。
行慢慢来吧。