一来累、Propagation (事務(wù)的傳播屬性)
Propagation-- key屬性確定代理應(yīng)該給哪個(gè)方法增加事務(wù)行為。這樣的屬性最重要的部份是傳播行為稻艰。有以下選項(xiàng)可供使用:
PROPAGATION_REQUIRED--支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就一個(gè)事務(wù)晶丘。是最常見的選擇。
PROPAGATION_SUPPORTS--支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù)浅浮,就以非事務(wù)方式執(zhí)行沫浆。
PROPAGATION_MANDATORY--支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù)滚秩,就拋出異常专执。
PROPAGATION_REQUIRES_NEW--新建事務(wù),如果當(dāng)前存在事務(wù)郁油,把當(dāng)前事務(wù)掛起本股,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行桐腌。
PROPAGATION_NOT_SUPPORTED--以非事務(wù)方式執(zhí)行操作拄显,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起哩掺,執(zhí)行當(dāng)前邏輯凿叠,結(jié)束后恢復(fù)上下文的事務(wù)。
PROPAGATION_NEVER--以非事務(wù)方式執(zhí)行嚼吞,如果當(dāng)前存在事務(wù)盒件,則拋出異常。
1: PROPAGATION_REQUIRED
加入當(dāng)前正要執(zhí)行的事務(wù)不在另外一個(gè)事務(wù)里舱禽,那么就起一個(gè)新的事務(wù)
比如說炒刁,ServiceB.methodB的事務(wù)級別定義為PROPAGATION_REQUIRED, 那么由于執(zhí)行ServiceA.methodA的時(shí)候,
ServiceA.methodA已經(jīng)起了事務(wù)誊稚,這時(shí)調(diào)用ServiceB.methodB翔始,ServiceB.methodB看到自己已經(jīng)運(yùn)行在ServiceA.methodA
的事務(wù)內(nèi)部戳寸,就不再起新的事務(wù)退腥。而假如ServiceA.methodA運(yùn)行的時(shí)候發(fā)現(xiàn)自己沒有在事務(wù)中,他就會為自己分配一個(gè)事務(wù)甘晤。
這樣疾瓮,在ServiceA.methodA或者在ServiceB.methodB內(nèi)的任何地方出現(xiàn)異常脖镀,事務(wù)都會被回滾。即使ServiceB.methodB的事務(wù)已經(jīng)被
提交狼电,但是ServiceA.methodA在接下來fail要回滾蜒灰,ServiceB.methodB也要回滾
2: PROPAGATION_SUPPORTS
如果當(dāng)前在事務(wù)中,即以事務(wù)的形式運(yùn)行肩碟,如果當(dāng)前不再一個(gè)事務(wù)中强窖,那么就以非事務(wù)的形式運(yùn)行
3: PROPAGATION_MANDATORY
必須在一個(gè)事務(wù)中運(yùn)行。也就是說削祈,他只能被一個(gè)父事務(wù)調(diào)用翅溺。否則,他就要拋出異常
4: PROPAGATION_REQUIRES_NEW 執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行未巫。
這個(gè)就比較繞口了窿撬。 比如我們設(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)镾erviceB.methodB是新起一個(gè)事務(wù)追城,那么就是存在
兩個(gè)不同的事務(wù)。如果ServiceB.methodB已經(jīng)提交燥撞,那么ServiceA.methodA失敗回滾座柱,ServiceB.methodB是不會回滾的。如果ServiceB.methodB失敗回滾物舒,
如果他拋出的異常被ServiceA.methodA捕獲色洞,ServiceA.methodA事務(wù)仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
當(dāng)前不支持事務(wù)冠胯。比如ServiceA.methodA的事務(wù)級別是PROPAGATION_REQUIRED 火诸,而ServiceB.methodB的事務(wù)級別是PROPAGATION_NOT_SUPPORTED ,
那么當(dāng)執(zhí)行到ServiceB.methodB時(shí)荠察,ServiceA.methodA的事務(wù)掛起置蜀,而他以非事務(wù)的狀態(tài)運(yùn)行完,再繼續(xù)ServiceA.methodA的事務(wù)悉盆。
6: PROPAGATION_NEVER
不能在事務(wù)中運(yùn)行盯荤。假設(shè)ServiceA.methodA的事務(wù)級別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務(wù)級別是PROPAGATION_NEVER 焕盟,
那么ServiceB.methodB就要拋出異常了廷雅。
7: PROPAGATION_NESTED
理解Nested的關(guān)鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區(qū)別是京髓,PROPAGATION_REQUIRES_NEW另起一個(gè)事務(wù),將會與他的父事務(wù)相互獨(dú)立商架,
而Nested的事務(wù)和他的父事務(wù)是相依的堰怨,他的提交是要等和他的父事務(wù)一塊提交的。也就是說蛇摸,如果父事務(wù)最后回滾备图,他也要回滾的。
而Nested事務(wù)的好處是他有一個(gè)savepoint。
ServiceA {
/**
- 事務(wù)屬性配置為 PROPAGATION_REQUIRED
*/
void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 級別
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如 ServiceC.methodC();
}
}
}
也就是說ServiceB.methodB失敗回滾揽涮,那么ServiceA.methodA也會回滾到savepoint點(diǎn)上抠藕,ServiceA.methodA可以選擇另外一個(gè)分支,比如ServiceC.methodC蒋困,繼續(xù)執(zhí)行盾似,來嘗試完成自己的事務(wù)。
但是這個(gè)事務(wù)并沒有在EJB標(biāo)準(zhǔn)中定義雪标。
那么什么是嵌套事務(wù)呢零院?很多人都不理解,我看過一些博客村刨,都是有些理解偏差告抄。
嵌套是子事務(wù)套在父事務(wù)中執(zhí)行,子事務(wù)是父事務(wù)的一部分嵌牺,在進(jìn)入子事務(wù)之前打洼,父事務(wù)建立一個(gè)回滾點(diǎn),叫save point逆粹,然后執(zhí)行子事務(wù)募疮,這個(gè)子事務(wù)的執(zhí)行也算是父事務(wù)的一部分,然后子事務(wù)執(zhí)行結(jié)束枯饿,父事務(wù)繼續(xù)執(zhí)行酝锅。重點(diǎn)就在于那個(gè)save point∩莘剑看幾個(gè)問題就明了了:
1)如果子事務(wù)回滾搔扁,會發(fā)生什么?
父事務(wù)會回滾到進(jìn)入子事務(wù)前建立的save point蟋字,然后嘗試其他的事務(wù)或者其他的業(yè)務(wù)邏輯稿蹲,父事務(wù)之前的操作不會受到影響,更不會自動回滾鹊奖。
2)如果父事務(wù)回滾苛聘,會發(fā)生什么?
父事務(wù)回滾忠聚,子事務(wù)也會跟著回滾设哗!為什么呢,因?yàn)楦甘聞?wù)結(jié)束之前两蟀,子事務(wù)是不會提交的网梢,我們說子事務(wù)是父事務(wù)的一部分,正是這個(gè)道理赂毯。那么:
3)事務(wù)的提交战虏,是什么情況拣宰?
是父事務(wù)先提交,然后子事務(wù)提交烦感,還是子事務(wù)先提交巡社,父事務(wù)再提交?答案是第二種情況手趣,還是那句話晌该,子事務(wù)是父事務(wù)的一部分,由父事務(wù)統(tǒng)一提交回懦。
Spring事務(wù)的隔離級別
ISOLATION_DEFAULT: 這是一個(gè)PlatfromTransactionManager默認(rèn)的隔離級別气笙,使用數(shù)據(jù)庫默認(rèn)的事務(wù)隔離級別.另外四個(gè)與JDBC的隔離級別相對應(yīng)
ISOLATION_READ_UNCOMMITTED: 這是事務(wù)最低的隔離級別,它充許令外一個(gè)事務(wù)可以看到這個(gè)事務(wù)未提交的數(shù)據(jù)怯晕。這種隔離級別會產(chǎn)生臟讀潜圃,不可重復(fù)讀和幻像讀。
ISOLATION_READ_COMMITTED: 保證一個(gè)事務(wù)修改的數(shù)據(jù)提交后才能被另外一個(gè)事務(wù)讀取舟茶。另外一個(gè)事務(wù)不能讀取該事務(wù)未提交的數(shù)據(jù)
ISOLATION_REPEATABLE_READ: 這種事務(wù)隔離級別可以防止臟讀谭期,不可重復(fù)讀。但是可能出現(xiàn)幻像讀吧凉。它除了保證一個(gè)事務(wù)不能讀取另一個(gè)事務(wù)未提交的數(shù)據(jù)外隧出,還保證了避免下面的情況產(chǎn)生(不可重復(fù)讀)。
ISOLATION_SERIALIZABLE 這是花費(fèi)最高代價(jià)但是最可靠的事務(wù)隔離級別阀捅。事務(wù)被處理為順序執(zhí)行胀瞪。除了防止臟讀,不可重復(fù)讀外饲鄙,還避免了幻像讀凄诞。
數(shù)據(jù)庫并發(fā)操作存在的異常情況:
更新丟失(Lost update): 兩個(gè)事務(wù)都同時(shí)更新一行數(shù)據(jù)但是第二個(gè)事務(wù)卻中途失敗退出導(dǎo)致對數(shù)據(jù)兩個(gè)修改都失效了這是系統(tǒng)沒有執(zhí) 行任何鎖操作因此并發(fā)事務(wù)并沒有被隔離開來。
臟讀热碳丁(Dirty Reads): 一個(gè)事務(wù)開始讀取 了某行數(shù)據(jù)但是另外一個(gè)事務(wù)已經(jīng)更新了此數(shù)據(jù)但沒有能夠及時(shí)提交帆谍。這是相當(dāng)危險(xiǎn)很可能所有操作都被回滾。
不可重復(fù)讀戎嵩邸(Non-repeatable Reads): 一 個(gè)事務(wù)對同一行數(shù)據(jù)重復(fù)讀取兩次但是卻得到了不同結(jié)果汛蝙。例如在兩次讀取中途有另外一個(gè)事務(wù)對該行數(shù)據(jù)進(jìn)行了修改并提交。
兩次更新問題(Second lost updates problem): 無法重復(fù)讀取特例朴肺,有兩個(gè)并發(fā)事務(wù)同時(shí)讀取同一行數(shù)據(jù)然后其中一個(gè)對它進(jìn)行修改提交而另一個(gè)也進(jìn)行了修改提交這就會造成 第一次寫操作失效窖剑。
-
幻讀(Phantom Reads): 也稱為幻像(幻影)。事務(wù)在操作過程中進(jìn)行兩次查詢戈稿,第二次查詢結(jié)果包含了第一次查詢中未出現(xiàn)的數(shù)據(jù)(這里并不要求兩次查詢SQL語句相同)這是因?yàn)樵趦纱尾樵冞^程中有 另外一個(gè)事務(wù)插入數(shù)據(jù)造成的苛吱。
為了避免上面出現(xiàn)幾種情況在標(biāo)準(zhǔn)SQL規(guī)范中定義了4個(gè)事務(wù)隔離級別,不同隔離級別對事務(wù)處理不同 器瘪。
1.未授權(quán)讀却浯ⅰ(Read Uncommitted): 也稱 未提交讀。允許臟讀取但不允許更新丟失橡疼,如果一個(gè)事務(wù)已經(jīng)開始寫數(shù)據(jù)則另外一個(gè)數(shù)據(jù)則不允許同時(shí)進(jìn)行寫操作但允許其他事務(wù)讀此行數(shù)據(jù)援所。該隔離級別可以通過 “排他寫鎖”實(shí)現(xiàn)。事務(wù)隔離的最低級別欣除,僅可保證不讀取物理損壞的數(shù)據(jù)住拭。與READ COMMITTED 隔離級相反,它允許讀取已經(jīng)被其它用戶修改但尚未提交確定的數(shù)據(jù)历帚。
授權(quán)讀忍显馈(Read Committed): 也稱提交 讀。允許不可重復(fù)讀取但不允許臟讀取挽牢。這可以通過“瞬間共享讀鎖”和“排他寫鎖”實(shí)現(xiàn)谱煤,讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù),但是未提交寫事務(wù)將 會禁止其他事務(wù)訪問該行禽拔。SQL Server 默認(rèn)的級別刘离。在此隔離級下,SELECT 命令不會返回尚未提交(Committed) 的數(shù)據(jù)睹栖,也不能返回臟數(shù)據(jù)硫惕。
可重復(fù)讀取(Repeatable Read): 禁止 不可重復(fù)讀取和臟讀取野来。但是有時(shí)可能出現(xiàn)幻影數(shù)據(jù)恼除,這可以通過“共享讀鎖”和“排他寫鎖”實(shí)現(xiàn),讀取數(shù)據(jù)事務(wù)將會禁止寫事務(wù)(但允許讀事務(wù))曼氛,寫事務(wù)則禁 止任何其他事務(wù)豁辉。在此隔離級下,用SELECT 命令讀取的數(shù)據(jù)在整個(gè)命令執(zhí)行過程中不會被更改搪锣。此選項(xiàng)會影響系統(tǒng)的效能秋忙,非必要情況最好不用此隔離級。
串行(Serializable): 也稱可串行讀构舟。提 供嚴(yán)格的事務(wù)隔離灰追,它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行狗超,但不能并發(fā)執(zhí)行弹澎。如果僅僅通過“行級鎖”是無法實(shí)現(xiàn)事務(wù)序列化的,必須通過其他機(jī) 制保證新插入的數(shù)據(jù)不會被剛執(zhí)行查詢操作事務(wù)訪問到努咐。事務(wù)隔離的最高級別苦蒿,事務(wù)之間完全隔離。如果事務(wù)在可串行讀隔離級別上運(yùn)行渗稍,則可以保證任何并發(fā)重疊 事務(wù)均是串行的佩迟。
隔離級別 更新丟失 臟讀取 重復(fù)讀取 幻讀
未授權(quán)讀取 N Y Y Y
授權(quán)讀取 N N Y Y
可重復(fù) 讀取 N N N Y
串行 N N N N
所以最安全的团滥,是Serializable,但是伴隨而來也是高昂的性能開銷报强。
另外灸姊,事務(wù)常用的兩個(gè)屬性:readonly和timeout
一個(gè)是設(shè)置事務(wù)為只讀以提升性能。
另一個(gè)是設(shè)置事務(wù)的超時(shí)時(shí)間秉溉,一般用于防止大事務(wù)的發(fā)生力惯。還是那句話,事務(wù)要盡可能的姓偎弧父晶!
最后引入一個(gè)問題:
一個(gè)邏輯操作需要檢查的條件有20條,能否為了減小事務(wù)而將檢查性的內(nèi)容放到事務(wù)之外呢弄跌?
很多系統(tǒng)都是在DAO的內(nèi)部開始啟動事務(wù)甲喝,然后進(jìn)行操作,最后提交或者回滾碟绑。這其中涉及到代碼設(shè)計(jì)的問題俺猿。小一些的系統(tǒng)可以采用這種方式來做,但是在一些比較大的系統(tǒng)格仲,
邏輯較為復(fù)雜的系統(tǒng)中押袍,勢必會將過多的業(yè)務(wù)邏輯嵌入到DAO中,導(dǎo)致DAO的復(fù)用性下降凯肋。所以這不是一個(gè)好的實(shí)踐谊惭。
來回答這個(gè)問題:能否為了縮小事務(wù),而將一些業(yè)務(wù)邏輯檢查放到事務(wù)外面侮东?答案是:對于核心的業(yè)務(wù)檢查邏輯圈盔,不能放到事務(wù)之外,而且必須要作為分布式下的并發(fā)控制悄雅!
一旦在事務(wù)之外做檢查驱敲,那么勢必會造成事務(wù)A已經(jīng)檢查過的數(shù)據(jù)被事務(wù)B所修改,導(dǎo)致事務(wù)A徒勞無功而且出現(xiàn)并發(fā)問題宽闲,直接導(dǎo)致業(yè)務(wù)控制失敗众眨。
所以,在分布式的高并發(fā)環(huán)境下容诬,對于核心業(yè)務(wù)邏輯的檢查娩梨,要采用加鎖機(jī)制。
比如事務(wù)開啟需要讀取一條數(shù)據(jù)進(jìn)行驗(yàn)證览徒,然后邏輯操作中需要對這條數(shù)據(jù)進(jìn)行修改狈定,最后提交。
這樣的一個(gè)過程习蓬,如果讀取并驗(yàn)證的代碼放到事務(wù)之外纽什,那么讀取的數(shù)據(jù)極有可能已經(jīng)被其他的事務(wù)修改措嵌,當(dāng)前事務(wù)一旦提交,又會重新覆蓋掉其他事務(wù)的數(shù)據(jù)芦缰,導(dǎo)致數(shù)據(jù)異常铅匹。
所以在進(jìn)入當(dāng)前事務(wù)的時(shí)候,必須要將這條數(shù)據(jù)鎖住饺藤,使用for update就是一個(gè)很好的在分布式環(huán)境下的控制手段。
一種好的實(shí)踐方式是使用編程式事務(wù)而非生命式流礁,尤其是在較為規(guī)模的項(xiàng)目中涕俗。對于事務(wù)的配置,在代碼量非常大的情況下神帅,將是一種折磨再姑,而且人肉的方式,絕對不能避免這種問題找御。
將DAO保持針對一張表的最基本操作元镀,然后業(yè)務(wù)邏輯的處理放入manager和service中進(jìn)行,同時(shí)使用編程式事務(wù)更精確的控制事務(wù)范圍霎桅。
特別注意的栖疑,對于事務(wù)內(nèi)部一些可能拋出異常的情況,捕獲要謹(jǐn)慎滔驶,不能隨便的catch Exception 導(dǎo)致事務(wù)的異常被吃掉而不能正秤龈铮回滾。