這篇源碼解析,和 Spring AOP 中的知識有很多重合的地方,但是比 AOP 要稍微簡單一些,建議兩篇文章對比學習。
下面我會簡單介紹一下 Spring 事務的基礎知識,以及使用方法,然後直接對源碼進行拆解。
不 BB,上文章目錄。

1. 項目準備
需要搭建環境的同學,代碼詳見:https://github.com/lml200701158/program_demo/tree/main/spring-transaction
下面是 DB 數據和 DB 操作接口:
//提供的接口publicinterfaceUserDao{//select*fromuser_testwhereuid="#{uid}"publicMyUserselectUserById(Integeruid);//updateuser_testsetuname=#{uname},usex=#{usex}whereuid=#{uid}publicintupdateUser(MyUseruser);}
基礎測試代碼,testSuccess() 是事務生效的情況:
@ServicepublicclassLouzai{@AutowiredprivateUserDaouserDao;publicvoidupdate(Integerid){MyUseruser=newMyUser();user.setUid(id);user.setUname("張三-testing");user.setUsex("女");userDao.updateUser(user);}publicMyUserquery(Integerid){MyUseruser=userDao.selectUserById(id);returnuser;}//正常情況@Transactional(rollbackFor=Exception.class)publicvoidtestSuccess()throwsException{Integerid=1;MyUseruser=query(id);System.out.println("原記錄:"+user);update(id);thrownewException("事務生效");}}
執行入口:
publicclassSpringMyBatisTest{publicstaticvoidmain(String[]args)throwsException{StringxmlPath="applicationContext.xml";ApplicationContextapplicationContext=newClassPathXmlApplicationContext(xmlPath);Louzaiuc=(Louzai)applicationContext.getBean("louzai");uc.testSuccess();}}
輸出:
16:44:38.267[main]DEBUGorg.springframework.beans.factory.support.DefaultListableBeanFactory-Creatingsharedinstanceofsingletonbean'org.springframework.transaction.interceptor.TransactionInterceptor#0'16:44:38.363[main]DEBUGorg.springframework.beans.factory.support.DefaultListableBeanFactory-Creatingsharedinstanceofsingletonbean'txManager'16:44:40.966[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-Creatingnewtransactionwithname[com.mybatis.controller.Louzai.testSuccess]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception16:44:40.968[main]DEBUGorg.springframework.jdbc.datasource.DriverManagerDataSource-CreatingnewJDBCDriverManagerConnectionto[jdbc:mysql://127.0.0.1:3306/java_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai]16:44:41.228[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-AcquiredConnection[com.mysql.cj.jdbc.ConnectionImpl@5b5caf08]forJDBCtransaction16:44:41.231[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-SwitchingJDBCConnection[com.mysql.cj.jdbc.ConnectionImpl@5b5caf08]tomanualcommit原記錄:MyUser(uid=1,uname=張三,usex=女)16:42:59.345[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-Initiatingtransactionrollback16:42:59.346[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-RollingbackJDBCtransactiononConnection[com.mysql.cj.jdbc.ConnectionImpl@70807224]16:42:59.354[main]DEBUGorg.springframework.jdbc.datasource.DataSourceTransactionManager-ReleasingJDBCConnection[com.mysql.cj.jdbc.ConnectionImpl@70807224]aftertransactionExceptioninthread"main"java.lang.Exception:事務生效atcom.mybatis.controller.Louzai.testSuccess(Louzai.java:34)//異常日誌省略...2. Spring 事務工作流程
為了方便大家能更好看懂後面的源碼,我先整體介紹一下源碼的執行流程,讓大家有一個整體的認識,否則容易被繞進去。
整個 Spring 事務源碼,其實分為 2 塊,我們會結合上面的示例,給大家進行講解。

第一塊是後置處理,我們在創建 Louzai Bean 的後置處理器中,裡面會做兩件事情:
獲取 Louzai 的切面方法:首先會拿到所有的切面信息,和 Louzai 的所有方法進行匹配,然後找到 Louzai 所有需要進行事務處理的方法,匹配成功的方法,還需要將事務屬性保存到緩存 attributeCache 中。
創建 AOP 代理對象:結合 Louzai 需要進行 AOP 的方法,選擇 Cglib 或 JDK,創建 AOP 代理對象。

第二塊是事務執行,整個邏輯比較複雜,我只選取 4 塊最核心的邏輯,分別為從緩存拿到事務屬性、創建並開啟事務、執行業務邏輯、提交或者回滾事務。
3. 源碼解讀
注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣!!!
上面的知識都不難,下面才是我們的重頭戲,讓你跟着樓仔,走一遍代碼流程。
3.1 代碼入口
這裡需要多跑幾次,把前面的 beanName 跳過去,只看 louzai。


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

進入 createBean(),調用 doCreateBean()。

進入 doCreateBean(),調用 initializeBean()。




如果看過我前面幾期系列源碼的同學,對這個入口應該會非常熟悉,其實就是用來創建代理對象。
3.2 創建代理對象
這裡是重點!敲黑板!!!

3.2.1 獲取切面列表
這裡有 2 個重要的方法,先執行 findCandidateAdvisors(),待會我們還會再返回 findEligibleAdvisors()。




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




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

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

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






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

再回到該小節開頭,我們拿到 louzai 的切面信息,去創建 AOP 代理對象。
3.2.2 創建 AOP 代理對象
創建 AOP 代理對象的邏輯,在上一篇文章(Spring AOP)講解過,我是通過 Cglib 創建,感興趣的同學可以關注公眾號「樓仔」,翻一下樓仔的歷史文章。
3.3 事務執行
回到業務邏輯,通過louzai 的 AOP 代理對象,開始執行主方法。

因為代理對象是 Cglib 方式創建,所以通過 Cglib 來執行。




這裡是重點!敲黑板!!!
下面的代碼是事務執行的核心邏輯 invokeWithinTransaction()。

protectedObjectinvokeWithinTransaction(Methodmethod,@NullableClass<?>targetClass,finalInvocationCallbackinvocation)throwsThrowable{//獲取我們的事務屬源對象TransactionAttributeSourcetas=getTransactionAttributeSource();//通過事務屬性源對象獲取到我們的事務屬性信息finalTransactionAttributetxAttr=(tas!=null?tas.getTransactionAttribute(method,targetClass):null);//獲取我們配置的事務管理器對象finalPlatformTransactionManagertm=determineTransactionManager(txAttr);//從tx屬性對象中獲取出標註了@Transactionl的方法描述符finalStringjoinpointIdentification=methodIdentification(method,targetClass,txAttr);//處理聲明式事務if(txAttr==null||!(tminstanceofCallbackPreferringPlatformTransactionManager)){//有沒有必要創建事務TransactionInfotxInfo=createTransactionIfNecessary(tm,txAttr,joinpointIdentification);ObjectretVal;try{//調用鈎子函數進行回調目標方法retVal=invocation.proceedWithInvocation();}catch(Throwableex){//拋出異常進行回滾處理completeTransactionAfterThrowing(txInfo,ex);throwex;}finally{//清空我們的線程變量中transactionInfo的值cleanupTransactionInfo(txInfo);}//提交事務commitTransactionAfterReturning(txInfo);returnretVal;}//編程式事務else{//這裡不是我們的重點,省略...}}3.3.1 獲取事務屬性
在 invokeWithinTransaction() 中,我們找到獲取事務屬性的入口。

從 attributeCache 獲取事務的緩存數據,緩存數據是在 「2.2.1 獲取切面列表」 中保存的。

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

下面是開啟事務的詳細邏輯,了解一下即可。
protectedvoiddoBegin(Objecttransaction,TransactionDefinitiondefinition){//強制轉化事務對象DataSourceTransactionObjecttxObject=(DataSourceTransactionObject)transaction;Connectioncon=null;try{//判斷事務對象沒有數據庫連接持有器if(!txObject.hasConnectionHolder()||txObject.getConnectionHolder().isSynchronizedWithTransaction()){//通過數據源獲取一個數據庫連接對象ConnectionnewCon=obtainDataSource().getConnection();if(logger.isDebugEnabled()){logger.debug("AcquiredConnection["+newCon+"]forJDBCtransaction");}//把我們的數據庫連接包裝成一個ConnectionHolder對象然後設置到我們的txObject對象中去txObject.setConnectionHolder(newConnectionHolder(newCon),true);}//標記當前的連接是一個同步事務txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con=txObject.getConnectionHolder().getConnection();//為當前的事務設置隔離級別IntegerpreviousIsolationLevel=DataSourceUtils.prepareConnectionForTransaction(con,definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);//關閉自動提交if(con.getAutoCommit()){txObject.setMustRestoreAutoCommit(true);if(logger.isDebugEnabled()){logger.debug("SwitchingJDBCConnection["+con+"]tomanualcommit");}con.setAutoCommit(false);}//判斷事務為只讀事務prepareTransactionalConnection(con,definition);//設置事務激活txObject.getConnectionHolder().setTransactionActive(true);//設置事務超時時間inttimeout=determineTimeout(definition);if(timeout!=TransactionDefinition.TIMEOUT_DEFAULT){txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}//綁定我們的數據源和連接到我們的同步管理器上把數據源作為key,數據庫連接作為value設置到線程變量中if(txObject.isNewConnectionHolder()){TransactionSynchronizationManager.bindResource(obtainDataSource(),txObject.getConnectionHolder());}}catch(Throwableex){if(txObject.isNewConnectionHolder()){//釋放數據庫連接DataSourceUtils.releaseConnection(con,obtainDataSource());txObject.setConnectionHolder(null,false);}thrownewCannotCreateTransactionException("CouldnotopenJDBCConnectionfortransaction",ex);}}
最後返回到 invokeWithinTransaction(),得到 txInfo 對象。

3.3.3 執行邏輯
還是在 invokeWithinTransaction() 中,開始執行業務邏輯。





進入到真正的業務邏輯。

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

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



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

到這裡,所有的流程結束。
4. 結語
我們再小節一下,文章先介紹了事務的使用示例,以及事務的執行流程。
之後再剖析了事務的源碼,分為 2 塊:
- EOF -
IntelliJ IDEA終於支持對Redis 的可視化窗口操作了,真香!
面試官:斷網了,還能 ping 通 127.0.0.1 嗎?
兩萬字!多線程硬核50問!
看完本文有收穫?請轉發分享給更多人
關注「ImportNew」,提升Java技能
點讚和在看就是最大的支持❤️