Source: https://www.cnblogs.com/xuwujing/p/11184162.html

takeaway

First in the public number: JAVA thief ship, wonderful articles, waiting for your attention! A share Java learning resources, practical experience and technical articles of the public number!

preface

This article is a tutorial on the use of SpringBoot Transaction.

SpringBoot Transaction

Note: If you want to get the project directly, you can jump to the bottom and download the project code through the link.

Transaction

Transaction management mode

In Spring, transactions are implemented in two ways: programmatic and declarative transaction management.

  • Programmatic transaction management: Programmatic transaction management is usedTransactionTemplateOr just use the underlying onePlatformTransactionManager. For programmatic transaction management, Spring recommends itTransactionTemplate.
  • Declarative transaction management: Built on AOP. The essence is to intercept before and after the method, then create or join a transaction before the target method starts, and commit or roll back the transaction after the target method is executed. Declarative transaction management does not require an intrusion code, through@TransactionalCan carry out transactions, faster and simpler, recommended use.

Transaction commit mode

By default, the database is in auto-commit mode. Each statement is in a separate transaction, and at the end of the statement execution, the transaction is implicitly committed on success and implicitly rolled back on failure.

For normal transaction management, a group of related operations are in a transaction, so the automatic commit mode of the database must be turned off. However, we don’t have to worry about this; Spring sets the auto-commit feature for the underlying connection to false. When using Spring for transaction management, spring sets automatic commit to false, which is equivalent to connection.setautocommit (false) in JDBC. Connection.mit (); .

Transaction isolation level

The isolation level refers to the degree of isolation between several concurrent transactions. Five constants representing the isolation level are defined in the TransactionDefinition interface:

  • TransactionDefinition.ISOLATION_DEFAULT: This is the default value and indicates that the default isolation level of the underlying database is used. For most of the database, this value is usually TransactionDefinition. ISOLATION_READ_COMMITTED.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: This isolation level indicates that one transaction can read data modified by another transaction but not yet committed. This level does not protect against dirty reads, non-repeatable reads, and phantom reads, so it is rarely used. PostgreSQL, for example, doesn’t actually have this level.
  • TransactionDefinition.ISOLATION_READ_COMMITTED: This isolation level indicates that a transaction can only read data already committed by another transaction. This level protects against dirty reads and is recommended in most cases.
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: Isolation level Indicates that a transaction can execute a query multiple times throughout the process and return the same records each time. This level prevents dirty and unrepeatable reads.
  • TransactionDefinition.ISOLATION_SERIALIZABLE: All transactions are executed one by one so that interference between transactions is completely impossible. That is, this level prevents dirty reads, unrepeatable reads, and phantom reads. But this severely affects the performance of the program. This level is also not typically used.

Transaction propagation behavior

The propagation behavior of a transaction means that there are several options to specify the execution behavior of a transactional method if a transaction context already exists before the current transaction is started. TransactionDefinition includes the following constants that represent propagation behavior:

  • TransactionDefinition.PROPAGATION_REQUIRED: If a transaction exists, join the transaction. If there is no transaction currently, a new transaction is created. This is the default value.
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: Creates a new transaction and suspends the current transaction if one exists.
  • TransactionDefinition.PROPAGATION_SUPPORTS: If a transaction exists, join the transaction. If there is no transaction currently, it continues in a non-transactional manner.
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: Runs nontransactionally and suspends the current transaction if one exists.
  • TransactionDefinition.PROPAGATION_NEVER: Runs nontransactionally and throws an exception if a transaction currently exists.
  • TransactionDefinition.PROPAGATION_MANDATORY: If a transaction exists, join the transaction. If there is no transaction currently, an exception is thrown.
  • TransactionDefinition.PROPAGATION_NESTED: Creates a transaction to run as a nested transaction of the current transaction if one exists; If no current affairs, the value of equivalent to the TransactionDefinition. PROPAGATION_REQUIRED.

Transaction rollback rules

The recommended way to instruct the Spring transaction manager to roll back a transaction is to throw an exception in the context of the current transaction. The Spring transaction manager catches any unhandled exceptions and then decides, based on rules, whether to roll back the transaction that threw the exception.

By default, Spring rolls back the transaction only if the exception thrown is a runtime unchecked exception, that is, if the exception thrown is a subclass of RuntimeException (Errors also causes the transaction to roll back). Throwing checked does not. You can explicitly configure the transaction to be rolled back when those exceptions are thrown, including checked. You can also explicitly define those exceptions that do not roll back transactions when thrown.

Common Transaction configuration

  • readOnly: This property is used to set whether the current transaction is read-only. True indicates read-only, false indicates read/write, and the default is false. Such as:

@ Transactional (readOnly = true);

  • rollbackForThis property sets the array of exception classes that need to be rolled back. When an exception from the specified array is thrown in a method, the transaction is rolled back. Such as:

Transactional(rollbackFor= runtimeException.class) Specifies multiple exception classes: @ Transactional (rollbackFor = {RuntimeException. Class, the Exception class});

  • rollbackForClassName: This property sets the array of exception class names that need to be rolled back. When an exception from the specified array of exception names is thrown in a method, the transaction is rolled back. Such as:

Transactional(rollbackForClassName= “RuntimeException”) Specifies multiple exception class names: @ Transactional (rollbackForClassName = {” RuntimeException “, “Exception”}).

  • noRollbackFor: This property is used to set the array of exception classes that do not need to be rolled back. When an exception from the specified array is thrown in a method, no transaction rollback is performed. Such as:

Transactional(noRollbackFor= runtimeException.class) @ Transactional (noRollbackFor = {RuntimeException. Class, the Exception class}).

  • noRollbackForClassName: This property is used to set the array of exception class names that do not need to be rolled back. When an exception from the specified array of exception names is thrown in a method, no transaction rollback is performed. Such as:

Transactional(noRollbackForClassName= “RuntimeException”) @ Transactional (noRollbackForClassName = {” RuntimeException “, “Exception”}).

  • propagation: This property sets the propagation behavior of the transaction. Such as:

@ Transactional (propagation = propagation NOT_SUPPORTED, readOnly = true).

  • isolation: This attribute is used to set the transaction isolation level of the underlying database. The transaction isolation level is used to handle multiple concurrent transactions. The default isolation level of the database is used.
  • timeout: This property is used to set the number of timeout seconds for a transaction. The default value is -1, indicating that the transaction never times out.

Matters needing attention

  • To decide whether to use things according to the actual needs, it is best to consider before coding, otherwise it will be difficult to maintain later;
  • If you use things, be sure to test things, because there are many cases where you think things work, but they don’t!
  • things@TransactionalThe use of the class is to be placed in the public method of the classprotected,privateThe @Transactional annotation is used on the method, which also fails (IDEA prompts it), but the transaction is invalid.
  • things@TransactionalDoes not apply to submethods in this method! That’s what you declare in public method A@TransactionalBut there are submethods B and C in method A, where method B does the data operation, but the exception is handled by B itself, so the thing will not take effect! Transactional method B declares things @transactional, but public method A does not declare things either! If you want something to work, you need to give transaction control of the child method to the calling method and use it in the child methodrollbackForThe annotation specifies the exception to be rolled back or thrown to the calling method for processing. In a word, the exception of the thing is handled by the caller!
  • things@TransactionalWhen controlled by Spring, it rolls back when an exception is thrown. If you use a catch, you can manually roll back the catch or throw an exception inside the catch, such as throw new RuntimeException(). .

The development of preparation

Environmental requirements

JDK: 1.8 SpringBoot: 1.5.17.RELEASE

First, there are Maven dependencies:

The pom.xml file is as follows:

<properties>

     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  <java.version>1.8</java.version>

  <maven.compiler.source>1.8</maven.compiler.source>

  <maven.compiler.target>1.8</maven.compiler.target>

  </properties>

 <parent>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-parent</artifactId>

  <version>1.5.17.RELEASE</version>

  <relativePath /> 

 </parent>

  <dependencies>

<! -- Spring Boot Web dependency core -->

  <dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-web</artifactId>

  </dependency>

<! -- Spring Boot Test dependency -->

  <dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-test</artifactId>

   <scope>test</scope>

  </dependency>

    <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-devtools</artifactId>

            <optional>true</optional>

        </dependency>

   <dependency>

    <groupId>org.mybatis.spring.boot</groupId>

    <artifactId>mybatis-spring-boot-starter</artifactId>

    <version>1.2.0</version>

   </dependency>

<! MySQL connection driver dependency -->

   <dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

    <version>5.1.44</version>

   </dependency>

<! -- Druid data connection pool dependency -->

   <dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>druid</artifactId>

    <version>1.1.8</version>

   </dependency>

  </dependencies>

Copy the code

Application. Properties file configuration:

banner.charset=UTF-8

server.tomcat.uri-encoding=UTF-8

spring.http.encoding.charset=UTF-8

spring.http.encoding.enabled=true

spring.http.encoding.force=true

spring.messages.encoding=UTF-8

spring.application.name=springboot-transactional

server.port=8182



spring.datasource.url=jdbc:mysql://localhost:3306/springBoot? useUnicode=true&characterEncoding=utf8&allowMultiQueries=true

spring.datasource.username=root

spring.datasource.password=123456

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.initialSize=5

spring.datasource.minIdle=5

spring.datasource.maxActive=20

spring.datasource.maxWait=60000

spring.datasource.timeBetweenEvictionRunsMillis=60000

spring.datasource.minEvictableIdleTimeMillis=300000

spring.datasource.validationQuery=SELECT 1 FROM DUAL

spring.datasource.testWhileIdle=true

spring.datasource.testOnBorrow=false

spring.datasource.testOnReturn=false

spring.datasource.poolPreparedStatements=true

spring.datasource.maxPoolPreparedStatementPerConnectionSize=20

spring.datasource.filters=stat,wall,log4j

spring.datasource.connectionProperties=druid.stat.mergeSql=true; druid.stat.slowSqlMillis=5000



logging.level.com.pancm.dao=debug

Copy the code

The code

SpringBoot when using Transactional things, want to add @ EnableTransactionManagement notes on the main method statement development things, Annotate the public methods of the service layer with @Transactional(Spring) annotations.

Use Example 1

First let’s look at how the @Transactional annotation can be used, simply by adding it to a public method that needs to be added. But doing so requires you to throw an exception and let Spring control it.

Code examples:



 @Transactional

 public boolean test1(User user) throws Exception {

  long id = user.getId();

  System.out.println("Query data 1:" + udao.findById(id));

  // If the primary key ID conflicts with the primary key ID, check whether the primary key can be rolled back

  udao.insert(user);

  System.out.println("Query data 2:" + udao.findById(id));

  udao.insert(user);

  return false;

 }

Copy the code

Use Example 2

If we want to handle exceptions ourselves while using Transactional Transactional, we can manually roll back the Transactional. In the catch with TransactionAspectSupport. CurrentTransactionStatus (.) setRollbackOnly (); Method to manually roll back. However, it is important to note that an exception needs to be rolled back manually in the first place, before the exception is thrown!

Code examples:



 @Transactional

 public boolean test2(User user) {



  long id = user.getId();

  try {

   System.out.println("Query data 1:" + udao.findById(id));

   // If the primary key ID conflicts with the primary key ID, check whether the primary key can be rolled back

   udao.insert(user);

   System.out.println("Query data 2:" + udao.findById(id));

   udao.insert(user);

  } catch (Exception e) {

   System.out.println("An exception occurred. Roll back manually!");

   // Roll things back manually

   TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

   e.printStackTrace();

  }

  return false;

 }

Copy the code

Example 3

If you call another child method to perform a database operation when you use Transactional Transactional, but you want it to work, you can use the rollbackFor annotation or throw an exception from that child to be processed by the Transactional method. Submethods must also be public methods!

Code examples:



@Transactional

 public boolean test3(User user) {



  / *

* Roll back an exception to the child method

* /


  try {

   System.out.println("Query data 1:" + udao.findById(user.getId()));

   deal1(user);

   deal2(user);

   deal3(user);

  } catch (Exception e) {

   System.out.println("An exception occurred. Roll back manually!");

   // Roll things back manually

   TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

   e.printStackTrace();

  } 

  return false;



 }



 public void deal1(User user) throws SQLException {

  udao.insert(user);

  System.out.println("Query data 2:" + udao.findById(user.getId()));

 }



 public void deal2(User user)  throws SQLException{

  if(user.getAge()<20) {

   / / SQL exceptions

   udao.insert(user);

  }else{

   user.setAge(21);

   udao.update(user);

   System.out.println("Query data 3:" + udao.findById(user.getId()));

  }

 }





 @Transactional(rollbackFor = SQLException.class)

 public void deal3(User user)  {

  if(user.getAge()>20) {

   / / SQL exceptions

   udao.insert(user);

  }



 }

Copy the code

Example 4

If we don’t want to use the Transactional annotation @Transactional and want to do Transactional control ourselves (programming Transactional management), but we don’t want to write that much code ourselves, You can use springboot DataSourceTransactionManager and TransactionDefinition in these two classes to use, can achieve manually control the commit rollback of things. Make sure that the object is opened but not committed. If it is not opened or committed, an exception will occur in the catch.

Code examples:



 @Autowired

 private DataSourceTransactionManager dataSourceTransactionManager;

 @Autowired

 private TransactionDefinition transactionDefinition;



    public boolean test4(User user) {

  / *

* Manual control of things

* /


  TransactionStatus transactionStatus=null;

  boolean isCommit = false;

  try {

   transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

   System.out.println("Query data 1:" + udao.findById(user.getId()));

   // Add/modify

   udao.insert(user);

   System.out.println("Query data 2:" + udao.findById(user.getId()));

   if(user.getAge()<20) {

    user.setAge(user.getAge()+2);

    udao.update(user);

    System.out.println("Query data 3:" + udao.findById(user.getId()));

   }else {

    throw new Exception("Simulate an exception!");

   }

   // Manually commit

   dataSourceTransactionManager.commit(transactionStatus);

   isCommit= true;

   System.out.println("Manual submission succeeded!");

   throw new Exception("Simulate the second exception!");



  } catch (Exception e) {

   // Roll back if not committed

   if(! isCommit){

    System.out.println("An exception occurred. Roll back manually!");

    // Roll things back manually

    dataSourceTransactionManager.rollback(transactionStatus);

   }

   e.printStackTrace();

  }

  return false;

 }



Copy the code

The above examples are quite common and can basically meet our daily use of things. There is also a control method of things in Spring, which is to set breakpoints to roll back. However, this method has not been personally verified, and its reliability needs to be confirmed. The usage method is as follows:

 Object savePoint =null;

 try{

 // Set the rollback point

 savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();

 }catch(Exception e){

  // An exception occurs and is rolled back to savePoint.

  TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);

 }

Copy the code

With the above examples in mind, let’s take a look at some of the main classes.

First, the entity class:

Entity class

Again, the all-purpose user table



 public class User {

  

   private Long id;

 

   private String name;

   

   private Integer age;

   

  / / getter and setter slightly

  

 }

Copy the code

The Controller control layer

Then there is the control layer, the control layer piece I did the last query, used to verify that things are successful!

The control layer code is as follows:



 @RestController

 @RequestMapping(value = "/api/user")

 public class UserRestController {

 

  @Autowired

     private UserService userService;

  

  @Autowired

  private UserDao userDao;

  

 

  @PostMapping("/test1")

     public boolean test1(@RequestBody User user) {

      System.out.println("Request parameters: + user);

   try {

    userService.test1(user);

   } catch (Exception e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

   }

   System.out.println("Last query data :" + userDao.findById(user.getId()));

         return true;

     }

     

  @PostMapping("/test2")

     public boolean test2(@RequestBody User user) 

      System.out.println("Request parameters: + user);

   userService.test2(user);

   System.out.println("Last query data :" + userDao.findById(user.getId()));

         return true;

     }

    

  @PostMapping("/test3")

     public boolean test3(@RequestBody User user) 

      System.out.println("Request parameters: + user);

   userService.test3(user);

   System.out.println("Last query data :" + userDao.findById(user.getId()));

         return true;

     }

  

  @PostMapping("/test4")

     public boolean test4(@RequestBody User user) 

       System.out.println("Request parameters: + user);

   userService.test4(user);

   System.out.println("Last query data :" + userDao.findById(user.getId()));

         return true;

     }

 }



Copy the code

App entrance

And ordinary SpringBoot project is essentially the same, just need to add @ EnableTransactionManagement comments!

The code is as follows:



 @EnableTransactionManagement

 @SpringBootApplication

 public class TransactionalApp

 
{

   

     public static void main( String[] args )

     
{

   SpringApplication.run(TransactionalApp.class, args);

   System.out.println(The Transactional program is running...);

  

     }

 }

Copy the code

A functional test

After we start the program, to carry out the above several sample tests, here are the test examples corresponding to the above use examples, some examples need to test both sides to verify whether things work! Here we use Postman for testing!

Test Example 1

Two tests, the first without the @Transactional annotation, and the second!

First Test:

Comment out the @Transactional annotation! Use to make POST requests

http://localhost:8182/api/user/test1

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:18}

Data printed by the console:

Request parameters :User [id=1, name=xuwujing, age=18] Query data 1:null query data 2:User [id=1, name=xuwujing, [id=1, name=xuwujing, age=18] Duplicate entry ‘1’ for key ‘PRIMARY’

Second test:

Unannotate @Transactional!

Use to make POST requests

http://localhost:8182/api/user/test1

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:18}

Data printed by the console:

Request parameters :User [id=1, name=xuwujing, age=18] Query data 1:null query data 2:User [id=1, name=xuwujing, Age =18] Duplicate entry ‘1’ for key ‘PRIMARY

Note: the first test was written to the database before the second test id is deleted!

However, when the Transactional annotation was not implemented in the test, the data was rolled back even though it had already been written. From the above test case you can see that the test case one thing is already in effect!

Test Example 2

Because using the code in Example 2 is almost the same as using Example 1, the exception is under our own control!

Use to make POST requests

http://localhost:8182/api/user/test2

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:18}

Data printed by the console:

Request parameters :User [id=1, name= Xuwujing, age=18] Query data 1: NULL Query data 2:User [id=1, name= Xuwujing, age=18] An exception occurs and manual rollback is performed. Duplicate entry ‘1’ for key ‘PRIMARY’

Test Example 3

Because of the submethod calls made in Example 3, here we test twice, depending on different request conditions!

First Test:

Use to make POST requests

http://localhost:8182/api/user/test3

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:18}

Data printed by the console:

Request parameters :User [id=1, name= Xuwujing, age=18] Query data 1: NULL Query data 2:User [id=1, name= Xuwujing, age=18] An exception occurs and manual rollback is performed. Duplicate entry ‘1’ for key ‘PRIMARY’ Last queried data: NULL

Second test:

Use to make POST requests

http://localhost:8182/api/user/test3

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:21}

Data printed by the console:

Request parameters :User [id=1, name=xuwujing, age=21] Query data 1:null query data 2:User [id=1, name=xuwujing, age=21] Query data 3:User [id=1, Name =xuwujing2, age=21] An exception occurs. Perform manual rollback. Duplicate entry ‘1’ for key ‘PRIMARY’ last query data: NULL According to the above two tests, using the rollbackFor annotation or throwing the exception of this child method can make things work.

Test Example 4

Since we used example 4 to manually control things, here we do two tests, based on different request conditions!

First Test:

Use to make POST requests

http://localhost:8182/api/user/test4

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:18}

Data printed by the console:

Request parameters :User [id=1, name=xuwujing, age=18] Query data 1:null query data 2:User [id=1, name=xuwujing, age=18] Query data 3:User [id=1, Name =xuwujing2, age=20 Simulate the second exception! User [id=1, name=xuwujing, age=20]

Second test:

Select * from database where id = 1;

Use to make POST requests

http://localhost:8182/api/user/test4

The Body argument is:

{“id”:1,”name”:”xuwujing”,”age”:21}

Data printed by the console:

Request parameters :User [id=1, name= Xuwujing, age=21] Query data 1: NULL Query data 2:User [id=1, name= Xuwujing, age=21] An exception occurs and manual rollback is performed. Simulate an exception! Last query data :null

Based on the above two tests, we can see that using manual controls is completely ok, as long as the thing is committed, even if an exception occurs later it does not affect the previous write! Rollback can also be done if an exception occurs in the scope of control or something like that!

Sample test diagram:



other

Reference: https://www.cnblogs.com/yepei/p/4716112.html

The project address

SpringBoot things Transaction project address: https://github.com/xuwujing/springBoot-study/tree/master/springboot-transactional

SpringBoot entire collection address:

https://github.com/xuwujing/springBoot-study