The Spring Transactional annotation @Transactional can guarantee atomicity, allowing the entire transaction to be rolled back if an error occurs within the transaction, but adding a try catch or transaction nesting can cause the transaction to fail to roll back. Test a wave.

To prepare

Create two tables to simulate two data operations

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;
Copy the code

test

According to the permutation and combination principle, we carry out four tests: 1. No try catch and no nesting; 2, there is a try catch, no nesting; 3, no try catch, nested; 4. Both.

Simplest test

Can transactions roll back normally if we simply @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();
    }
Copy the code

If a RuntimeException error is reported within the transaction, the transaction can be rolled back.

    @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();
    }
Copy the code

If an Exception (non-runtimeException) error is reported within a transaction, the transaction cannot be rolled back.

    @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();
    }
Copy the code

If it is an Exception error (non-runtimeException), the rollbackFor = exception.class argument can also be rolled back.

@Transactional guarantees rollback of RuntimeException errors. To ensure rollback of non-RuntimeException errors, add the rollbackFor = Exception. Class parameter.

Try catch influence

After testing bloggers in various cases, it was found that try catch had no effect on the rollback itself, and the conclusion was still valid. Try catch only has an effect on whether an exception can be sensed by @Transactional. If the error is thrown to the point where the aspect can feel it, it can work.

    @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; }}Copy the code

For example, the code above is rolled back.

    @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){
        }
    }
Copy the code

However, if an error in a catch is not continued online, the segment cannot perceive the error and cannot process it, then the transaction cannot be rolled back.

Try catch only checks whether exceptions can be Transactional by @transactionalperceptionTo have an impact. If the error is thrown to the point where the aspect can feel it, it can work.

Impact of transaction nesting

RollbackFor = Exception; rollbackFor = Exception; rollbackFor = Exception; Rollback is not performed when non-RuntimeException errors are reported either internally or externally. If you add rollbackFor = exception. class, it will be rolled back regardless of any internal or external error. I’m not going to give you that code. Next, try the following two scenarios:

    @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();
    }
Copy the code

RollbackFor = exception.class; rollbackFor = exception.class; Because, in any case, the error is thrown to the outside transaction, which, with rollbackFor = exception.class, has the ability to handle non-RuntimeException errors, can allow the transaction to rollback normally.

RollbackFor = Exception. Class rollbackFor = Exception. Class rollbackFor = Exception.

    @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);
    }
Copy the code

Transactions can not be rolled back, this is we have a question, the inside of the transaction clearly has a strong processing ability ah, why and the outside of the rollback failure, don’t worry, wait to talk about this.

Then try the error:

    @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();
    }
Copy the code

Well, they all rolled back normally this time. My god, this time there is no processing ability outside, why accept the error thrown out inside, also rolled back!! It’s like the inside and the outside go hand in hand, right? Transactional (@transactional) ¶ This annotation also has a default value:

Propagation propagation() default Propagation.REQUIRED;
Copy the code

REQUIRED means that when a transaction is nested, if it is found that a transaction already exists, join the transaction instead of creating a new one, so there are no two transactions at all, only one! Other values of this parameter are not tested in this article. RollbackFor = exception. class = RuntimeException; rollbackFor = exception. class = RuntimeException; By the time an error is reported, the transaction has added the ability to handle non-RuntimeExceptions, so the code will be rolled back as soon as it runs.

Due to the REQUIRED attribute, the “two transactions” are actually one transaction, and the ability to handle non-runtimeExceptions has been added.

Try catch and transaction nesting work together

With conclusion 1, 2, 3 true, it is much easier to explore the problem of common influence, because there are too many cases, not too much code to show.

conclusion

@Transactional guarantees rollback of RuntimeException errors. To ensure rollback of non-RuntimeException errors, add the rollbackFor = Exception. Class parameter. Try catch only affects whether an exception can be sensed by @Transactional. If the error is thrown to the point where the aspect can feel it, it can work. Due to the REQUIRED attribute, the “two transactions” are actually one transaction, and the ability to handle non-runtimeExceptions has been added.