Spring源碼的圖文詳解:Spring事務(wù)
一:概述及目錄
下?我會簡單介紹?下 Spring 事務(wù)的基礎(chǔ)知識,以及使??法,然后直接對源碼進(jìn)?拆解。
目錄:
二. 項?準(zhǔn)備
下?是 DB 數(shù)據(jù)和 DB 操作接?:
Id | uname | usex |
1 | 小王 | 男 |
2 | 小李 | 女 |
1 | 小趙 | 男 |
@Data public class MyUser { private int id; private String uname; private String usex; } 復(fù)制代碼
public interface UserDao { // select * from user_test where id = "#{id}" MyUser selectUserById(Integer uid); // update user_test set uname =#{uname},usex = #{usex} where id = #{id} int updateUser(MyUser user); } 復(fù)制代碼
基礎(chǔ)測試代碼,testSuccess() 是事務(wù)?效的情況:
@Service public class Model { @Autowired private UserDao userDao; public void update(Integer id) { MyUser user = new MyUser(); user.setId(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ù)?效"); } } 復(fù)制代碼
執(zhí)???:
public class SpringMyBatisTest { public static void main(String[] args) throws Exception { String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); Model uc = (Model) applicationContext.getBean("model"); uc.testSuccess(); } } 復(fù)制代碼
輸出:
三:Spring 事務(wù)?作流程
為了?便?家能更好看懂后?的源碼,我先整體介紹?下源碼的執(zhí)?流程,讓?家有?個整體的認(rèn)識,否則容易被 繞進(jìn)去。
整個 Spring 事務(wù)源碼,其實分為 2 塊,我們會結(jié)合上?的示例,給?家進(jìn)?講解。
第?塊是后置處理,我們在創(chuàng)建 Model Bean 的后置處理器中,??會做兩件事情:
獲取 Model 的切??法:?先會拿到所有的切?信息,和 Model 的所有?法進(jìn)?匹配,然后找到 Model 所有需 要進(jìn)?事務(wù)處理的?法,匹配成功的?法,還需要將事務(wù)屬性保存到緩存 attributeCache 中。
創(chuàng)建 AOP 代理對象:結(jié)合 Model 需要進(jìn)? AOP 的?法,選擇 Cglib 或 JDK,創(chuàng)建 AOP 代理對象。
第?塊是事務(wù)執(zhí)?,整個邏輯?較復(fù)雜,我只選取 4 塊最核?的邏輯,分別為從緩存拿到事務(wù)屬性、創(chuàng)建并開啟事 務(wù)、執(zhí)?業(yè)務(wù)邏輯、提交或者回滾事務(wù)。
四. 源碼解讀
注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不?樣?。?!
上?的知識都不難,下?才是我們的重頭戲,讓我們一起??遍代碼流程。
4.1 代碼??
這?需要多跑?次,把前?的 beanName 跳過去,只看 model。
進(jìn)? doGetBean(),進(jìn)?創(chuàng)建 Bean 的邏輯。
進(jìn)? createBean(),調(diào)? doCreateBean()。
進(jìn)? doCreateBean(),調(diào)? initializeBean()。
如果看過我前??期系列源碼的同學(xué),對這個??應(yīng)該會?常熟悉,其實就是?來創(chuàng)建代理對象。
4.2 創(chuàng)建代理對象
這?是重點!敲?板?。?!
先獲取 model 類的所有切?列表; 創(chuàng)建?個 AOP 的代理對象。
4.2.1 獲取切?列表
這?有 2 個重要的?法,先執(zhí)? findCandidateAdvisors(),待會我們還會再返回 findEligibleAdvisors()。
依次返回,重新來到 findEligibleAdvisors()。
進(jìn)? canApply(),開始匹配 model 的切?。
這?是重點!敲?板!?。?/strong> 這?只會匹配到 Model.testSuccess() ?法,我們直接進(jìn)?匹配邏輯。
如果匹配成功,還會把事務(wù)的屬性配置信息放? attributeCache 緩存。
我們依次返回到 getTransactionAttribute(),再看看放?緩存中的數(shù)據(jù)。
再回到該?節(jié)開頭,我們拿到 mdoel 的切?信息,去創(chuàng)建 AOP 代理對象。
4.2.2 創(chuàng)建 AOP 代理對象
創(chuàng)建 AOP 代理對象的邏輯,在上?篇?章【Spring源碼解析-Spring AOP】講解過,我是通過 Cglib 創(chuàng)建,感興趣的同學(xué)可以翻?下我的歷史?章。
4.3 事務(wù)執(zhí)?
回到業(yè)務(wù)邏輯,通過 model 的 AOP 代理對象,開始執(zhí)?主?法。
因為代理對象是 Cglib ?式創(chuàng)建,所以通過 Cglib 來執(zhí)?。
這?是重點!敲?板?。。?/strong>
下?的代碼是事務(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屬性對象中獲取出標(biāo)注了@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ù)進(jìn)?回調(diào)?標(biāo)?法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { //拋出異常進(jìn)?回滾處理 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { //清空我們的線程變量中transactionInfo的值 cleanupTransactionInfo(txInfo); } //提交事務(wù) commitTransactionAfterReturning(txInfo); return retVal; } //編程式事務(wù) else { // 這?不是我們的重點,省略... } } 復(fù)制代碼
4.3.1 獲取事務(wù)屬性
在 invokeWithinTransaction() 中,我們找到獲取事務(wù)屬性的??。
從 attributeCache 獲取事務(wù)的緩存數(shù)據(jù),緩存數(shù)據(jù)是在 “3.2.1 獲取切?列表” 中保存的。
4.3.2 創(chuàng)建事務(wù)
通過 doGetTransaction() 獲取事務(wù)。
protected Object doGetTransaction() { //創(chuàng)建?個數(shù)據(jù)源事務(wù)對象 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); //是否允許當(dāng)前事務(wù)設(shè)置保持點 txObject.setSavepointAllowed(isNestedTransactionAllowed()); /** * TransactionSynchronizationManager 事務(wù)同步管理器對象(該類中都是局部線程變量) * ?來保存當(dāng)前事務(wù)的信息,我們第?次從這?去線程變量中獲取 事務(wù)連接持有器對象 通過數(shù)據(jù)源為key 去獲取 * 由于第?次進(jìn)來開始事務(wù) 我們的事務(wù)同步管理器中沒有被存放.所以此時獲取出來的conHolder為null */ ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); txObject.setConnectionHolder(conHolder, false); //返回事務(wù)對象 return txObject; } 復(fù)制代碼
通過 startTransaction() 開啟事務(wù)。
下?是開啟事務(wù)的詳細(xì)邏輯,了解?下即可。
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); } //標(biāo)記當(dāng)前的連接是?個同步事務(wù) txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); //為當(dāng)前的事務(wù)設(shè)置隔離級別 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); 最后返回到 invokeWithinTransaction(),得到 txInfo 對象。 //關(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 } } 復(fù)制代碼
最后返回到 invokeWithinTransaction(),得到 txInfo 對象。
4.3.3 執(zhí)?邏輯
還是在 invokeWithinTransaction() 中,開始執(zhí)?業(yè)務(wù)邏輯。
進(jìn)?到真正的業(yè)務(wù)邏輯。
執(zhí)?完畢后拋出異常,依次返回,?后續(xù)的回滾事務(wù)邏輯。
4.3.4 回滾事務(wù)
還是在 invokeWithinTransaction() 中,進(jìn)?回滾事務(wù)的邏輯。
執(zhí)?回滾邏輯很簡單,我們只看如何判斷是否回滾。
如果拋出的異常類型,和事務(wù)定義的異常類型匹配,證明該異常需要捕獲。
之所以?遞歸,不僅需要判斷拋出異常的本身,還需要判斷它繼承的?類異常,滿?任意?個即可捕獲。
到這?,所有的流程結(jié)束。
五. 總要有總結(jié)
我們再?節(jié)?下,?章先介紹了事務(wù)的使?示例,以及事務(wù)的執(zhí)?流程。
之后再剖析了事務(wù)的源碼,分為 2 塊:
先匹配出 model 對象所有關(guān)于事務(wù)的切?列表,并將匹配成功的事務(wù)屬性保存到緩存; 從緩存取出事務(wù)屬性,然后創(chuàng)建、啟動事務(wù),執(zhí)?業(yè)務(wù)邏輯,最后提交或者回滾事務(wù)。