1. Introduction
reference resources:
mysql essentially uses database rollback, and the table engine needs InnoDB
The versions used are as follows
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <!-- Packaging configuration --> <packaging>jar</packaging> <properties> <java.version>1.8</java.version> <mysql-driver.version>8.0.28</mysql-driver.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Hot deployment --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!-- thymeleaf,for html,jsp... --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- json --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Database connection pool druid --> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <!-- redis springboot 2.x:Default use lettuce Realized springboot 1.x:Default use Jedis Realized springboot 2.x change to the use of sth. jedis,The following code <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> exclude lettuce package <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> add to jedis client <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redis use pool --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <!-- <version>2.11.1</version>--> </dependency> </dependencies> <build> <plugins> <!-- application SpringBoot plug-in unit use spring-boot-devtools Application of module, When classpath When the file in is changed, it will restart automatically! --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- If the version number is not added, it will be reported in red --> <version>2.6.3</version> </plugin> <!-- mybatis-generator.start --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <dependencies> <dependency> <groupId> mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-driver.version}</version> <scope>runtime</scope> </dependency> <!-- <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency> --> </dependencies> <!-- <executions> <execution> <id>Generate MyBatis Artifacts</id> <phase>package</phase> <goals> <goal>generate</goal> </goals> </execution> </executions> --> <configuration> <!--Allow moving generated files --> <verbose>true</verbose> <!-- Overwrite --> <!-- Special attention should be paid here,If you do not add this setting, it will be created again in the original directory every time you run--> <overwrite>true</overwrite> <!-- generator Location of the tool configuration file --> <configurationFile> src/main/resources/mybatis-generator.xml </configurationFile> </configuration> </plugin> <!-- mybatis-generator.end --> </plugins> </build> </project>
2. By default, transactions will be rolled back when unchecked exceptions occur
2.1 note that exceptions should be thrown in the @ Transactional annotated class / method and its sub methods
2.1.1Service
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { int count = userMapper.insert(user); if( count > 0){ //An unchecked exception is thrown here, which will cause rollback throw new RuntimeException("test uncheck Exception causes rollback"); } return count; } @Transactional public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //The insertUser method does not define the propagation behavior. It will adopt required (default) //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
2.1.2 controller
package com.demo.controllers; import com.demo.models.po.User; import com.demo.services.UserService; import com.demo.utils.AjaxResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.HashMap; @RestController @RequestMapping("/user") public class UserController { //Automatic injection @Autowired private UserService service; /** * http://localhost:9000/demo/user/addlist * @param user * @return */ @RequestMapping(value = "/addlist", method = RequestMethod.POST) public AjaxResult insertUsers(User user){ try{ int count = service.insertUsers(Arrays.asList(user)); return AjaxResult.success("Insert successful", new HashMap<String, Object>(){ { put("count", count); put("user", user); } }); }catch(Exception e){ return AjaxResult.error(e.getMessage()); } } }
2.1.3 testing
package com.demo.controllers; import com.alibaba.fastjson.JSON; import com.demo.models.po.User; import com.example.demo.DemoApplication; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; import javax.servlet.http.Cookie; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.*; /** * When you want to execute, place the mouse over the corresponding method and right-click to select run. */ @RunWith(SpringRunner.class) //you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test //classes points to the startup class @SpringBootTest(classes= DemoApplication.class) public class UserControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mvc; private MockHttpSession session; /** * Simulate login before starting */ @Before public void setupMockMvc(){ //Initialize MockMvc object mvc = MockMvcBuilders.webAppContextSetup(wac).build(); session = new MockHttpSession(); //The interceptor will determine whether the user logs in, so a user is injected here session.setAttribute("user", "root"); } /** * Note: the url here does not need to add / demo (the secondary directory of the configuration file server. Servlet. Context path = / Demo) * @throws Exception */ @Test public void insertUser() throws Exception { MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>(); params.add("id", "4"); params.add("name", "test4"); params.add("birthday", "2020-1-4"); params.add("address", "Test City 4"); String uri = "/user/addlist"; String result = mvc.perform(MockMvcRequestBuilders.post(uri) //Impersonate cookie s .cookie( new Cookie("token", "123456") ) //Simulation session .session(session) //Mainstream browsers such as Google comply with normal specifications and do not need to set character coding, but considering non mainstream browsers, they are still retained .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .params(params) // .contentType(MediaType.APPLICATION_JSON) // / / pass content as Null (JSON)???? ) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andDo(MockMvcResultHandlers.print()) //Returns the requested string information .andReturn().getResponse().getContentAsString(); Assert.assertEquals("Call succeeded","demo8-1",result); } }
Result: rollback
2.2 the exception thrown is not in the class / method of @ Transactional annotation and its sub methods
If @ Transactional is in the Service class and an exception is thrown in the controller (whether unchecked or not), it will not be rolled back
2.2.1 controller
package com.demo.controllers; import com.demo.models.po.User; import com.demo.services.UserService; import com.demo.utils.AjaxResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.HashMap; @RestController @RequestMapping("/user") public class UserController { //Automatic injection @Autowired private UserService service; /** * http://localhost:9000/demo/user/addlist * @param user * @return */ @RequestMapping(value = "/addlist", method = RequestMethod.POST) public AjaxResult insertUsers(User user){ try{ int count = service.insertUsers(Arrays.asList(user)); if( count>0 ){ //If an exception is not thrown in the annotation method and its sub methods, it will not cause rollback throw new RuntimeException("test uncheck Exceptions do not cause rollback"); } return AjaxResult.success("Insert successful", new HashMap<String, Object>(){ { put("count", count); put("user", user); } }); }catch(Exception e){ return AjaxResult.error(e.getMessage()); } } }
2.2.2Service
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { int count = userMapper.insert(user); return count; } @Transactional public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //1. The insertuser method does not define the propagation behavior. It will adopt required (default) //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: no rollback
3. By default, if a checked exception is encountered, it will not be rolled back
3.1Service
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { int count = userMapper.insert(user); if( count > 0){ //check exceptions will not cause rollback throw new Exception("test check Exceptions do not cause rollback"); } return count; } @Transactional public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //The insertUser method does not define the propagation behavior. It will adopt required (default) //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
3.2 controller
package com.demo.controllers; import com.demo.models.po.User; import com.demo.services.UserService; import com.demo.utils.AjaxResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.HashMap; @RestController @RequestMapping("/user") public class UserController { //Automatic injection @Autowired private UserService service; /** * http://localhost:9000/demo/user/addlist * @param user * @return */ @RequestMapping(value = "/addlist", method = RequestMethod.POST) public AjaxResult insertUsers(User user){ try{ int count = service.insertUsers(Arrays.asList(user)); return AjaxResult.success("Insert successful", new HashMap<String, Object>(){ { put("count", count); put("user", user); } }); }catch(Exception e){ return AjaxResult.error(e.getMessage()); } } }
Result: no rollback
3.3 solution 1: set rollback point
Set the rollback point in @ Transactional's class / method and its sub methods, and roll back to the rollback point after catching the checked exception
Service:
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { //Insert id=5 that will not be rolled back User noRollUser = new User(); noRollUser.setId(5); noRollUser.setName("Test 5"); userMapper.insert(noRollUser); //Set rollback point Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); try{ int count = userMapper.insert(user); if( count > 0){ // throw new RuntimeException("test uncheck exception causes rollback"); throw new Exception("test check Exceptions do not cause rollback"); } return count; }catch(Exception e){ //After catching the exception, roll back to savePoint TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); throw e; } } @Transactional public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //The insertUser method does not define the propagation behavior. It will adopt required (default) //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: only 4 but no 5 were rolled back
3.4 if it is an unchecked exception, it will also go to the rollback point, not all
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { //Insert id=5 that won't roll User noRollUser = new User(); noRollUser.setId(5); noRollUser.setName("Test 5"); userMapper.insert(noRollUser); //Set rollback point Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); try{ int count = userMapper.insert(user); if( count > 0){ throw new RuntimeException("test uncheck Exception causes rollback"); // throw new Exception("test check exception will not cause rollback"); } return count; }catch(Exception e){ //After catching the exception, roll back to savePoint TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); throw e; } } @Transactional public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //The insertUser method does not define the propagation behavior. It will adopt required (default) //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: only 4 but no 5 were rolled back
3.5 solution 2: @ Transactional's rollback for
Throw an exception directly. The exception cannot be caught here. Otherwise, the rollback for will be invalidated
Service
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { int count = userMapper.insert(user); if( count > 0){ throw new Exception("test check Exceptions do not cause rollback"); } return count; } @Transactional( //Isolation level: //A transaction can only read data that has been committed by another transaction. This level prevents dirty reads isolation = Isolation.READ_COMMITTED, //Communication behavior: // REQUIRED: if a transaction currently exists, join the transaction; If there is no current transaction, a new transaction is created. propagation = Propagation.REQUIRED, //Specify exception class rollbackFor = Exception.class //{RuntimeException.class, Exception.class} ) public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //1. The insertuser method does not define the propagation behavior. It will adopt REQUIRED, //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: rollback occurs
3.6 solution 3: catch exceptions and roll back manually
3.6.1 note that it should be captured and processed in @ Transactional's class / method and its sub methods. It is invalid outside
3.6.2 when an exception is caught and no longer thrown, the rollback will not be rolled back even if the rollback for is specified
Note that both checked and unchecked exceptions will not be rolled back as long as they are caught and no longer thrown
Service
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { try{ int count = userMapper.insert(user); if( count > 0){ //throw new RuntimeException("test uncheck exception causes rollback"); throw new Exception("test check Exceptions do not cause rollback"); } return count; }catch(Exception e){ //To handle rollback, or even unchecked is invalid return 0; } } @Transactional( //Isolation level: //A transaction can only read data that has been committed by another transaction. This level prevents dirty reads isolation = Isolation.READ_COMMITTED, //Communication behavior: // REQUIRED: if a transaction currently exists, join the transaction; If there is no current transaction, a new transaction is created. propagation = Propagation.REQUIRED, //Specify exception class rollbackFor = Exception.class ) public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //1. The insertuser method does not define the propagation behavior. It will adopt REQUIRED, //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: no rollback
3.6.3 correct usage: manually roll back after catching exceptions
package com.demo.services; import com.demo.models.mapper.AppadminMapper; import com.demo.models.mapper.UserMapper; import com.demo.models.po.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.util.List; @Service public class UserService { //Automatic injection @Autowired private UserMapper userMapper; public User getUser(Integer id) { return userMapper.selectByPrimaryKey(id); } public int insertUser(User user) throws Exception { try{ int count = userMapper.insert(user); if( count > 0){ //throw new RuntimeException("test uncheck exception causes rollback"); throw new Exception("test check Exceptions do not cause rollback"); } return count; }catch(Exception e){ //Only when used in combination with @ Transactional(rollbackFor = Exception.class) can it be rolled back TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return 0; } } @Transactional( //Isolation level: //A transaction can only read data that has been committed by another transaction. This level prevents dirty reads isolation = Isolation.READ_COMMITTED, //Communication behavior: // REQUIRED: if a transaction currently exists, join the transaction; If there is no current transaction, a new transaction is created. propagation = Propagation.REQUIRED, //Specify exception class rollbackFor = Exception.class //{RuntimeException.class, Exception.class} ) public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //1. The insertuser method does not define the propagation behavior. It will adopt REQUIRED, //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method count += insertUser(user); } return count; } }
Result: rollback occurs
4. Transaction attribute setting
@Transactional( //Isolation level: //A transaction can only read data that has been committed by another transaction. This level prevents dirty reads isolation = Isolation.READ_COMMITTED, //Communication behavior: // REQUIRED: if a transaction currently exists, join the transaction; If there is no current transaction, a new transaction is created. propagation = Propagation.REQUIRED // SUPPORTS: if a transaction currently exists, join the transaction; If there are currently no transactions, continue to run in a non transactional manner. // propagation = Propagation.SUPPORTS ) public int insertUsers(List<User> userList) throws Exception { int count = 0; for(User user : userList){ //Calling the sub method insertUser will use the annotation @ Transactional to define the propagation behavior //1. The insertuser method does not define the propagation behavior. It will adopt REQUIRED, //That is, the current transaction is followed, so it will use the same transaction as the insertUsers method //2.SUPPORTS: the insertUser method does not define the propagation behavior, so SUPPORTS is adopted count += insertUser(user); } return count; }
5. Summary
Transaction rollback must be in the @ Transactional class / method and its sub methods. It is invalid outside, so general exception capture can be placed outside
In order to roll back unchecked and checked exceptions at the same time, within the scope of @ Transactional, you can use:
1. Catch exception + set rollback point
2. Catch exception + rollback for + manual rollback setRollbackOnly