@Transactional回滾問題(try catch、嵌套)

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對回滾這個事自己沒有什麼影響,結論一照樣成立。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的能力。

try catch和事務嵌套 共同影響

在結論一二三成立的條件下,探索共同影響的問題就簡單多了,因爲狀況太多,就不進行過多的代碼展現了。

結論

結論一:對於@Transactional能夠保證RuntimeException錯誤的回滾,若是想保證非RuntimeException錯誤的回滾,須要加上rollbackFor = Exception.class 參數。 結論二:try catch只是對異常是否能夠被@Transactional 感知 到有影響。若是錯誤拋到切面能夠感知到的地步,那就能夠起做用。 結論三:因爲REQUIRED屬性,「兩個事務」實際上是一個事務,處理能力看報錯時刻,是否添加了處理非RuntimeException的能力。

關注我,作一個有趣的coder
相關文章
相關標籤/搜索