spring聲明式事務(wù) @Transactional 不回滾的多種情況以及解決方案
本文是基于springboot完成測(cè)試測(cè)試代碼地址如下:
https://github.com/Dr-Water/springboot-action/tree/master/springboot-shiro
一、 spring 事務(wù)原理
一、Spring事務(wù)原理
在使用JDBC事務(wù)操作數(shù)據(jù)庫(kù)時(shí),流程如下:
//獲取連接 1.Connection con = DriverManager.getConnection() //開(kāi)啟事務(wù) 2.con.setAutoCommit(true/false); 3.執(zhí)行CRUD //提交事務(wù)/回滾事務(wù) 4. con.commit() / con.rollback(); //關(guān)閉連接 5. conn.close();
Spring本身并不提供事務(wù),而是對(duì)JDBC事務(wù)通過(guò)AOP做了封裝,隱藏了2和4的操作,簡(jiǎn)化了JDBC的應(yīng)用。
spring對(duì)JDBC事務(wù)的封裝,是通過(guò)AOP動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的,在調(diào)用目標(biāo)方法(也就是第3步)前后會(huì)通過(guò)代理類(lèi)來(lái)執(zhí)行事務(wù)的開(kāi)啟、提交或者回滾操作。
spring事務(wù)使用的兩個(gè)不可忽略點(diǎn):
注意關(guān)鍵詞 “動(dòng)態(tài)代理”,這意味著要生成一個(gè)代理類(lèi),那么我們就不能在一個(gè)類(lèi)內(nèi)直接調(diào)用事務(wù)方法,否則無(wú)法代理,而且該事務(wù)方法必須是public,如果定義成 protected、private 或者默認(rèn)可見(jiàn)性,則無(wú)法調(diào)用!
問(wèn)題一、@Transactional 應(yīng)該加到什么地方,如果加到Controller會(huì)回滾嗎?
@Transactional 最好加到service層,加到Controller層也是生效的,但是為了規(guī)范起見(jiàn),還是加到service層上。
下載代碼并啟動(dòng)難項(xiàng)目進(jìn)行驗(yàn)證:主要代碼如下:
Controller層代碼如下:
@Autowired
private TransactionalService transactionalService;
@Autowired
private UserDao userDao;
@Autowired
private JwtUserDao jwtUserDao;
/**
* 測(cè)試@Transactional 注解加到service層事務(wù)是否回滾
*/
@RequestMapping("/tx")
public void serviceTX(){
transactionalService.controllerTX();
}
/**
* 測(cè)試@Transactional 注解加到Controller層事務(wù)是否回滾
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/ctx2")
public void cTX2(){
userDao.update();
System.out.println(2/0);
jwtUserDao.update();
}
/**
* 測(cè)試@Transactional 注解加到Controller層事務(wù)是否回滾
* 這里在Controller層為了方便直接調(diào)用了dao層,在實(shí)際開(kāi)發(fā)中dao層即可在Controller層調(diào)用也可以在service層調(diào)用,
* 比如service層只是直接調(diào)用dao層一個(gè)方法,此外沒(méi)有任何操作,那么這時(shí)候完全不用寫(xiě)service層的方法,直接在Controller調(diào)用dao層即可,
* 當(dāng)然如果公司有規(guī)范,必須嚴(yán)格按照mvc的模式進(jìn)行開(kāi)發(fā),則另說(shuō)
*/
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/ctx2")
public void cTX2(){
userDao.update();
//手動(dòng)拋出一個(gè)RuntimeException
System.out.println(2/0);
jwtUserDao.update();
}
service層的主要代碼
@Autowired
private UserDao userDao;
@Autowired
private JwtUserDao jwtUserDao;
@Transactional(rollbackFor = Exception.class)
public void controllerTX(){
userDao.update();
//手動(dòng)拋出一個(gè)RuntimeException
System.out.println(2/0);
jwtUserDao.update();
}
dao層sql語(yǔ)句如下:
<update id="update">
UPDATE jwt_user SET username ='wangwuupdate' WHERE user_id= 2
</update>
<update id="update">
UPDATE user SET username ='zsupdate' WHERE id= 2
</update>
數(shù)據(jù)庫(kù)原始數(shù)據(jù):


瀏覽器中輸入:http://localhost:8081/tx/tx,由于本次使用測(cè)試代碼進(jìn)行統(tǒng)一的異常處理所以瀏覽器的返回?cái)?shù)據(jù)如下:

控制臺(tái)輸出如下:

查看數(shù)據(jù)庫(kù)中的數(shù)據(jù)并沒(méi)有被修改
瀏覽器中輸入:http://localhost:8081/tx/ctx2,


查看數(shù)據(jù)庫(kù)中的數(shù)據(jù)并沒(méi)有被修改
由此可以得出 :@Transactional 加到Controller層也是生效的,但是為了規(guī)范起見(jiàn),還是加到service層上。
問(wèn)題二、 @Transactional 注解中用不用加rollbackFor = Exception.class 這個(gè)屬性值
spring的api doc中有折磨一句描述:

紅框中的內(nèi)容如下:
rolling back on RuntimeException and Error but not on checked exceptions
大致意思就默認(rèn)情況下,當(dāng)程序發(fā)生 RuntimeException 和 Error 的這兩種異常的時(shí)候事務(wù)會(huì)回滾,但是如果發(fā)生了checkedExcetions ,如fileNotfundException 則不會(huì)回滾,所以 rollbackFor = Exception.class 這個(gè)一定要加!
驗(yàn)證如下:
瀏覽器輸入:http://localhost:8081/tx/ctx3
控制臺(tái)輸出如下:

這時(shí)候查看數(shù)據(jù)庫(kù)中的數(shù)據(jù)并沒(méi)有被修改
瀏覽器輸入:http://localhost:8081/tx/ctx4

這時(shí)候查看數(shù)據(jù)庫(kù)數(shù)據(jù)已經(jīng)被修改:


問(wèn)題三:事務(wù)調(diào)用嵌套問(wèn)題具體結(jié)果如下代碼:
/**
* 同類(lèi)中在方法a中調(diào)用b
* a沒(méi)有事務(wù),b有 ,異常發(fā)生在b中 不會(huì)回滾
*/
@RequestMapping("/a1")
public void a1(){
transactionalService.a1();
}
/**
* 同類(lèi)中在方法a中調(diào)用b
* a沒(méi)有事務(wù),b有 ,異常發(fā)生在a中 不會(huì)回滾
*/
@RequestMapping("/a2")
public void a2(){
transactionalService.a2();
}
/**
* 同類(lèi)中在方法a中調(diào)用b
* a有事務(wù),b沒(méi)有 ,異常發(fā)生在b中 會(huì)回滾
*/
@RequestMapping("/a3")
public void a3(){
transactionalService.a3();
}
/**
* 同類(lèi)中在方法a中調(diào)用b
* a有事務(wù),b沒(méi)有 ,異常發(fā)生在a中 會(huì)回滾
*/
@RequestMapping("/a4")
public void a4(){
transactionalService.a4();
}
/**
* 同類(lèi)中在方法a中調(diào)用b
* a有事務(wù),b也有 ,異常發(fā)生在b中 會(huì)回滾
*/
@RequestMapping("/a5")
public void a5(){
transactionalService.a5();
}
/**
* 同類(lèi)中在方法a中調(diào)用b
* a有事務(wù),b也有 ,異常發(fā)生在a中 會(huì)回滾
*/
@RequestMapping("/a6")
public void a6(){
transactionalService.a6();
}
/**
*a類(lèi)中調(diào)用b類(lèi)中的方法
* a中有事務(wù),b中也有 會(huì)回滾
*
*/
@RequestMapping("/b5")
public void b5(){
transactionalService.b5();
}
/**
*a類(lèi)中調(diào)用b類(lèi)中的方法
* a中有事務(wù),b中沒(méi)有 會(huì)回滾
*
*/
@RequestMapping("/b6")
public void b6(){
transactionalService.b6();
}
/**
*a類(lèi)中調(diào)用b類(lèi)中的方法
* a沒(méi)有事務(wù),b中有 不會(huì)回滾
*
*/
@RequestMapping("/b7")
public void b7(){
transactionalService.b7();
}
/**
*a類(lèi)中調(diào)用b類(lèi)中的方法
* a沒(méi)有事務(wù),b中沒(méi)有 不會(huì)回滾
*
*/
@RequestMapping("/b8")
public void b8(){
transactionalService.b8();
}
總結(jié):如果在a方法中調(diào)用b方法不管是不是a和b是不是在同一個(gè)類(lèi)中,只要a方法中沒(méi)有事務(wù),則發(fā)生異常的時(shí)候不會(huì)回滾,即:當(dāng)a無(wú)事務(wù)時(shí),則a和b均沒(méi)有事務(wù),當(dāng)a有事務(wù)時(shí),b如果有事務(wù),則b事務(wù)會(huì)加到a事務(wù)中,二者為同一事務(wù)!
四、總結(jié)
在springboot中默認(rèn)是開(kāi)啟事務(wù)的,在service層的方法加上@Transactional(rollbackFor = Exception.class) 注解即可實(shí)現(xiàn)事務(wù) 如果在方法a中調(diào)用方法b 如果要實(shí)現(xiàn)事務(wù),則只需要在方法上加上@Transactional(rollbackFor = Exception.class) 即可!
如果業(yè)務(wù)需要,一定要拋出checked異常的話,可以通過(guò)rollbackFor屬性指定異常類(lèi)型即可。有興趣的可以動(dòng)手驗(yàn)證一下,這里不再贅述。
五、 參考鏈接
Spring Boot中的事務(wù)管理
深入理解 Spring 之 SpringBoot 事務(wù)原理
聲明式事務(wù)不回滾@Transactional的避坑正確使用
Spring聲明式事務(wù)不回滾問(wèn)題
spring 事務(wù)應(yīng)用誤區(qū)總結(jié):那些導(dǎo)致事務(wù)不回滾的坑
你的Spring事務(wù)為什么不會(huì)自動(dòng)回滾,包含異常的分類(lèi)
Java異常之checked與unchecked
到此這篇關(guān)于spring聲明式事務(wù) @Transactional 不回滾的多種情況以及解決方案的文章就介紹到這了,更多相關(guān)spring @Transactional不回滾內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼)
這篇文章主要介紹了詳解Maven 搭建spring boot多模塊項(xiàng)目(附源碼),具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
Java中Double除保留后小數(shù)位的幾種方法(小結(jié))
這篇文章主要介紹了Java中Double保留后小數(shù)位的幾種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
JavaWeb項(xiàng)目中DLL文件動(dòng)態(tài)加載方法
在JavaWeb項(xiàng)目中,有時(shí)候我們需要在運(yùn)行時(shí)動(dòng)態(tài)加載DLL文件(在Windows中是DLL,在Linux中是SO文件),這通常用于實(shí)現(xiàn)一些特定的功能,比如調(diào)用本機(jī)代碼或者使用某些特定于操作系統(tǒng)的API,本文將介紹如何在JavaWeb項(xiàng)目中動(dòng)態(tài)加載DLL文件,需要的朋友可以參考下2024-12-12
Java Web實(shí)現(xiàn)文件下載和亂碼處理方法
文件上傳和下載是web開(kāi)發(fā)中常遇到的問(wèn)題。今天小編給大家分享下Java Web實(shí)現(xiàn)文件下載和亂碼處理方法的相關(guān)資料,需要的朋友可以參考下2016-10-10
SpringBoot使用TraceId進(jìn)行日志鏈路追蹤的實(shí)現(xiàn)步驟
有時(shí)候一個(gè)業(yè)務(wù)調(diào)用鏈場(chǎng)景,很長(zhǎng),調(diào)了各種各樣的方法,看日志的時(shí)候,各個(gè)接口的日志穿插,確實(shí)讓人頭大,所以為了解決這個(gè)問(wèn)題,本文給大家介紹了SpringBoot使用TraceId進(jìn)行日志鏈路追蹤的實(shí)現(xiàn)步驟,需要的朋友可以參考下2024-11-11
Java 中函數(shù) Function 的使用和定義示例小結(jié)
這篇文章主要介紹了Java 中函數(shù) Function 的使用和定義小結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07
Java中的CyclicBarrier循環(huán)柵欄深入解析
這篇文章主要介紹了Java中的CyclicBarrier循環(huán)柵欄深入解析,CycleBarrier 它就相當(dāng)于是一個(gè)柵欄,所有線程在到達(dá)柵欄后都需要等待其他線程,等所有線程都到達(dá)后,再一起通過(guò),需要的朋友可以參考下2023-12-12
Spring Boot利用Java Mail實(shí)現(xiàn)郵件發(fā)送
這篇文章主要為大家詳細(xì)介紹了Spring Boot利用Java Mail實(shí)現(xiàn)郵件發(fā)送,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
Java實(shí)現(xiàn)矩陣順時(shí)針旋轉(zhuǎn)90度的示例
今天小編就為大家分享一篇Java實(shí)現(xiàn)矩陣順時(shí)針旋轉(zhuǎn)90度的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01

