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

