關(guān)于Spring?@Transactional事務(wù)傳播機制詳解
Spring事務(wù)傳播機制
1.什么是事務(wù)傳播機制?
舉個栗子,方法A是一個事務(wù)的方法,方法A執(zhí)行過程中調(diào)用了方法B,那么方法B有無事務(wù)以及方法B對事務(wù)的要求不同都會對方法A的事務(wù)具體執(zhí)行造成影響,同時方法A的事務(wù)對方法B的事務(wù)執(zhí)行也有影響,這種影響具體是什么就由兩個方法所定義的事務(wù)傳播類型所決定。
簡單說就是,我們方法調(diào)用通常是,一個方法調(diào)用另外一個,而不同方法可以有不同的事務(wù),所以傳播機制就是指在多個方法,事務(wù)要如何傳播。
2.Spring事務(wù)傳播類型Propagation介紹
一共有七種傳播類型
- Propagation.REQUIRED
- Propagation.SUPPORTS
- Propagation.MANDATORY
- Propagation.REQUIRED_NEW
- Propagation.NOT_SUPPORTED
- Propagation.NESTED
- Propagation.NEVER
本文從案例結(jié)合解釋一下不同傳播類型下多個@Transactional方法會發(fā)生什么?在遇到異常情況下,不同傳播機制會產(chǎn)生什么影響。
1. Propagation.REQUIRED
這是默認(rèn)的傳播機制,我們最常用的一種,也是@Transactional默認(rèn)的一種
如果當(dāng)前沒有事務(wù),則自己新建一個事務(wù),如果當(dāng)前存在事務(wù),則加入這個事務(wù)
// 示例1: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.REQUIRED) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
簡單來說就是,開啟一個事務(wù),上面的案例就是當(dāng)main方法如果沒開啟事務(wù),那么sub方法就會開啟,如果main方法已經(jīng)@Transactional開啟了事務(wù),sub方法就會加入外層方法的事務(wù),所以上面方法執(zhí)行在遇到異常時候會全部回滾
結(jié)果:
A、B、C全部無法插入。
// 示例2: public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.REQUIRED) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
結(jié)果:
A插入成功,BC開啟新的事務(wù),遇到異?;貪L,B、C無法插入
2. Propagation.SUPPORTS
當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方法執(zhí)行
// 示例3: public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.SUPPORTS) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
這個和REQUIRED很像,但是里層的sub方法事務(wù)取決于main方法,如果main方法有開啟那么里面的就和外層事務(wù)一起,如果發(fā)生異常全部回滾。
結(jié)果:
A、B插入成功,C無法插入因為發(fā)生異常
3. Propagation.MANDATORY
當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),如果當(dāng)前事務(wù)不存在,則拋出異常。
// 示例4: public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.MANDATORY) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
這種情形的執(zhí)行結(jié)果就是insertA存儲成功,而insertB和insertC沒有存儲。b和c沒有存儲,并不是事務(wù)回滾的原因,而是因為main方法沒有聲明事務(wù),在去執(zhí)行sub方法時就直接拋出事務(wù)要求的異常(如果當(dāng)前事務(wù)不存在,則拋出異常),所以sub方法里的內(nèi)容就完全沒有執(zhí)行。
結(jié)果:
A插入成功,B、C無法插入,方法拋出異常
那么當(dāng)main方法有事務(wù)的情況下
// 示例5: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.MANDATORY) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
結(jié)果:
A、B、C全部無法插入,A、B回滾
4. Propagation.REQUIRED_NEW
創(chuàng)建一個新事務(wù),如果存在當(dāng)前事務(wù),則掛起該事務(wù)。
// 示例5: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 throw RuntimeException; //發(fā)生異常拋出 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.REQUIRES_NEW) public void sub(){ insertB(); //插入B insertC(); //調(diào)用C
因為sub方法會開啟一個新的事務(wù),所以main方法拋出的異常并不會影響sub方法的提交
結(jié)果:
A插入失敗,B、C能插入成功
5. Propagation.NOT_SUPPORTED
始終以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)
// 示例6: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
示例6因為當(dāng)main方法有事務(wù)的時候,就會掛起當(dāng)前事務(wù)即main以事務(wù)運行,sub不以事務(wù)運行
所以最終結(jié)果:
A因為sub拋出異常事務(wù)回滾,插入失敗,B因為不以事務(wù)運行插入成功,C因為遇到異常,后續(xù)不會執(zhí)行,所以插入失敗。
// 示例7: public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
示例7這種情況就是所有方法都不會以事務(wù)運行,A、B均能插入成功,C無法插入
6. Propagation.NEVER
不使用事務(wù),如果當(dāng)前事務(wù)存在,則拋出異常
// 示例7: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.NEVER) public void sub(){ insertB(); //插入B insertC(); //調(diào)用C
sub因為是Never所以是不會執(zhí)行直接拋出錯誤,所以main的事務(wù)遇到異常直接回滾,所以A回滾無法插入,B、C不會插入。
7. Propagation.NESTED
如果當(dāng)前事務(wù)存在,則在嵌套(父子)事務(wù)中執(zhí)行,否則REQUIRED的操作一樣(開啟一個事務(wù))
// 示例7: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A service.sub(); // 調(diào)用其他方法 throw RuntimeException; //發(fā)生異常拋出 } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.NESTED) public void sub(){ insertB(); //插入B insertC(); //調(diào)用C
這個是最需要理解的一種傳播機制,要理清楚嵌套(父子)事務(wù),main的是父事務(wù),sub是子事務(wù),main發(fā)生異常全部都會回滾。
結(jié)果:
A、B、C全部回滾
// 示例8: @Transactional(propagation = Propagation.REQUIRED) public void main(){ insertA(); // 插入A try { service.sub(); // 調(diào)用其他方法 } catch (Exception e) { } insertD(); } // 兩個Service中調(diào)用,如果同一個要注意不能用this調(diào)用,事務(wù)不會起作用 @Transactional(propagation = Propagation.NESTED) public void sub(){ insertB(); //插入B throw RuntimeException; //發(fā)生異常拋出 insertC(); //調(diào)用C
示例8,子事務(wù)發(fā)生異常拋出,但父事務(wù)catch掉了,那么這個時候main方法就相當(dāng)于正常執(zhí)行沒有發(fā)生異常,那么就只有子事務(wù)回滾。
結(jié)果:
A、D插入成功,B、C插入失敗
- REQUIRED
- 內(nèi)外同一個事務(wù),任何一個地方拋出異常全部一起回滾。
- REQUIRED_NEW
- 內(nèi)部開啟一個新的事務(wù),外部事務(wù)回滾并不會影響內(nèi)部的事務(wù),而如果內(nèi)部事務(wù)拋出被catch也不會影響外部事務(wù)。
怎么樣快速記憶,七個分四組,221這樣記,兩個一對互相類似
組 | 傳播類型 | 含義 |
---|---|---|
group1 | Propagation.REQUIRED | 如果當(dāng)前已有事務(wù)則加入當(dāng)前事務(wù),否則開啟新的事務(wù) |
group1 | Propagation.REQUIRED_NEW | 無論當(dāng)前是否有事務(wù)都開啟新的事務(wù) |
group2 | Propagation.SUPPORTED | 如果當(dāng)前事務(wù)存在就加入事務(wù),否則以非事務(wù)運行 |
group2 | Propagation.NOT_SUPPORTED | 始終以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù) |
group3 | Propagation.NEVER | 不使用事務(wù),如果當(dāng)前事務(wù)存在,則拋出異常 |
group3 | Propagation.MANDATORY | 當(dāng)前存在事務(wù),則加入當(dāng)前事務(wù),如果當(dāng)前事務(wù)不存在,則拋出異常。 |
group4 | Propagation.NESTED | 父子(嵌套)事務(wù),父回滾全回滾,子回滾不影響父事務(wù) |
3.具體案例
單純講案例比較枯燥,會覺得工作中什么情況會使用到呢,這邊就舉一個例子來講解一下。
在下單時候,我們最主要是寫入訂單、然后添加積分,最后記錄日志
@Service public class OrderServiceImpl implements OrderService{ @Transactional public void placeOrder(OrderDTO orderDTO){ try { pointService.addPoint(Point point); } catch (Exception e) { // 記錄錯誤信息 } //省略... } //省略... }
@Service public class PointServiceImpl implements PointService{ @Transactional(propagation = Propagation.NESTED) public void addPoint(Point point){ try { recordService.addRecord(Record record); } catch (Exception e) { //省略... } //省略... } //省略... }
@Service public class RecordServiceImpl implements RecordService{ @Transactional(propagation = Propagation.NOT_SUPPORTED) public void addRecord(Record record){ //省略... } //省略... }
下單的操作不會影響添加積分的操作,所以我們使用NESTED,下單只要成功,添加積分可以成功或失敗,失敗的話就錯誤信息后續(xù)補償。而記錄日志我們可以有也可以沒有,就可以設(shè)置為NOT_SUPPORTED不開啟事務(wù),使得事務(wù)的方法能盡可能的精簡,避免一個很大的事務(wù)方法。
總結(jié)
本文講解了Spring事務(wù)的七種傳播機制,我們可以根據(jù)具體的類型,具體設(shè)置,避免事務(wù)的方法過于長,一個事務(wù)里面調(diào)用的庫表越多,就越有可能造成死鎖,所以我們要根據(jù)具體的需要拆分使用。
以上就是關(guān)于Spring @Transactional事務(wù)傳播機制詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring @Transactional事務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis配置mapper-locations位置的三種方式小結(jié)
這篇文章主要給大家介紹了關(guān)于mybatis配置mapper-locations位置的三種方式,Mybatis-Plus的初衷是為了簡化開發(fā),而不建議開發(fā)者自己寫SQL語句的,但是有時客戶需求比較復(fù)雜,需要的朋友可以參考下2023-08-08Java定時器例子_動力節(jié)點Java學(xué)院整理
本文給大家分享了java定時器例子,非常不錯,具有參考借鑒價值,需要的的朋友參考下吧2017-05-05詳解Java的Hibernat框架中的Map映射與SortedMap映射
這篇文章主要介紹了Java的Hibernat框架中的Map映射與SortedMap映射,Hibernat是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12Java實現(xiàn)創(chuàng)建Zip壓縮包并寫入文件
這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)創(chuàng)建Zip壓縮包并寫入文件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01java鏈表應(yīng)用--基于鏈表實現(xiàn)隊列詳解(尾指針操作)
這篇文章主要介紹了java鏈表應(yīng)用--基于鏈表實現(xiàn)隊列,結(jié)合實例形式分析了java基于鏈表實現(xiàn)隊列尾指針相關(guān)操作使用技巧,需要的朋友可以參考下2020-03-03關(guān)于遠(yuǎn)程調(diào)用RestTemplate的使用避坑指南
這篇文章主要介紹了關(guān)于遠(yuǎn)程調(diào)用RestTemplate的使用避坑指南,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10