The Use of Transaction Annotation @ Transactional and trycatch in spring

  • 2021-09-24 22:27:27
  • OfStack

spring transaction annotations @ Transactional and trycatch

In the @ service layer of the project, we will often see the transaction annotation of spring on the method of doing some additions, deletions and modifications. @ transaction is known to ask spring to help us realize transaction control.

However, in projects, you will often see that some methods exist in trycatch blocks, including methods annotated with @ transaction

eg:


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
            .....
            e.printStackTrace();}
//        }
        return json;
    }

After the above method is executed, you can see that the transaction is not executed. Next, look at an example eg:


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
         //  Manual hard-coded open spring Transaction management 
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();}
//        }
        return json;
    }

After the above method is executed, we can see that the transaction finally executes, but in fact, the transaction executes only because the manual hard-coded opening of spring transaction management works, but the annotation on the method does not work

Next, look at an example eg


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
         
            throw new RuntimeException();
            }
//        }
        return json;
    }

After the above method is executed, we can see that the transaction is executed, but there is a small detail here: @ Transactional does not make any configuration. By default, the thrown unchecked exception is rolled back, and the checked exception will not be rolled back. In order for all exceptions to start the transaction, @ Transactional can be configured as @ Transactional (rollbackFor = Exception. class)

Explanation:

The transaction boundary of spring begins before the business method is invoked, and commit or rollback is executed after the business method is executed (spring depends by default on whether an runtime exception is thrown).

If runtime exception is thrown and catch does not arrive in your business method, the transaction rolls back.

1 generally do not need in the business method catch exception, if you must catch, after you want to do the work (such as closing files, etc.) 1 must throw runtime exception, otherwise spring will your operation commit, this will produce dirty data. So your catch code is gilding the lily.

@ Transactional Rollback Issue (try catch, nested)

Spring transaction annotation @ Transactional is supposed to guarantee atomicity, and the whole transaction can be rolled back if an error is reported within the transaction, but adding try catch or transaction nesting may cause the transaction rollback to fail. Test 1 wave.

Prepare

Build 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;

Test

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

The simplest test

If we simply @ Transactional, can the transaction be rolled back normally?


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

If an RuntimeException error is reported in a 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();
    }

If an Exception error (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();
    }

If it is an Exception error (non-RuntimeException), a rollback can also be achieved by adding the rollbackFor = Exception. class parameter.

Conclusion 1: For @ Transactional, you can guarantee the rollback of RuntimeException errors. If you want to guarantee the rollback of non-RuntimeException errors, you need to add the parameter rollbackFor = Exception. class.

try catch effect

After blogger tests in various situations, it is found that try catch has no effect on rollback itself, and Conclusion 1 is still valid. try catch only affects whether an exception can be perceived by @ Transactional. If the error is thrown to the point where the section can be perceived, 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;
        }
    }

For example, the above 1 code 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){
        }
    }

However, if the error in catch does not continue to be thrown online, the facet cannot perceive the error and cannot handle it, then the transaction cannot be rolled back.

Conclusion 2: try catch only affects whether abnormality can be perceived by @ Transactional. If the error is thrown to the point where the section can be perceived, it can work.

Transaction nesting effect

First of all, after experiments, conclusion 1 is still valid, that is, when rollbackFor = Exception. class is not added, RuntimeException will be rolled back regardless of internal and external reports; No matter whether a non-RuntimeException error is reported inside or outside, it will not be rolled back. If you add rollbackFor = Exception. class, no matter what error is reported inside or outside, it will be rolled back. These codes are not given. Next, try the following two situations:


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

Case 1, external transactions plus rollbackFor = Exception. class, internal transactions are not added, and both internal and external errors are tested (in order to simplify the code amount, only external error-reporting codes are given), which can be rolled back. Because, in any case, the error is thrown to the outside transaction for processing, and the outside transaction with rollbackFor = Exception. class has the ability to handle non-RuntimeException errors, so the transaction can be rolled back normally.

Let's look at situation 2. Inside transactions plus rollbackFor = Exception. class, outside is not added, and outside errors are reported.


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
         //  Manual hard-coded open spring Transaction management 
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();}
//        }
        return json;
    }
0

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

Then try to report errors inside:


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
         //  Manual hard-coded open spring Transaction management 
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();}
//        }
        return json;
    }
1

Gee, this time we all rolled back normally. My God, this time there is no processing power outside, why accept the errors thrown inside and roll back! ! ! It seems that internal and external affairs always live and die together, right? Originally, @ Transactional also has a parameter. Look at the source code. This annotation has a default value:


 @Override
    @Transactional
    public Json addOrder(TOrderAddReq tOrderAddReq) {
  try{
    // Addition, deletion and modification methods 
        } catch (Exception e) {
         //  Manual hard-coded open spring Transaction management 
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();}
//        }
        return json;
    }
2

REQUIRED means that when transactions are nested, if you find that there is already a transaction, you will join this transaction instead of creating a new transaction, so there are no two transactions at all, and there is only one transaction in 1! As for other values of this parameter, we will not test them in this paper. Back to the above problem, when an error is reported outside, look at the transaction at this time, and the parameter rollbackFor = Exception. class is not added, that is, there is no ability to handle non-RuntimeException, so when the code is finished, it seems that "two transactions" have failed to roll back. When an error is reported, the transaction has been added with the ability to handle non-RuntimeException, so the code rollback is successful when it is finished.

Conclusion 3: Because of the REQUIRED attribute, "two transactions" are actually one transaction, and the processing ability depends on whether the ability to process non-RuntimeException is added when the error is reported.

try catch and transaction nesting affect together

Under the condition that conclusion 123 holds, it is much simpler to explore the problem of common influence. Because there are too many situations, there is no too much code display.

Conclusion

Conclusion 1:

For @ Transactional, the rollback of RuntimeException errors can be guaranteed. If you want to guarantee the rollback of non-RuntimeException errors, you need to add the parameter rollbackFor = Exception. class.

Conclusion 2:

try catch only affects whether an exception can be perceived by @ Transactional. If the error is thrown to the point where the section can be perceived, it can work.

Conclusion 3:

Because of the REQUIRED attribute, "two transactions" are actually one transaction, and the processing capacity depends on whether the ability to process non-RuntimeException is added at the time of error.


Related articles: