深入理解 Spring 事務(wù)原理
參考:
https://mp.weixin.qq.com/s/HHLGfRuD_Ynv7GhDsy7rKA
一失晴、事務(wù)的基本原理
Spring事務(wù)的本質(zhì)其實(shí)就是數(shù)據(jù)庫對事務(wù)的支持绢慢,沒有數(shù)據(jù)庫的事務(wù)支持,
spring是無法提供事務(wù)功能的陷揪。對于純JDBC操作數(shù)據(jù)庫,想要用到事務(wù),可以按照以下步驟進(jìn)行:
獲取連接
Connection con = DriverManager.getConnection()
開啟事務(wù)
con.setAutoCommit(true/false);
執(zhí)行
CRUD
提交事務(wù)
/回滾事務(wù)
con.commit() / con.rollback();
關(guān)閉連接
conn.close();
使用Spring的事務(wù)管理功能后,我們可以不再寫步驟2和4的代碼九秀,而是由Spirng自動完成。那么Spring是如何在我們書寫的CRUD之前和之后開啟事務(wù)和關(guān)閉事務(wù)的呢粘我?解決這個(gè)問題鼓蜒,也就可以從整體上理解Spring的事務(wù)管理實(shí)現(xiàn)原理了。下面簡單地介紹下涂滴,注解方式為例子友酱。
配置文件開啟注解驅(qū)動,在相關(guān)的類和方法上通過注解@Transactional標(biāo)識。
spring在啟動的時(shí)候會去解析生成相關(guān)的bean夺姑,這時(shí)候會查看擁有相關(guān)注解的類和方法鳍征,并且為這些類和方法生成代理,并根據(jù)@Transaction的相關(guān)參數(shù)進(jìn)行相關(guān)配置注入或详,這樣就在代理中為我們把相關(guān)的事務(wù)處理掉了(開啟正常提交事務(wù)系羞,異常回滾事務(wù))霸琴。真正的數(shù)據(jù)庫層的事務(wù)提交和回滾是通過binlog或者redo log實(shí)現(xiàn)的椒振。
二、Spring事務(wù)的傳播屬性
所謂spring事務(wù)的傳播屬性梧乘,就是定義在存在多個(gè)事務(wù)同時(shí)存在的時(shí)候澎迎,spring應(yīng)該如何處理這些事務(wù)的行為。這些屬性在TransactionDefinition中定義选调,具體常量的解釋見下表:
三夹供、數(shù)據(jù)庫隔離級別
臟讀:一事務(wù)對數(shù)據(jù)進(jìn)行了增刪改,但未提交仁堪,另一事務(wù)可以讀取到未提交的數(shù)據(jù)哮洽。如果第一個(gè)事務(wù)這時(shí)候回滾了,那么第二個(gè)事務(wù)就讀到了臟數(shù)據(jù)弦聂。
不可重復(fù)讀:一個(gè)事務(wù)中發(fā)生了兩次讀操作鸟辅,第一次讀操作和第二次操作之間,另外一個(gè)事務(wù)對數(shù)據(jù)進(jìn)行了修改莺葫,這時(shí)候兩次讀取的數(shù)據(jù)是不一致的匪凉。
幻讀:第一個(gè)事務(wù)對一定范圍的數(shù)據(jù)進(jìn)行批量修改,第二個(gè)事務(wù)在這個(gè)范圍增加一條數(shù)據(jù)徙融,這時(shí)候第一個(gè)事務(wù)就會丟失對新增數(shù)據(jù)的修改洒缀。
總結(jié):
隔離級別越高,越能保證數(shù)據(jù)的完整性和一致性欺冀,但是對并發(fā)性能的影響也越大树绩。
大多數(shù)的數(shù)據(jù)庫默認(rèn)隔離級別為 Read Commited,比如 SqlServer隐轩、Oracle
少數(shù)數(shù)據(jù)庫默認(rèn)隔離級別為:Repeatable Read 比如: MySQL InnoDB饺饭。
四、Spring中的隔離級別
?五职车、事物的嵌套
通過上面的理論知識的鋪墊瘫俊,我們大致知道了數(shù)據(jù)庫事務(wù)和spring事務(wù)的一些屬性和特點(diǎn),接下來我們通過分析一些嵌套事務(wù)的場景悴灵,來深入理解spring事務(wù)傳播的機(jī)制扛芽。
假設(shè)外層事務(wù) Service A 的 Method A() 調(diào)用 內(nèi)層Service B 的 Method B()
PROPAGATION_REQUIRED(spring 默認(rèn))
如果ServiceB.methodB() 的事務(wù)級別定義為 PROPAGATION_REQUIRED,那么執(zhí)行 ServiceA.methodA() 的時(shí)候spring已經(jīng)起了事務(wù)积瞒,這時(shí)調(diào)用 ServiceB.methodB()川尖,ServiceB.methodB() 看到自己已經(jīng)運(yùn)行在 ServiceA.methodA() 的事務(wù)內(nèi)部,就不再起新的事務(wù)茫孔。
假如 ServiceB.methodB() 運(yùn)行的時(shí)候發(fā)現(xiàn)自己沒有在事務(wù)中叮喳,他就會為自己分配一個(gè)事務(wù)被芳。
這樣,在 ServiceA.methodA() 或者在 ServiceB.methodB() 內(nèi)的任何地方出現(xiàn)異常馍悟,事務(wù)都會被回滾畔濒。
PROPAGATION_REQUIRES_NEW
比如我們設(shè)計(jì) ServiceA.methodA() 的事務(wù)級別為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務(wù)級別為 PROPAGATION_REQUIRES_NEW锣咒。
那么當(dāng)執(zhí)行到 ServiceB.methodB() 的時(shí)候侵状,ServiceA.methodA() 所在的事務(wù)就會掛起,ServiceB.methodB() 會起一個(gè)新的事務(wù)毅整,等待 ServiceB.methodB() 的事務(wù)完成以后壹将,它才繼續(xù)執(zhí)行。
他與 PROPAGATION_REQUIRED 的事務(wù)區(qū)別在于事務(wù)的回滾程度了毛嫉。因?yàn)?ServiceB.methodB() 是新起一個(gè)事務(wù),那么就是存在兩個(gè)不同的事務(wù)妇菱。如果 ServiceB.methodB() 已經(jīng)提交承粤,那么 ServiceA.methodA() 失敗回滾,ServiceB.methodB() 是不會回滾的闯团。如果 ServiceB.methodB() 失敗回滾辛臊,如果他拋出的異常被 ServiceA.methodA() 捕獲,ServiceA.methodA() 事務(wù)仍然可能提交(主要看B拋出的異常是不是A會回滾的異常)房交。
PROPAGATION_SUPPORTS
假設(shè)ServiceB.methodB() 的事務(wù)級別為 PROPAGATION_SUPPORTS彻舰,那么當(dāng)執(zhí)行到ServiceB.methodB()時(shí),如果發(fā)現(xiàn)ServiceA.methodA()已經(jīng)開啟了一個(gè)事務(wù)候味,則加入當(dāng)前的事務(wù)刃唤,如果發(fā)現(xiàn)ServiceA.methodA()沒有開啟事務(wù),則自己也不開啟事務(wù)白群。這種時(shí)候尚胞,內(nèi)部方法的事務(wù)性完全依賴于最外層的事務(wù)。
PROPAGATION_NESTED
現(xiàn)在的情況就變得比較復(fù)雜了, ServiceB.methodB() 的事務(wù)屬性被配置為 PROPAGATION_NESTED, 此時(shí)兩者之間又將如何協(xié)作呢?
ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(wù)(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint 而外部事務(wù)(即 ServiceA#methodA) 可以有以下兩種處理方式:
a帜慢、捕獲異常笼裳,執(zhí)行異常分支邏輯
<pre>
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如
ServiceC.methodC();
}
}
</pre>
這種方式也是嵌套事務(wù)最有價(jià)值的地方, 它起到了分支執(zhí)行的效果, 如果 ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 所以不會產(chǎn)生臟數(shù)據(jù)(相當(dāng)于此方法從未執(zhí)行過), 這種特性可以用在某些特殊的業(yè)務(wù)中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都沒有辦法做到這一點(diǎn)。
b粱玲、 外部事務(wù)回滾/提交 代碼不做任何修改, 那么如果內(nèi)部事務(wù)(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會如此), 外部事務(wù)(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback躬柬。
另外三種事務(wù)傳播屬性基本用不到,在此不做分析抽减。
六允青、總結(jié)
對于項(xiàng)目中需要使用到事務(wù)的地方,我建議開發(fā)者還是使用spring的TransactionCallback接口來實(shí)現(xiàn)事務(wù)胯甩,不要盲目使用spring事務(wù)注解昧廷,如果一定要使用注解堪嫂,那么一定要對spring事務(wù)的傳播機(jī)制和隔離級別有個(gè)詳細(xì)的了解,否則很可能發(fā)生意想不到的效果木柬。