spring 中事務註解@Transactional與trycatch的使用
spring事務註解@Transactional與trycatch
在項目中 @service層中 我們會經常在做一些增刪改操作的方法上看到 spring 的事務註解 @transaction 已知@transaction 是讓spring 幫我們實現事務的控制。
但是在項目中會經常看到 有的方法中 會存在trycatch塊包括的方法上註解著@transaction
eg:
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { ..... e.printStackTrace();} // } return json; }
上述的方法執行後可以看到事務並沒有執行,接下來再看一個例子eg:
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { // 手動硬編碼開啟spring事務管理 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace();} // } return json; }
上述方法執行後我們可以看到事務最後執行瞭,但實際上 事務 執行隻是因為手動硬編碼開啟spring事務管理起瞭作用 而方法上的註解並沒有起作用
接下來再看一個例子eg
@Override @Transactional public Json addOrder(TOrderAddReq tOrderAddReq) { try{ //增刪改方法 } catch (Exception e) { throw new RuntimeException(); } // } return json; }
上述方法執行後我們可以看到事務是執行瞭的,但這裡有個小細節:@Transactional不做任何配置 默認是對拋出的unchecked異常回滾,checked異常不會回滾,為瞭讓所有異常都會讓事務啟動可以將 @Transactional配置為 @Transactional(rollbackFor = Exception.class)
解釋:
spring的事務邊界是在調用業務方法之前開始的,業務方法執行完畢之後來執行commit or rollback(spring默認取決於是否拋出runtime異常).
如果拋出runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。
一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作後(比如關閉文件等)一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產生臟數據.所以你的catch代碼是畫蛇添足。
@Transactional回滾問題(try catch、嵌套)
Spring 事務註解 @Transactional 本來可以保證原子性,如果事務內有報錯的話,整個事務可以保證回滾,但是加上try catch或者事務嵌套,可能會導致事務回滾失敗。測試一波。
準備
建兩張表,模擬兩個數據操作
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;
測試
根據排列組合原理,我們進行四種測試:1、無try catch、無嵌套;2、有try catch、無嵌套;3、無try catch、有嵌套;4、都有。
最簡單測試
如果我們單純@Transactional,事務可以正常回滾嗎?
@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(); }
如果事務內報瞭RuntimeException錯誤,事務可以回滾。
@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(); }
如果事務內報瞭Exception錯誤(非RuntimeException錯誤),事務不可以回滾。
@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 參數也可以實現回滾。
結論一:對於@Transactional可以保證RuntimeException錯誤的回滾,如果想保證非RuntimeException錯誤的回滾,需要加上rollbackFor = Exception.class 參數。
try catch 影響
經過博主多種情況測試,發現try catch對回滾這個事本身沒有什麼影響,結論一照樣成立。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中的錯誤不繼續網上拋,切面無法感知到錯誤,無法進行處理,那麼事務就無法回滾瞭。
結論二:try catch隻是對異常是否可以被@Transactional 感知 到有影響。如果錯誤拋到切面可以感知到的地步,那就可以起作用。
事務嵌套 影響
首先經過實驗,結論一仍然成立,即,當不加上rollbackFor = Exception.class 的時候,無論內外報RuntimeException,都會回滾;無論內外報 非RuntimeException 錯誤,都不會回滾。如果加上rollbackFor = Exception.class,無論內外怎麼報錯,都會回滾。這些代碼就不給出瞭。接下來,試下下面兩種情況:
@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(); }
情況一,外面事務加上rollbackFor = Exception.class,裡面事務不加,測試內外分別報錯的情況(為瞭簡化代碼量,隻給出瞭外面報錯的代碼),都可以回滾。因為,無論如何,錯誤都拋給瞭外面那個事務進行處理,而外面那個加上瞭rollbackFor = Exception.class,具備處理非RuntimeException錯誤的能力,所以都可以讓事務進行正常回滾。
下面看情況二,裡面的事務加上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); }
事務都無法回滾,這是我們有個疑問,裡面的事務明明有很強的處理能力啊,為什麼和外面一起回滾失敗呢,別著急,等等聊這個。
然後試下裡面報錯:
@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(); }
咦,這回都進行瞭正常的回滾。我的天,這回外面沒有處理能力,為什麼接受裡面拋出來的錯誤,也進行瞭回滾!!!看上去,就好像裡外事務總是同生共死的對不對?原來,@Transactional還有個參數,看下源碼,這個註解還有默認值:
Propagation propagation() default Propagation.REQUIRED;
REQUIRED的意思是說,事務嵌套的時候,如果發現已經有事務存在瞭,就加入這個事務,而不是新建一個事務,所以根本就不存在兩個事務,一直隻有一個!至於,此參數其他值,本文不進行測試。回到上面的問題,當外面報錯的時候,此時查看事務,沒有增加rollbackFor = Exception.class參數,即沒有處理非RuntimeException能力,所以代碼走完,貌似“兩個事務”,都回滾失敗瞭。當裡面報錯的時候,事務已經添加上瞭處理非RuntimeException能力,所以,代碼走完就回滾成功瞭。
結論三:由於REQUIRED屬性,“兩個事務”其實是一個事務,處理能力看報錯時刻,是否添加瞭處理非RuntimeException的能力。
try catch和事務嵌套 共同影響
在結論一二三成立的條件下,探索共同影響的問題就簡單多瞭,由於情況太多,就不進行過多的代碼展示瞭。
結論
結論一:
對於@Transactional可以保證RuntimeException錯誤的回滾,如果想保證非RuntimeException錯誤的回滾,需要加上rollbackFor = Exception.class 參數。
結論二:
try catch隻是對異常是否可以被@Transactional 感知 到有影響。如果錯誤拋到切面可以感知到的地步,那就可以起作用。
結論三:
由於REQUIRED屬性,“兩個事務”其實是一個事務,處理能力看報錯時刻,是否添加瞭處理非RuntimeException的能力。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 使用@Transactional 設置嵌套事務不回滾
- Spring中@Transactional(rollbackFor=Exception.class)屬性用法介紹
- Spring事務@Transactional註解四種不生效案例場景分析
- Spring事務失效的一種原因關於this調用的問題
- 關於controller的異常處理及service層的事務控制方式