写点什么

Spring @Transaction 注解是如何执行事务的?

发布于: 3 小时前

前言

相信小伙伴一定用过 @Transaction 注解,那 @Transaction 背后的秘密又知道多少呢?


Spring 是如何开启事务的?又是如何进行提交事务和关闭事务的呢?

画图猜测

在开始 debug 阅读源码之前,小伙伴们应该已经知道 MySQL 是如何开启事务的


因此可以得出猜测:



那下面跟着源码一起读一读,Spring 的 @Transaction 注解是如何执行事务逻辑的?

Spring 事务执行流程

开启事务

这里使用的是 Spring Boot + MySQL + Druid


<dependency>    <groupId>com.alibaba</groupId>    <artifactId>druid-spring-boot-starter</artifactId>    <version>1.2.6</version></dependency>
复制代码



  1. 在创建 Bean 的时候,会对 UserService 基于 AOP 生成代理对象;


AbstractAutowireCapableBeanFactory#initializeBean...wrapIfNecessaryAbstractAutoProxyCreator#createProxyCglibAopProxy#getProxy 生成代理对象


  • 开始执行 userService.updateUserInfo(); 这里的 userService 就是代理对象;会被 CglibAopProxy.DynamicAdvisedInterceptor#intercept 方法拦截;

  • TransactionInterceptor#invoke 被事务拦截器拦截

  • TransactionAspectSupport#invokeWithinTransaction 事务处理

  • AbstractPlatformTransactionManager#getTransaction 会在这里调用 AbstractPlatformTransactionManager#startTransaction 方法,来开启事务。



是不是看到 doBegin 这个词突然感觉很熟悉。


跟进 DataSourceTransactionManager#doBegin 方法,注意看,此时是在 spring-jdbc-5.3.8.jar 包下面的。



因为使用的 druid 连接池,所以这块 Connection 是 durid 的连接池。


  • DruidPooledConnection#setAutoCommit(false) 关闭自动提交;


这里就是 druid 的逻辑,一顿执行然后到 com.alibaba.druid.filter.FilterChainImpl#connection_setAutoCommit



  • ConnectionImpl#setAutoCommit,这个是在 mysql-connector-java-8.0.25.jar 包下的。



这一句才是重点 SET autocommit=0


SET autocommit=0
复制代码


开启事务了!


总结一下流程:


执行 SQL

在开始事务之后,会通过回调执行方法的内部逻辑。



  • 因为这里使用的是 Mybatis,所以还是会被代理,MapperProxy#invoke

  • DruidPooledPreparedStatement#execute

  • ClientPreparedStatement#execute



执行过程相对比较简单:


提交事务

TransactionAspectSupport#invokeWithinTransaction 最后一行,commitTransactionAfterReturning(txInfo); 就是提交事务。


  • AbstractPlatformTransactionManager#commit 抽象事务管理器,进行提交事务

  • DataSourceTransactionManager#doCommit 数据源数据管理器,提交事务



这里肯定是调用连接池的方法,所以会执行到 DruidPooledConnection


  • DruidPooledConnection commit

  • 最终还是执行到 mysql-connector-java-8.0.25.jar 包下面的 ConnectionImpl#commit



调用 commit 提交事务。


commit
复制代码


异常回滚

异常在这里 TransactionAspectSupport#invokeWithinTransaction 会被 catch。



AbstractPlatformTransactionManager#rollback 在这里进行 rollback



执行 DataSourceTransactionManager#doRollback


最终执行到 mysql-connector-java-8.0.25.jarConnectionImpl#rollback()ConnectionImpl#rollbackNoChecks



从而执行 rollback 语句


rollback
复制代码


恢复 autocommit

cleanupTransactionInfo(txInfo);


在 这个方法中会将之前设置的 autocommit 进行恢复。

Java 原生开启事务

如果觉得这样有点绕,那咱们可以看简单版本的,不带 Spring。


/** * @author liuzhihang * @date 2021/6/18 16:51 */public class MainTest {

public static void main(String[] args) throws Exception {
DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/demo"); dataSource.setUsername("root"); dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
try { // 关闭自动提交 connection.setAutoCommit(false);
connection.prepareStatement("update user_info set user_name = 'liuzhihang' where user_id = '1001';").executeUpdate(); connection.prepareStatement("update user_address set address = 'anhui' where user_id = '1001';").executeUpdate(); // 提交事务 connection.commit(); } catch (Exception e) { // 回滚 connection.rollback(); } finally { // 开启自动提交 connection.setAutoCommit(true); } }}
复制代码


看完 Java 原生提交事务的方式,是不是感觉简单明了。


Spring @Transaction 只是创建了 AOP 代理,通过代理调用原生的开启关闭事务,同样在执行 SQL 那一块,也是 Mybatis 进行了代理,从而提交 SQL。

总结

最后,将图进行合并,总结流程。



至此,事务执行过程分析完毕。


不过还是有一个疑问?


为什么使用 set autocommit = 0 来开启事务,而不是使用 begin 或者 start transaction 来开启事务呢?

相关推荐

发布于: 3 小时前阅读数: 4
用户头像

个人公众号:『 程序员小航 』 2020.07.30 加入

某不知名互联网公司 Java 程序员一枚。记录工作学习中的技术、开发及源码笔记;分享生活中的见闻感悟。

评论

发布
暂无评论
Spring @Transaction 注解是如何执行事务的?