javascript
spring手动控制事务开启_“上帝视角”图解Spring事务的传播机制原理
轉載:https://mp.weixin.qq.com/s/odP1DKgRtXsCcAKxwGahug
數據庫事務的“抓手”
數據庫的事務功能已經由數據庫自身實現,它留給用戶的就是三個指令:開啟事務、提交事務和回滾事務。
開啟事務一般是start transaction或begin transaction。提交事務通常都是commit。回滾事務通常都是rollback。
數據庫通常都有自動開啟事務和自動提交事務的開關,打開后,事務就會自動的開啟和提交或回滾。這樣用戶就無感知了。JDBC的事務“抓手”
JDBC實現了訪問數據庫的協議,可以認為是對一些指令的封裝,當然也包括這些事務指令。它們都被做到了java.sql.Connection這個接口里。
它代表到數據庫的一個連接,當這個連接建立好后,默認事務就已經打開了,而且默認情況下執行完一個SQL語句后事務也是自動提交的。
可以通過下面這個API進行檢測:
boolean getAutoCommit();
通常情況下,我們都不希望事務是一句一提交,而是要執行若干個SQL語句后一次性提交,此時我們就需要改變這種自動提交的行為。
可以通過下面這個API進行設置:
void setAutoCommit(boolean autoCommit);
當我們禁用掉自動提交之后,一定要記得自己手動提交事務,否則結果可想而知。當然,需要回滾的時候也要記得手動回滾。
可以通過下面這個API提交事務:
void commit();
可以通過下面這個API回滾事務:
void rollback();
這樣我們就可以在Java代碼級別來控制事務的行為了。同時我們也應該認識到,在Java代碼級別事務是和Connecton的實例對象綁在一起的。
換句話說,只有在同一個Connection對象上執行的SQL語句才會在同一個事務里。在不同的Connection對象上執行的SQL語句永遠不會在同一個事務里。
更精確地說,后者構成的是分布式事務,前者通常稱為本地事務。當然,分布式事務有屬于自己的解決方案,只是不能再使用本地事務了。備注:以上這些其實都是基本常識,只是現在的ORM框架太牛了,導致很多年輕的碼農都沒機會再接觸這些了。Spring的事務“抓手”
Spring通過使用@Transactional注解實現了聲明式事務,并且可以通過設置Propagation屬性來影響事務的傳播特性。
事務的傳播特性其實就是指,Service層的若干方法在互相調用交織在一起的時候,究竟哪些方法的代碼是在同一個事務里執行,哪些方法的代碼不是。
通過前面的分析可知,在同一個事務里執行的方法代碼背后必須使用的是同一個Connection對象,當事務切換時,必須要切換背后的Connection對象為對應的另一個。
因此,當執行流程進入/退出不同的方法時,Spring根據方法上注解的傳播特性,在背后對應的進行Connection對象的切換,也包括新建Connection對象,提交或回滾事務等。
我們知道,在寫Service層方法或Mapper層方法時,根本接觸不到Connection對象,所以它更不可能明目張膽的以參數的方式傳來傳去,只能在背地里暗箱操作。
由于這些互相交織的方法代碼最終都是在同一個線程里運行的,所以借助線程的ThreadLocal來實現背后的操作是最適合的。
只需在方法調用的入口/出口來新建/切換Connection對象,并提交/回滾Connection對象上的事務即可。Spring事務的傳播特性原理
Spring事務是通過代理來實現的,通常是通過CGLIB操作字節碼來生成子類,因為要動態加入開啟/提交事務的這些代碼。
下面通過一個例子來說明,有四個方法及其對應的傳播特性:
方法一,傳播特性為REQUIRED:
void method1();
方法二,傳播特性為REQUIRED:
void method2();
方法三,傳播特性為REQUIRES_NEW:
void method3();
方法四,傳播特性為REQUIRED:
void method4();
假設它們之間的調用關系是,在方法一里依次調用方法二三四:
void method1() {
method2();
method3();
method4();
}
可以使用下面這個圖來表示,圖01:
那么經過Spring生成代理后,會重寫每個方法,并在原來的每個方法前面開啟事務,方法后面提交/回滾事務。
等效的偽代碼如下:
beginTx();
void method1() {
beginTx();
method2();
commit/rollbackTx();
beginTx();
method3();
commit/rollbackTx();
beginTx();
method4();
commit/rollbackTx();
}
commit/rollbackTx();
可以使用下面這個圖來表示,圖02:
其中紅色的向上箭頭對應于beginTx()操作,藍色的向下箭頭對應于commit/rollbackTx()操作。
下面就來看看具體的執行過程:
一、執行方法一的開啟事務,圖03:
由于方法一要求有事務,此時線程中沒有事務,于是就開啟一個新的事務,即創建一個新的Connection對象并綁定到線程的ThreadLocal。
二、進入方法一開始執行,圖04:
三、執行方法二的開啟事務,圖05:
由于方法二要求有事務,此時線程中已經有事務了,因此直接參與/使用這個事務即可。
四、進入方法二開始執行,圖06:
五、方法二完畢執行提交事務,圖07:
由于該事務并非方法二新建的,它只是參與而已,所以它沒有資格提交事務,因此實際并不提交事務。
六、方法二結束后又回到方法一里執行,圖08:
七、執行方法三的開啟事務,圖09:
由于方法三要求新的事物,所以新建一個事務,并把當前的現有事務掛起,使當前線程使用新事務。
即新建一個Connection對象,把當前現有Connection對象與線程的ThreadLocal解綁,把新的Connection對象綁定到線程的ThreadLocal。
那原來的那個Connection對象呢?當然是存儲起來了,存儲的形式是依附于新的Connection對象,即和新的對象關聯一下。
八、進入方法三開始執行,圖10:
可以看到,原來的事務相當于暫存在新的事務里。注意,這種說法只是一個形象的比喻。
九、方法三完畢執行提交事務,圖11:
由于這個事務是個新建事務,即是方法三創建的而非參與的,所以有權提交,所以事務就真的提交了。
十、方法三結束后又回到方法一里執行,圖12:
方法三的事務完成了,但是它里面暫存了方法一的事務,于是把它重新恢復到線程里去。
十一、執行方法四的開啟事務,圖13:
由于方法四要求有事務,此時線程中已經有事務了,因此直接參與/使用這個事務即可。
十二、進入方法四開始執行,圖14:
十三、方法四完畢執行提交事務,圖15:
由于該事務并非方法四新建的,它只是參與而已,所以它沒有資格提交事務,因此實際并不提交事務。
十四、方法四結束后又回到方法一里執行,圖16:
十五、方法一完畢執行提交事務,圖17:
由于這個事務是個新建事務,即是方法一創建的而非參與的,所以有權提交,所以事務就真的提交了。
十六、所有事務完成,只剩一個空線程,圖18:
方法一的事務完成了,由于它里面沒有暫存其它事務,所以沒有事務了,因此最后只剩一個空線程。說明:這只是傳播特性的實現原理解說,所以比較簡單,實際代碼實現要考慮很多事情,因此會復雜很多。
推薦閱讀:
太好了!某騰花399¥買來史上最全MySQL數據庫視頻教程(2020Java面試必備底層)
Spring源碼實戰全集,資深架構師帶你搞懂Spring源碼底層從入門到入墳
阿里P9架構師120分鐘帶你掌握線程池,不在為線程而煩惱_
面試美團被JVM慘虐?阿里P9架構師用500分鐘把JVM從入門講到實戰#合集
全B站有史以來最全阿里、騰訊、字節、美團、谷歌算法面試題合集_
總結
以上是生活随笔為你收集整理的spring手动控制事务开启_“上帝视角”图解Spring事务的传播机制原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原理 快速邻近匹配_论文推荐 | 陈晓勇
- 下一篇: mvc ajax提交多选,javascr