61 张图,剖析 Spring 事务,就是要钻到底!
技术琐话 2022-12-20 09:16 发表于四川
以下文章来源于楼仔,作者楼仔
公众号
预计阅读 30 分钟,建议先收藏~~
大家好,我是楼仔!
我终于知道为什么很少有博主愿意写源码系列的文章,真的太熬人,这个是源码系列的第 4 篇,感觉人都快被熬废了。
这篇源码解析,和 Spring AOP 中的知识有很多重合的地方,但是比 AOP 要稍微简单一些,建议两篇文章对比学习。
下面我会简单介绍一下 Spring 事务的基础知识,以及使用方法,然后直接对源码进行拆解。
不 BB,上文章目录。
1. 项目准备
需要搭建环境的同学,代码详见:https://github.com/lml200701158/program_demo/tree/main/spring-transaction
下面是 DB 数据和 DB 操作接口:
| uid | uname | usex |
| 1 | 张三 | 女 |
| 2 | 陈恒 | 男 |
| 3 | 楼仔 | 男 |
// 提供的接口 public interface UserDao { // select * from user_test where uid = "#{uid}" public MyUser selectUserById(Integer uid); // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid} public int updateUser(MyUser user); }
基础测试代码,testSuccess() 是事务生效的情况:
@Service public class Louzai { @Autowired private UserDao userDao; public void update(Integer id) { MyUser user = new MyUser(); user.setUid(id); user.setUname("张三-testing"); user.setUsex("女"); userDao.updateUser(user); } public MyUser query(Integer id) { MyUser user = userDao.selectUserById(id); return user; } // 正常情况 @Transactional(rollbackFor = Exception.class) public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原记录:" + user); update(id); throw new Exception("事务生效"); } }
执行入口:
public class SpringMyBatisTest { public static void main(String[] args) throws Exception { String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); Louzai uc = (Louzai) applicationContext.getBean("louzai"); uc.testSuccess(); } }
输出:
16:44:38.267 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0' 16:44:38.363 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'txManager' 16:44:40.966 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.mybatis.controller.Louzai.testSuccess]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception 16:44:40.968 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/java_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai] 16:44:41.228 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] for JDBC transaction 16:44:41.231 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] to manual commit 原记录:MyUser(uid=1, uname=张三, usex=女) 16:42:59.345 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback 16:42:59.346 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224] 16:42:59.354 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224] after transaction Exception in thread "main" java.lang.Exception: 事务生效 at com.mybatis.controller.Louzai.testSuccess(Louzai.java:34) // 异常日志省略...
2. Spring 事务工作流程
为了方便大家能更好看懂后面的源码,我先整体介绍一下源码的执行流程,让大家有一个整体的认识,否则容易被绕进去。
整个 Spring 事务源码,其实分为 2 块,我们会结合上面的示例,给大家进行讲解。
第一块是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:
获取 Louzai 的切面方法:首先会拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行事务处理的方法,匹配成功的方法,还需要将事务属性保存到缓存 attributeCache 中。
创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。
第二块是事务执行,整个逻辑比较复杂,我只选取 4 块最核心的逻辑,分别为从缓存拿到事务属性、创建并开启事务、执行业务逻辑、提交或者回滚事务。




