Spring 事務註解 @Transactional 原本能夠保證原子性,若是事務內有報錯的話,整個事務能夠保證回滾,可是加上try catch或者事務嵌套,可能會致使事務回滾失敗。測試一波。
建兩張表,模擬兩個數據操做app
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;
根據排列組合原理,咱們進行四種測試:一、無try catch、無嵌套;二、有try catch、無嵌套;三、無try catch、有嵌套;四、都有。dom
若是咱們單純@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錯誤,事務能夠回滾。spa
@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錯誤),事務不能夠回滾。code
@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 參數也能夠實現回滾。orm
結論一:對於@Transactional能夠保證RuntimeException錯誤的回滾,若是想保證非RuntimeException錯誤的回滾,須要加上rollbackFor = Exception.class 參數。事務
通過博主多種狀況測試,發現try catch對回滾這個事自己沒有什麼影響,結論一照樣成立。try catch只是對異常是否能夠被@Transactional 感知 到有影響。若是錯誤拋到切面能夠感知到的地步,那就能夠起做用。ci
@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中的錯誤不繼續網上拋,切面沒法感知到錯誤,沒法進行處理,那麼事務就沒法回滾了。it
結論二: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的能力。
在結論一二三成立的條件下,探索共同影響的問題就簡單多了,因爲狀況太多,就不進行過多的代碼展現了。
結論一:對於@Transactional能夠保證RuntimeException錯誤的回滾,若是想保證非RuntimeException錯誤的回滾,須要加上rollbackFor = Exception.class 參數。
結論二:try catch只是對異常是否能夠被@Transactional 感知 到有影響。若是錯誤拋到切面能夠感知到的地步,那就能夠起做用。
結論三:因爲REQUIRED屬性,「兩個事務」實際上是一個事務,處理能力看報錯時刻,是否添加了處理非RuntimeException的能力。