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.