大家好,我是樓仔!
下面我會簡單介紹一下 Spring 事務(wù)的基礎(chǔ)知識,以及使用方法,然后直接對源碼進行拆解。
不 BB,上文章目錄。

1. 項目準備
需要搭建環(huán)境的同學(xué),代碼詳見:https://github.com/lml200701158/program_demo/tree/main/spring-transaction
下面是 DB 數(shù)據(jù)和 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);
}
基礎(chǔ)測試代碼,testSuccess() 是事務(wù)生效的情況:
@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("事務(wù)生效");
}
}
執(zhí)行入口:
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: 事務(wù)生效
at com.mybatis.controller.Louzai.testSuccess(Louzai.java:34)
// 異常日志省略...
2. Spring 事務(wù)工作流程
為了方便大家能更好看懂后面的源碼,我先整體介紹一下源碼的執(zhí)行流程,讓大家有一個整體的認識,否則容易被繞進去。
整個 Spring 事務(wù)源碼,其實分為 2 塊,我們會結(jié)合上面的示例,給大家進行講解。
第一塊是后置處理,我們在創(chuàng)建 Louzai Bean 的后置處理器中,里面會做兩件事情:
獲取 Louzai 的切面方法:首先會拿到所有的切面信息,和 Louzai 的所有方法進行匹配,然后找到 Louzai 所有需要進行事務(wù)處理的方法,匹配成功的方法,還需要將事務(wù)屬性保存到緩存 attributeCache 中。
創(chuàng)建 AOP 代理對象:結(jié)合 Louzai 需要進行 AOP 的方法,選擇 Cglib 或 JDK,創(chuàng)建 AOP 代理對象。

第二塊是事務(wù)執(zhí)行,整個邏輯比較復(fù)雜,我只選取 4 塊最核心的邏輯,分別為從緩存拿到事務(wù)屬性、創(chuàng)建并開啟事務(wù)、執(zhí)行業(yè)務(wù)邏輯、提交或者回滾事務(wù)。
3. 源碼解讀
注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣?。?!
上面的知識都不難,下面才是我們的重頭戲,讓你跟著樓仔,走一遍代碼流程。
3.1 代碼入口


這里需要多跑幾次,把前面的 beanName 跳過去,只看 louzai。


進入 doGetBean(),進入創(chuàng)建 Bean 的邏輯。

進入 createBean(),調(diào)用 doCreateBean()。

進入 doCreateBean(),調(diào)用 initializeBean()。




如果看過我前面幾期系列源碼的同學(xué),對這個入口應(yīng)該會非常熟悉,其實就是用來創(chuàng)建代理對象。
3.2 創(chuàng)建代理對象

這里是重點!敲黑板!??!
先獲取 louzai 類的所有切面列表;
創(chuàng)建一個 AOP 的代理對象。

3.2.1 獲取切面列表

這里有 2 個重要的方法,先執(zhí)行 findCandidateAdvisors(),待會我們還會再返回 findEligibleAdvisors()。




依次返回,重新來到 findEligibleAdvisors()。




進入 canApply(),開始匹配 louzai 的切面。

這里是重點!敲黑板!?。?/p>
這里只會匹配到 Louzai.testSuccess() 方法,我們直接進入匹配邏輯。

如果匹配成功,還會把事務(wù)的屬性配置信息放入 attributeCache 緩存。






我們依次返回到 getTransactionAttribute(),再看看放入緩存中的數(shù)據(jù)。

再回到該小節(jié)開頭,我們拿到 louzai 的切面信息,去創(chuàng)建 AOP 代理對象。

3.2.2 創(chuàng)建 AOP 代理對象
創(chuàng)建 AOP 代理對象的邏輯,在上一篇文章(Spring AOP)講解過,我是通過 Cglib 創(chuàng)建,感興趣的同學(xué)可以關(guān)注公眾號「樓仔」,翻一下樓仔的歷史文章。
3.3 事務(wù)執(zhí)行
回到業(yè)務(wù)邏輯,通過 louzai 的 AOP 代理對象,開始執(zhí)行主方法。

因為代理對象是 Cglib 方式創(chuàng)建,所以通過 Cglib 來執(zhí)行。




這里是重點!敲黑板!?。?/p>
下面的代碼是事務(wù)執(zhí)行的核心邏輯 invokeWithinTransaction()。

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
//獲取我們的事務(wù)屬源對象
TransactionAttributeSource tas = getTransactionAttributeSource();
//通過事務(wù)屬性源對象獲取到我們的事務(wù)屬性信息
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//獲取我們配置的事務(wù)管理器對象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//從tx屬性對象中獲取出標注了@Transactionl的方法描述符
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//處理聲明式事務(wù)
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//有沒有必要創(chuàng)建事務(wù)
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
//調(diào)用鉤子函數(shù)進行回調(diào)目標方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//拋出異常進行回滾處理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清空我們的線程變量中transactionInfo的值
cleanupTransactionInfo(txInfo);
}
//提交事務(wù)
commitTransactionAfterReturning(txInfo);
return retVal;
}
//編程式事務(wù)
else {
// 這里不是我們的重點,省略...
}
}
3.3.1 獲取事務(wù)屬性
在 invokeWithinTransaction() 中,我們找到獲取事務(wù)屬性的入口。

從 attributeCache 獲取事務(wù)的緩存數(shù)據(jù),緩存數(shù)據(jù)是在 “2.2.1 獲取切面列表” 中保存的。

3.3.2 創(chuàng)建事務(wù)



通過 doGetTransaction() 獲取事務(wù)。
protected Object doGetTransaction(){
//創(chuàng)建一個數(shù)據(jù)源事務(wù)對象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//是否允許當前事務(wù)設(shè)置保持點
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/**
* TransactionSynchronizationManager 事務(wù)同步管理器對象(該類中都是局部線程變量)
* 用來保存當前事務(wù)的信息,我們第一次從這里去線程變量中獲取 事務(wù)連接持有器對象 通過數(shù)據(jù)源為key去獲取
* 由于第一次進來開始事務(wù) 我們的事務(wù)同步管理器中沒有被存放.所以此時獲取出來的conHolder為null
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
//返回事務(wù)對象
return txObject;
}
通過 startTransaction() 開啟事務(wù)。

下面是開啟事務(wù)的詳細邏輯,了解一下即可。
protected void doBegin(Object transaction, TransactionDefinition definition){
//強制轉(zhuǎn)化事務(wù)對象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//判斷事務(wù)對象沒有數(shù)據(jù)庫連接持有器
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//通過數(shù)據(jù)源獲取一個數(shù)據(jù)庫連接對象
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//把我們的數(shù)據(jù)庫連接包裝成一個ConnectionHolder對象 然后設(shè)置到我們的txObject對象中去
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//標記當前的連接是一個同步事務(wù)
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//為當前的事務(wù)設(shè)置隔離級別
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//關(guān)閉自動提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
//判斷事務(wù)為只讀事務(wù)
prepareTransactionalConnection(con, definition);
//設(shè)置事務(wù)激活
txObject.getConnectionHolder().setTransactionActive(true);
//設(shè)置事務(wù)超時時間
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 綁定我們的數(shù)據(jù)源和連接到我們的同步管理器上 把數(shù)據(jù)源作為key,數(shù)據(jù)庫連接作為value 設(shè)置到線程變量中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//釋放數(shù)據(jù)庫連接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
最后返回到 invokeWithinTransaction(),得到 txInfo 對象。

3.3.3 執(zhí)行邏輯
還是在 invokeWithinTransaction() 中,開始執(zhí)行業(yè)務(wù)邏輯。





進入到真正的業(yè)務(wù)邏輯。

執(zhí)行完畢后拋出異常,依次返回,走后續(xù)的回滾事務(wù)邏輯。
3.3.4 回滾事務(wù)
還是在 invokeWithinTransaction() 中,進入回滾事務(wù)的邏輯。

。

執(zhí)行回滾邏輯很簡單,我們只看如何判斷是否回滾。



如果拋出的異常類型,和事務(wù)定義的異常類型匹配,證明該異常需要捕獲。
之所以用遞歸,不僅需要判斷拋出異常的本身,還需要判斷它繼承的父類異常,滿足任意一個即可捕獲。

到這里,所有的流程結(jié)束。
4. 結(jié)語
我們再小節(jié)一下,文章先介紹了事務(wù)的使用示例,以及事務(wù)的執(zhí)行流程。
之后再剖析了事務(wù)的源碼,分為 2 塊:
先匹配出 louzai 對象所有關(guān)于事務(wù)的切面列表,并將匹配成功的事務(wù)屬性保存到緩存;
從緩存取出事務(wù)屬性,然后創(chuàng)建、啟動事務(wù),執(zhí)行業(yè)務(wù)邏輯,最后提交或者回滾事務(wù)。