spring 中事務(wù)注解@Transactional與trycatch的使用
spring事務(wù)注解@Transactional與trycatch
在項目中 @service層中 我們會經(jīng)常在做一些增刪改操作的方法上看到 spring 的事務(wù)注解 @transaction 已知@transaction 是讓spring 幫我們實現(xiàn)事務(wù)的控制。
但是在項目中會經(jīng)常看到 有的方法中 會存在trycatch塊包括的方法上注解著@transaction
eg:
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { ..... e.printStackTrace();} // } return json; }
上述的方法執(zhí)行后可以看到事務(wù)并沒有執(zhí)行,接下來再看一個例子eg:
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { // 手動硬編碼開啟spring事務(wù)管理 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace();} // } return json; }
上述方法執(zhí)行后我們可以看到事務(wù)最后執(zhí)行了,但實際上 事務(wù) 執(zhí)行只是因為手動硬編碼開啟spring事務(wù)管理起了作用 而方法上的注解并沒有起作用
接下來再看一個例子eg
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { throw new RuntimeException(); } // } return json; }
上述方法執(zhí)行后我們可以看到事務(wù)是執(zhí)行了的,但這里有個小細(xì)節(jié):@Transactional不做任何配置 默認(rèn)是對拋出的unchecked異?;貪L,checked異常不會回滾,為了讓所有異常都會讓事務(wù)啟動可以將 @Transactional配置為 @Transactional(rollbackFor = Exception.class)
解釋:
spring的事務(wù)邊界是在調(diào)用業(yè)務(wù)方法之前開始的,業(yè)務(wù)方法執(zhí)行完畢之后來執(zhí)行commit or rollback(spring默認(rèn)取決于是否拋出runtime異常).
如果拋出runtime exception 并在你的業(yè)務(wù)方法中沒有catch到的話,事務(wù)會回滾。
一般不需要在業(yè)務(wù)方法中catch異常,如果非要catch,在做完你想做的工作后(比如關(guān)閉文件等)一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產(chǎn)生臟數(shù)據(jù).所以你的catch代碼是畫蛇添足。
@Transactional回滾問題(try catch、嵌套)
Spring 事務(wù)注解 @Transactional 本來可以保證原子性,如果事務(wù)內(nèi)有報錯的話,整個事務(wù)可以保證回滾,但是加上try catch或者事務(wù)嵌套,可能會導(dǎo)致事務(wù)回滾失敗。測試一波。
準(zhǔn)備
建兩張表,模擬兩個數(shù)據(jù)操作
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `age` smallint(3) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
測試
根據(jù)排列組合原理,我們進(jìn)行四種測試:1、無try catch、無嵌套;2、有try catch、無嵌套;3、無try catch、有嵌套;4、都有。
最簡單測試
如果我們單純@Transactional,事務(wù)可以正?;貪L嗎?
@GetMapping("/saveNormal0") @Transactional public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new RuntimeException(); }
如果事務(wù)內(nèi)報了RuntimeException錯誤,事務(wù)可以回滾。
@GetMapping("/saveNormal0") @Transactional public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }
如果事務(wù)內(nèi)報了Exception錯誤(非RuntimeException錯誤),事務(wù)不可以回滾。
@GetMapping("/saveNormal0") @Transactional( rollbackFor = Exception.class) public void saveNormal0() throws Exception { int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }
如果是Exception錯誤(非RuntimeException),加上 rollbackFor = Exception.class 參數(shù)也可以實現(xiàn)回滾。
結(jié)論一:對于@Transactional可以保證RuntimeException錯誤的回滾,如果想保證非RuntimeException錯誤的回滾,需要加上rollbackFor = Exception.class 參數(shù)。
try catch 影響
經(jīng)過博主多種情況測試,發(fā)現(xiàn)try catch對回滾這個事本身沒有什么影響,結(jié)論一照樣成立。try catch只是對異常是否可以被@Transactional 感知 到有影響。如果錯誤拋到切面可以感知到的地步,那就可以起作用。
@GetMapping("/saveTryCatch") @Transactional( rollbackFor = Exception.class) public void saveTryCatch() throws Exception{ try{ int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }catch (Exception e){ throw e; } }
比如上面一段代碼就回滾了。
@GetMapping("/saveTryCatch") @Transactional( rollbackFor = Exception.class) public void saveTryCatch() throws Exception{ try{ int age = random.nextInt(100); User user = new User().setAge(age).setName("name:"+age); userService.save(user); throw new Exception(); }catch (Exception e){ } }
然而,將catch中的錯誤不繼續(xù)網(wǎng)上拋,切面無法感知到錯誤,無法進(jìn)行處理,那么事務(wù)就無法回滾了。
結(jié)論二:try catch只是對異常是否可以被@Transactional 感知 到有影響。如果錯誤拋到切面可以感知到的地步,那就可以起作用。
事務(wù)嵌套 影響
首先經(jīng)過實驗,結(jié)論一仍然成立,即,當(dāng)不加上rollbackFor = Exception.class 的時候,無論內(nèi)外報RuntimeException,都會回滾;無論內(nèi)外報 非RuntimeException 錯誤,都不會回滾。如果加上rollbackFor = Exception.class,無論內(nèi)外怎么報錯,都會回滾。這些代碼就不給出了。接下來,試下下面兩種情況:
@GetMapping("/out") @Transactional( rollbackFor = Exception.class) public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); throw new Exception(); } @Transactional public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); // throw new Exception(); }
情況一,外面事務(wù)加上rollbackFor = Exception.class,里面事務(wù)不加,測試內(nèi)外分別報錯的情況(為了簡化代碼量,只給出了外面報錯的代碼),都可以回滾。因為,無論如何,錯誤都拋給了外面那個事務(wù)進(jìn)行處理,而外面那個加上了rollbackFor = Exception.class,具備處理非RuntimeException錯誤的能力,所以都可以讓事務(wù)進(jìn)行正?;貪L。
下面看情況二,里面的事務(wù)加上rollbackFor = Exception.class,外面不加,外面報錯。
@GetMapping("/out") @Transactional public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); throw new Exception(); } @Transactional( rollbackFor = Exception.class) public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); }
事務(wù)都無法回滾,這是我們有個疑問,里面的事務(wù)明明有很強(qiáng)的處理能力啊,為什么和外面一起回滾失敗呢,別著急,等等聊這個。
然后試下里面報錯:
@GetMapping("/out") @Transactional public void out() throws Exception{ innerService.inner(); int age = random.nextInt(100); User user = new User().setAge(age).setName("name:" + age); userService.save(user); } @Transactional( rollbackFor = Exception.class) public void inner() throws Exception{ Role role = new Role(); role.setRoleName("roleName:"+new Random().nextInt(100)); roleService.save(role); throw new Exception(); }
咦,這回都進(jìn)行了正常的回滾。我的天,這回外面沒有處理能力,為什么接受里面拋出來的錯誤,也進(jìn)行了回滾!?。】瓷先?,就好像里外事務(wù)總是同生共死的對不對?原來,@Transactional還有個參數(shù),看下源碼,這個注解還有默認(rèn)值:
Propagation propagation() default Propagation.REQUIRED;
REQUIRED的意思是說,事務(wù)嵌套的時候,如果發(fā)現(xiàn)已經(jīng)有事務(wù)存在了,就加入這個事務(wù),而不是新建一個事務(wù),所以根本就不存在兩個事務(wù),一直只有一個!至于,此參數(shù)其他值,本文不進(jìn)行測試。回到上面的問題,當(dāng)外面報錯的時候,此時查看事務(wù),沒有增加rollbackFor = Exception.class參數(shù),即沒有處理非RuntimeException能力,所以代碼走完,貌似“兩個事務(wù)”,都回滾失敗了。當(dāng)里面報錯的時候,事務(wù)已經(jīng)添加上了處理非RuntimeException能力,所以,代碼走完就回滾成功了。
結(jié)論三:由于REQUIRED屬性,“兩個事務(wù)”其實是一個事務(wù),處理能力看報錯時刻,是否添加了處理非RuntimeException的能力。
try catch和事務(wù)嵌套 共同影響
在結(jié)論一二三成立的條件下,探索共同影響的問題就簡單多了,由于情況太多,就不進(jìn)行過多的代碼展示了。
結(jié)論
結(jié)論一:
對于@Transactional可以保證RuntimeException錯誤的回滾,如果想保證非RuntimeException錯誤的回滾,需要加上rollbackFor = Exception.class 參數(shù)。
結(jié)論二:
try catch只是對異常是否可以被@Transactional 感知 到有影響。如果錯誤拋到切面可以感知到的地步,那就可以起作用。
結(jié)論三:
由于REQUIRED屬性,“兩個事務(wù)”其實是一個事務(wù),處理能力看報錯時刻,是否添加了處理非RuntimeException的能力。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
idea中開啟Run Dashboard 和 快速復(fù)制項目并改變端口的方法
這篇文章主要介紹了idea中開啟Run Dashboard 和 快速復(fù)制項目并改變端口的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08mybatis insert foreach循環(huán)插入方式
這篇文章主要介紹了mybatis insert foreach循環(huán)插入方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07java如何根據(jù)PostMan發(fā)送請求設(shè)置接口請求工具類
在Java中調(diào)用第三方接口可以通過不同的方式,如使用GET、POST等請求,關(guān)鍵點(diǎn)包括設(shè)置正確的請求方式、URL、參數(shù)(params)、頭信息(headers)和請求體(body),對于不同的數(shù)據(jù)格式,如XML和JSON,需在header中聲明內(nèi)容類型2024-09-09springboot前后臺數(shù)據(jù)交互的示例代碼
這篇文章主要介紹了springboot前后臺數(shù)據(jù)交互的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式]
本篇文章主要介紹了Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式],具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01Java并發(fā)編程之柵欄(CyclicBarrier)實例介紹
這篇文章主要介紹了Java并發(fā)編程之柵欄(CyclicBarrier)實例介紹,柵欄類似閉鎖,但是它們是有區(qū)別的,需要的朋友可以參考下2015-04-04