大家好,我是楼仔!
下面我会简单介绍一下 Spring 事务的基础知识,以及使用方法,然后直接对源码进行拆解。
不 BB,上文章目录。
![图片 图片](https://s7.51cto.com/oss/202210/31/1827d9765f93e07077b467a51f7d5cf894dc43.png)
1. 项目准备
需要搭建环境的同学,代码详见:https://github.com/lml200701158/program_demo/tree/main/spring-transaction
下面是 DB 数据和 DB 操作接口:
// 提供的接口
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&serverTimeznotallow=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 块,我们会结合上面的示例,给大家进行讲解。![图片 图片](https://s2.51cto.com/oss/202210/31/790a02828e60246f3bd44776accb9832484193.png)
第一块是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:
获取 Louzai 的切面方法:首先会拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行事务处理的方法,匹配成功的方法,还需要将事务属性保存到缓存 attributeCache 中。
创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。
![图片 图片](https://s5.51cto.com/oss/202210/31/66619c70139ebdf4a391515a467130516cc11d.png)
第二块是事务执行,整个逻辑比较复杂,我只选取 4 块最核心的逻辑,分别为从缓存拿到事务属性、创建并开启事务、执行业务逻辑、提交或者回滚事务。
3. 源码解读
注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不一样!!!
上面的知识都不难,下面才是我们的重头戏,让你跟着楼仔,走一遍代码流程。
3.1 代码入口
![图片 图片](https://s3.51cto.com/oss/202210/31/04fc60d414b2eae440e4597f82b17d91d4a66e.png)
![图片 图片](https://s9.51cto.com/oss/202210/31/583c46f36fc0fd9f1841071b74c87cdb6710b2.png)
这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai。
![图片 图片](https://s9.51cto.com/oss/202210/31/6389d7303476e292097070d782456fdf3b308a.png)
![图片 图片](https://s4.51cto.com/oss/202210/31/640f07c9629b16f0ea3679f0fc857475edd7de.png)
进入 doGetBean(),进入创建 Bean 的逻辑。
![图片 图片](https://s5.51cto.com/oss/202210/31/667b11f07e72da4f0c4487b77583093098d139.png)
进入 createBean(),调用 doCreateBean()。
![图片 图片](https://s5.51cto.com/oss/202210/31/d9c638803043551d9f6347ff143774e4ed5f5d.png)
进入 doCreateBean(),调用 initializeBean()。
![图片 图片](https://s7.51cto.com/oss/202210/31/e95073d928c1fb0603490381dfd882a8e3a66d.png)
![图片 图片](https://s3.51cto.com/oss/202210/31/f7deafe37194c5d06ec179fa00083b9ff327a6.png)
![图片 图片](https://s8.51cto.com/oss/202210/31/26a084394a60d1920b446714587d24123617f4.png)
![图片 图片](https://s4.51cto.com/oss/202210/31/44af06235420b1e158d2759575f5b6082bd4ff.png)
如果看过我前面几期系列源码的同学,对这个入口应该会非常熟悉,其实就是用来创建代理对象。
3.2 创建代理对象
![图片 图片](https://s7.51cto.com/oss/202210/31/78b522457d1a399ce38790060f661d4d040cd2.png)
这里是重点!敲黑板!!!
先获取 louzai 类的所有切面列表;
创建一个 AOP 的代理对象。
![图片 图片](https://s3.51cto.com/oss/202210/31/e97a50e2691dae86eed7761b42876bfa70d7aa.png)
3.2.1 获取切面列表
![图片 图片](https://s3.51cto.com/oss/202210/31/12f41f322c7c31c242e686365d33176c6b8a5c.png)
这里有 2 个重要的方法,先执行 findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。
![图片 图片](https://s5.51cto.com/oss/202210/31/879efff90542a416e06699a30fab296504eae5.png)
![图片 图片](https://s9.51cto.com/oss/202210/31/85bccf498c4575c6ae166994701e9ec5160e01.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/c148b9d51b7b6d1c8d88735aba15d99108c232.png)
![图片 图片](https://s8.51cto.com/oss/202210/31/63204a723852d116c31516dc5d26f8fc73bf4f.png)
依次返回,重新来到 findEligibleAdvisors()。
![图片 图片](https://s6.51cto.com/oss/202210/31/e8d5df2753a902daad26973eb0f36ae64c56f7.png)
![图片 图片](https://s6.51cto.com/oss/202210/31/21b0a9017aab48eeb868294f9eaa712a622149.png)
![图片 图片](https://s8.51cto.com/oss/202210/31/b5f8ab442c68d80378570436d4c06040267f87.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/057ad2344c9f487e2694867ab599fb1d6c6b93.png)
进入 canApply(),开始匹配 louzai 的切面。
![图片 图片](https://s5.51cto.com/oss/202210/31/371206689a90ac3cc199066f831399ca356263.png)
这里是重点!敲黑板!!!
这里只会匹配到 Louzai.testSuccess() 方法,我们直接进入匹配逻辑。
![图片 图片](https://s3.51cto.com/oss/202210/31/423c7fa659cb75461972493ea1a22d8ebfbd04.png)
如果匹配成功,还会把事务的属性配置信息放入 attributeCache 缓存。
![图片 图片](https://s8.51cto.com/oss/202210/31/f3e12cb560d9c460e6b54982e90e1134a802a3.png)
![图片 图片](https://s4.51cto.com/oss/202210/31/33ee26d40b8a86da916774bc09f680be008c82.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/242090020a088c0454477883853dd9e210facb.png)
![图片 图片](https://s3.51cto.com/oss/202210/31/a3980eb1790ad8e1e096216662154ce3838bcc.png)
![图片 图片](https://s3.51cto.com/oss/202210/31/79982fa71853e7b3320448ce1bdd7a89942c74.png)
![图片 图片](https://s6.51cto.com/oss/202210/31/a9d0063740d2cdad9c6869b4e02f81bde64950.png)
我们依次返回到 getTransactionAttribute(),再看看放入缓存中的数据。
![图片 图片](https://s5.51cto.com/oss/202210/31/36d36cd23bd60afed0644988d49a047b6eda58.png)
再回到该小节开头,我们拿到 louzai 的切面信息,去创建 AOP 代理对象。
![图片 图片](https://s5.51cto.com/oss/202210/31/a524658754e11f608766039575db288c4d2fe7.png)
3.2.2 创建 AOP 代理对象
创建 AOP 代理对象的逻辑,在上一篇文章(Spring AOP)讲解过,我是通过 Cglib 创建,感兴趣的同学可以关注公众号「楼仔」,翻一下楼仔的历史文章。
3.3 事务执行
回到业务逻辑,通过 louzai 的 AOP 代理对象,开始执行主方法。
![图片 图片](https://s8.51cto.com/oss/202210/31/d9a508290dfb5b9798a8915a4c9ad30afdf688.png)
因为代理对象是 Cglib 方式创建,所以通过 Cglib 来执行。
![图片 图片](https://s8.51cto.com/oss/202210/31/f90756777f095f66d496152c3eec6c9c4a14ac.png)
![图片 图片](https://s2.51cto.com/oss/202210/31/37fa46d9382175756f5921609c40e140ec6386.png)
![图片 图片](https://s8.51cto.com/oss/202210/31/338127a683a8922b95d8447482d4c079aab113.png)
![图片 图片](https://s2.51cto.com/oss/202210/31/a1778dd010c4b874f266232fb84058a6a57890.png)
这里是重点!敲黑板!!!
下面的代码是事务执行的核心逻辑 invokeWithinTransaction()。
![图片 图片](https://s9.51cto.com/oss/202210/31/b321f0e5166c5115b49309344bf99d94b700d3.png)
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
//获取我们的事务属源对象
TransactionAttributeSource tas = getTransactionAttributeSource();
//通过事务属性源对象获取到我们的事务属性信息
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//获取我们配置的事务管理器对象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//从tx属性对象中获取出标注了@Transactionl的方法描述符
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//处理声明式事务
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//有没有必要创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
//调用钩子函数进行回调目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//抛出异常进行回滚处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清空我们的线程变量中transactionInfo的值
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
//编程式事务
else {
// 这里不是我们的重点,省略...
}
}
3.3.1 获取事务属性
在 invokeWithinTransaction() 中,我们找到获取事务属性的入口。
![图片 图片](https://s5.51cto.com/oss/202210/31/e8868b5361dc84898e99649857898222546eaf.png)
从 attributeCache 获取事务的缓存数据,缓存数据是在 “2.2.1 获取切面列表” 中保存的。
![图片 图片](https://s7.51cto.com/oss/202210/31/620906350314bcb5858662303eb14ad91448d3.png)
3.3.2 创建事务
![图片 图片](https://s6.51cto.com/oss/202210/31/091ab2533a43624af47988c799e0ad3df12d08.png)
![图片 图片](https://s5.51cto.com/oss/202210/31/0963b0459d1f94ad89b090c57e2737c6d2e920.png)
![图片 图片](https://s6.51cto.com/oss/202210/31/a8ec62800e05f96d8d8008ad3d37dcdd697292.png)
通过 doGetTransaction() 获取事务。
protected Object doGetTransaction(){
//创建一个数据源事务对象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//是否允许当前事务设置保持点
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/**
* TransactionSynchronizationManager 事务同步管理器对象(该类中都是局部线程变量)
* 用来保存当前事务的信息,我们第一次从这里去线程变量中获取 事务连接持有器对象 通过数据源为key去获取
* 由于第一次进来开始事务 我们的事务同步管理器中没有被存放.所以此时获取出来的conHolder为null
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
//返回事务对象
return txObject;
}
通过 startTransaction() 开启事务。
![图片 图片](https://s4.51cto.com/oss/202210/31/47ced1229ede57475fc4513f3c22b12c35c7fd.png)
下面是开启事务的详细逻辑,了解一下即可。
protected void doBegin(Object transaction, TransactionDefinition definition){
//强制转化事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//判断事务对象没有数据库连接持有器
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//通过数据源获取一个数据库连接对象
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//标记当前的连接是一个同步事务
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//为当前的事务设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//关闭自动提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
//判断事务为只读事务
prepareTransactionalConnection(con, definition);
//设置事务激活
txObject.getConnectionHolder().setTransactionActive(true);
//设置事务超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 绑定我们的数据源和连接到我们的同步管理器上 把数据源作为key,数据库连接作为value 设置到线程变量中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//释放数据库连接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
最后返回到 invokeWithinTransaction(),得到 txInfo 对象。
![图片 图片](https://s3.51cto.com/oss/202210/31/73899da613e21e60ac943652fe4e429271d6d7.png)
3.3.3 执行逻辑
还是在 invokeWithinTransaction() 中,开始执行业务逻辑。
![图片 图片](https://s5.51cto.com/oss/202210/31/b891285809a4f8765bd680456713c60deb9eef.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/c7b9790880c697ce2c902153dee031ebe48ec2.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/b32ba8a1485f13088607823dba348924d4a854.png)
![图片 图片](https://s7.51cto.com/oss/202210/31/a8dccc6104e2188964e673a21f6c73b02596c5.png)
![图片 图片](https://s3.51cto.com/oss/202210/31/3479d326461b389e08004066f9cab56d509a34.png)
进入到真正的业务逻辑。
![图片 图片](https://s7.51cto.com/oss/202210/31/2330b01468b285aa7543470090f6dd1f72d5da.png)
执行完毕后抛出异常,依次返回,走后续的回滚事务逻辑。
3.3.4 回滚事务
还是在 invokeWithinTransaction() 中,进入回滚事务的逻辑。
![图片 图片](https://s9.51cto.com/oss/202210/31/79fa77237b781c4a174758822d5591077dca81.png)
。
![图片 图片](https://s8.51cto.com/oss/202210/31/a93ef3f912e92e54f05748a2eccd84fab4c9d2.png)
执行回滚逻辑很简单,我们只看如何判断是否回滚。
![图片 图片](https://s2.51cto.com/oss/202210/31/958e8d205f121c33f2f8745584c0775639c344.png)
![图片 图片](https://s8.51cto.com/oss/202210/31/985798e16afe8574c292104b2035e89f53eddb.png)
![图片 图片](https://s4.51cto.com/oss/202210/31/84dd77f04d34956983f05927452e804f5a2ece.png)
如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。
之所以用递归,不仅需要判断抛出异常的本身,还需要判断它继承的父类异常,满足任意一个即可捕获。
![图片 图片](https://s8.51cto.com/oss/202210/31/e5baa2e9566295def7b069f8589f99e7492e6c.png)
到这里,所有的流程结束。
4. 结语
我们再小节一下,文章先介绍了事务的使用示例,以及事务的执行流程。
之后再剖析了事务的源码,分为 2 块:
先匹配出 louzai 对象所有关于事务的切面列表,并将匹配成功的事务属性保存到缓存;
从缓存取出事务属性,然后创建、启动事务,执行业务逻辑,最后提交或者回滚事务。