Spring 事務註解 @Transactional 原本能夠保證原子性,若是事務內有報錯的話,整個事務能夠保證回滾,可是加上try catch或者事務嵌套,可能會致使事務回滾失敗。測試一波。bash
建兩張表,模擬兩個數據操做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 參數。cdn
通過博主多種狀況測試,發現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;
}
}
複製代碼
好比上面一段代碼就回滾了。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){
}
}
複製代碼
然而,將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的能力。
在結論一二三成立的條件下,探索共同影響的問題就簡單多了,因爲狀況太多,就不進行過多的代碼展現了。
結論一:對於@Transactional能夠保證RuntimeException錯誤的回滾,若是想保證非RuntimeException錯誤的回滾,須要加上rollbackFor = Exception.class 參數。 結論二:try catch只是對異常是否能夠被@Transactional 感知 到有影響。若是錯誤拋到切面能夠感知到的地步,那就能夠起做用。 結論三:因爲REQUIRED屬性,「兩個事務」實際上是一個事務,處理能力看報錯時刻,是否添加了處理非RuntimeException的能力。