一、@EnableTransactionManagement工作原理
開啟Spring事務(wù)本質(zhì)上就是增加了一個(gè)Advisor,但我們使用@EnableTransactionManagement注解來開啟Spring事務(wù)是,該注解代理的功能就是向Spring容器中添加了兩個(gè)Bean:
AutoProxyRegistrar
ProxyTransactionManagementConfiguration
AutoProxyRegistrar主要的作用是向Spring容器中注冊(cè)了一個(gè)InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator繼承了AbstractAdvisorAutoProxyCreator,所以這個(gè)類的主要作用就是開啟自動(dòng)代理的作用,也就是一個(gè)BeanPostProcessor,會(huì)在初始化后步驟中去尋找Advisor類型的Bean,并判斷當(dāng)前某個(gè)Bean是否有匹配的Advisor,是否需要利用動(dòng)態(tài)代理產(chǎn)生一個(gè)代理對(duì)象。
ProxyTransactionManagementConfiguration是一個(gè)配置類,它又定義了另外三個(gè)bean:
BeanFactoryTransactionAttributeSourceAdvisor :一個(gè)Advisor
AnnotationTransactionAttributeSource :相當(dāng)于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut
TransactionInterceptor :相當(dāng)于BeanFactoryTransactionAttributeSourceAdvisor中的Advice
AnnotationTransactionAttributeSource就是用來判斷某個(gè)類上是否存在@Transactional注解,或者判斷某個(gè)方法上是否存在@Transactional注解的。
TransactionInterceptor就是代理邏輯,當(dāng)某個(gè)類中存在@Transactional注解時(shí),到時(shí)就產(chǎn)生一個(gè)代理對(duì)象作為Bean,代理對(duì)象在執(zhí)行某個(gè)方法時(shí),最終就會(huì)進(jìn)入到TransactionInterceptor的invoke()方法。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
二、Spring事務(wù)基本執(zhí)行原理
一個(gè)Bean在執(zhí)行Bean的創(chuàng)建生命周期時(shí),會(huì)經(jīng)過InfrastructureAdvisorAutoProxyCreator的初始化后的方法,會(huì)判斷當(dāng)前Bean對(duì)象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配邏輯為判斷該Bean的類上是否存在@Transactional注解,或者類中的某個(gè)方法上是否存在@Transactional注解,如果存在則表示該Bean需要進(jìn)行動(dòng)態(tài)代理產(chǎn)生一個(gè)代理對(duì)象作為Bean對(duì)象。
該代理對(duì)象在執(zhí)行某個(gè)方法時(shí),會(huì)再次判斷當(dāng)前執(zhí)行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配則執(zhí)行該Advisor中的TransactionInterceptor的invoke()方法,執(zhí)行基本流程為:
利用所配置的PlatformTransactionManager事務(wù)管理器新建一個(gè)數(shù)據(jù)庫連接
修改數(shù)據(jù)庫連接的autocommit為false
執(zhí)行MethodInvocation.proceed()方法,簡(jiǎn)單理解就是執(zhí)行業(yè)務(wù)方法,其中就會(huì)執(zhí)行sql
如果沒有拋異常,則提交
如果拋了異常,則回滾
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
三、Spring事務(wù)詳細(xì)執(zhí)行流程
Spring事務(wù)執(zhí)行流程圖:https://www.processon.com/view/link/5fab6edf1e0853569633cc06
四、Spring事務(wù)傳播機(jī)制
在開發(fā)過程中,經(jīng)常會(huì)出現(xiàn)一個(gè)方法調(diào)用另外一個(gè)方法,那么這里就涉及到了多種場(chǎng)景,比如a()調(diào)用b():
a()和b()方法中的所有sql需要在同一個(gè)事務(wù)中嗎?
a()和b()方法需要單獨(dú)的事務(wù)嗎?
a()需要在事務(wù)中執(zhí)行,b()還需要在事務(wù)中執(zhí)行嗎?
等等情況…
所以,這就要求Spring事務(wù)能支持上面各種場(chǎng)景,這就是Spring事務(wù)傳播機(jī)制的由來。那Spring事務(wù)傳播機(jī)制是如何實(shí)現(xiàn)的呢?
先來看上述幾種場(chǎng)景中的一種情況,a()在一個(gè)事務(wù)中執(zhí)行,調(diào)用b()方法時(shí)需要新開一個(gè)事務(wù)執(zhí)行:
首先,代理對(duì)象執(zhí)行a()方法前,先利用事務(wù)管理器新建一個(gè)數(shù)據(jù)庫連接a
將數(shù)據(jù)庫連接a的autocommit改為false
把數(shù)據(jù)庫連接a設(shè)置到ThreadLocal中
執(zhí)行a()方法中的sql
執(zhí)行a()方法過程中,調(diào)用了b()方法(注意用代理對(duì)象調(diào)用b()方法)
代理對(duì)象執(zhí)行b()方法前,判斷出來了當(dāng)前線程中已經(jīng)存在一個(gè)數(shù)據(jù)庫連接a了,表示當(dāng)前線程其實(shí)已經(jīng)擁有一個(gè)Spring事務(wù)了,則進(jìn)行掛起
掛起就是把ThreadLocal中的數(shù)據(jù)庫連接a從ThreadLocal中移除,并放入一個(gè)掛起資源對(duì)象中
掛起完成后,再次利用事務(wù)管理器新建一個(gè)數(shù)據(jù)庫連接b
將數(shù)據(jù)庫連接b的autocommit改為false
把數(shù)據(jù)庫連接b設(shè)置到ThreadLocal中
執(zhí)行b()方法中的sql
b()方法正常執(zhí)行完,則從ThreadLocal中拿到數(shù)據(jù)庫連接b進(jìn)行提交
提交之后會(huì)恢復(fù)所掛起的數(shù)據(jù)庫連接a,這里的恢復(fù),其實(shí)只是把在掛起資源對(duì)象中所保存的數(shù)據(jù)庫連接a再次設(shè)置到ThreadLocal中
a()方法正常執(zhí)行完,則從ThreadLocal中拿到數(shù)據(jù)庫連接a進(jìn)行提交
這個(gè)過程中最為核心的是:在執(zhí)行某個(gè)方法時(shí),判斷當(dāng)前是否已經(jīng)存在一個(gè)事務(wù),就是判斷當(dāng)前線程的ThreadLocal中是否存在一個(gè)數(shù)據(jù)庫連接對(duì)象,如果存在則表示已經(jīng)存在一個(gè)事務(wù)了。
五、Spring事務(wù)傳播機(jī)制分類
其中,以非事務(wù)方式運(yùn)行,表示以非Spring事務(wù)運(yùn)行,表示在執(zhí)行這個(gè)方法時(shí),Spring事務(wù)管理器不會(huì)去建立數(shù)據(jù)庫連接,執(zhí)行sql時(shí),由Mybatis或JdbcTemplate自己來建立數(shù)據(jù)庫連接來執(zhí)行sql。
案例分析
情況1
@Component publicclassUserService{ @Autowired privateUserServiceuserService; @Transactional publicvoidtest(){ //test方法中的sql userService.a(); } @Transactional publicvoida(){ //a方法中的sql } }
默認(rèn)情況下傳播機(jī)制為REQUIRED,表示當(dāng)前如果沒有事務(wù)則新建一個(gè)事務(wù),如果有事務(wù)則在當(dāng)前事務(wù)中執(zhí)行。
所以上面這種情況的執(zhí)行流程如下:
新建一個(gè)數(shù)據(jù)庫連接conn
設(shè)置conn的autocommit為false
執(zhí)行test方法中的sql
執(zhí)行a方法中的sql
執(zhí)行conn的commit()方法進(jìn)行提交
情況2
假如是這種情況:
@Component publicclassUserService{ @Autowired privateUserServiceuserService; @Transactional publicvoidtest(){ //test方法中的sql userService.a(); intresult=100/0; } @Transactional publicvoida(){ //a方法中的sql } }
所以上面這種情況的執(zhí)行流程如下:
新建一個(gè)數(shù)據(jù)庫連接conn
設(shè)置conn的autocommit為false
執(zhí)行test方法中的sql
執(zhí)行a方法中的sql
拋出異常
執(zhí)行conn的rollback()方法進(jìn)行回滾,所以兩個(gè)方法中的sql都會(huì)回滾掉
情況3
假如是這種情況:
@Component publicclassUserService{ @Autowired privateUserServiceuserService; @Transactional publicvoidtest(){ //test方法中的sql userService.a(); } @Transactional publicvoida(){ //a方法中的sql intresult=100/0; } }
所以上面這種情況的執(zhí)行流程如下:
新建一個(gè)數(shù)據(jù)庫連接conn
設(shè)置conn的autocommit為false
執(zhí)行test方法中的sql
執(zhí)行a方法中的sql
拋出異常
執(zhí)行conn的rollback()方法進(jìn)行回滾,所以兩個(gè)方法中的sql都會(huì)回滾掉
情況4
如果是這種情況:
@Component publicclassUserService{ @Autowired privateUserServiceuserService; @Transactional publicvoidtest(){ //test方法中的sql userService.a(); } @Transactional(propagation=Propagation.REQUIRES_NEW) publicvoida(){ //a方法中的sql intresult=100/0; } }
所以上面這種情況的執(zhí)行流程如下:
新建一個(gè)數(shù)據(jù)庫連接conn
設(shè)置conn的autocommit為false
執(zhí)行test方法中的sql
又新建一個(gè)數(shù)據(jù)庫連接conn2
執(zhí)行a方法中的sql
拋出異常
執(zhí)行conn2的rollback()方法進(jìn)行回滾
繼續(xù)拋異常,對(duì)于test()方法而言,它會(huì)接收到一個(gè)異常,然后拋出
執(zhí)行conn的rollback()方法進(jìn)行回滾,最終還是兩個(gè)方法中的sql都回滾了
六、Spring事務(wù)強(qiáng)制回滾
正常情況下,a()調(diào)用b()方法時(shí),如果b()方法拋了異常,但是在a()方法捕獲了,那么a()的事務(wù)還是會(huì)正常提交的,但是有的時(shí)候,我們捕獲異常可能僅僅只是不把異常信息返回給客戶端,而是為了返回一些更友好的錯(cuò)誤信息,而這個(gè)時(shí)候,我們還是希望事務(wù)能回滾的,那這個(gè)時(shí)候就得告訴Spring把當(dāng)前事務(wù)回滾掉,做法就是:
@Transactional publicvoidtest(){ //執(zhí)行sql try{ b(); }catch(Exceptione){ //構(gòu)造友好的錯(cuò)誤信息返回 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } publicvoidb()throwsException{ thrownewException(); }
七、TransactionSynchronization
Spring事務(wù)有可能會(huì)提交,回滾、掛起、恢復(fù),所以Spring事務(wù)提供了一種機(jī)制,可以讓程序員來監(jiān)聽當(dāng)前Spring事務(wù)所處于的狀態(tài)。
@Component publicclassUserService{ @Autowired privateJdbcTemplatejdbcTemplate; @Autowired privateUserServiceuserService; @Transactional publicvoidtest(){ TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){ @Override publicvoidsuspend(){ System.out.println("test被掛起了"); } @Override publicvoidresume(){ System.out.println("test被恢復(fù)了"); } @Override publicvoidbeforeCommit(booleanreadOnly){ System.out.println("test準(zhǔn)備要提交了"); } @Override publicvoidbeforeCompletion(){ System.out.println("test準(zhǔn)備要提交或回滾了"); } @Override publicvoidafterCommit(){ System.out.println("test提交成功了"); } @Override publicvoidafterCompletion(intstatus){ System.out.println("test提交或回滾成功了"); } }); jdbcTemplate.execute("insertintot1values(1,1,1,1,'1')"); System.out.println("test"); userService.a(); } @Transactional(propagation=Propagation.REQUIRES_NEW) publicvoida(){ TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){ @Override publicvoidsuspend(){ System.out.println("a被掛起了"); } @Override publicvoidresume(){ System.out.println("a被恢復(fù)了"); } @Override publicvoidbeforeCommit(booleanreadOnly){ System.out.println("a準(zhǔn)備要提交了"); } @Override publicvoidbeforeCompletion(){ System.out.println("a準(zhǔn)備要提交或回滾了"); } @Override publicvoidafterCommit(){ System.out.println("a提交成功了"); } @Override publicvoidafterCompletion(intstatus){ System.out.println("a提交或回滾成功了"); } }); jdbcTemplate.execute("insertintot1values(2,2,2,2,'2')"); System.out.println("a"); } }
審核編輯:湯梓紅
-
容器
+關(guān)注
關(guān)注
0文章
495瀏覽量
22061 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14343
原文標(biāo)題:淺談 Spring 事務(wù)底層原理
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論