preface

In introduction to Database Transactions, MySQL Transaction Learning Notes (1), we have discussed the concept of transaction, transaction related operations, how to start a transaction, rollback, etc. So how do we do transactions in a program. In Java, transactions are controlled through the JDBC API. This approach is relatively primitive, and the modern Java Web world generally does not have the shadow of the Spring framework. Spring provides us with an introduction to control transactions. It is recommended to read this article before reading:

  • Proxy Patterns – An Introduction to AOP
  • Welcome to the Spring Era (ii) AOP biography

Declarative transactions: @transational annotation

Simple Usage Example

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired
    private StudentInfoDao studentInfoDao;
    
    @Transactional(rollbackFor = Exception.class) // any Exception encountered and its subclasses will be rolled back
    @Override
    public void studyTransaction(a) {
        studentInfoDao.updateById(newStudentInfo()); }}Copy the code

This is an elegant way for Spring to control transactions, but the catch is that @transational is invalidated if the method modifier is not public. The reason for this is that Spring intercepts @Transactional classes and methods via TransactionInterceptor.

Note that the TransactionInterceptor implements the MethodInterceptor interface. If you’re familiar with Spring, until this is a wrap notification, you can enhance the class to do some work before and after the method is executed.

The method call chain is as follows:

Tears came to my eyes when I learned the truth. I remember one interview where I was asked by an interviewer who didn’t know that Spring’s transaction interceptor could do this, because I had a subconscious feeling that THE principle of AOP is dynamic proxy and I could proxy any method. I didn’t see the comment in the @transational annotation saying that any method modifier would work.

Choose properties about

We’ve used only one @transational rollbackFor property above, which controls method rollback in case of an exception. Now let’s take a quick look at @transational’s other properties:

  • Value and transactionManager are synonyms used to specify that the transactionManager is available starting with version 4.2

Database transactions are controlled by Java domain frameworks and have different implementations. Such as Jdbc transactions, Hibernate transactions, and so on

Spring has carried on the unification of abstract, formed the PlatformTransactionManager, ReactiveTransactionManage two top interface transaction manager. Two classes inherit from TransactionManager.

We were using the Spring integration, if there is a connection pool to manage connections, Spring DataSourceTransactionManager to management affairs.

If you are using Spring Data JPA, Spring Data JPA also comes with a JpaTransationManager.

If you are using a Spring – the boot – JDBC – the starter, then Spring inject DataSourceTransactionManager boot will default, as a transaction manager. If spring-boot-starter-data-jPA is used, spring Boot uses JpaTransactionManager by default.

  • Label 5.3 is available

Defines zero (0) or more transaction labels. Labels may be used to describe a transaction, and they can be evaluated by individual transaction managers. Labels may serve a solely descriptive purpose or map to pre-defined transaction manager-specific options.

Defines a transaction label that describes special transactions to be handled by predefined transaction managers.

  • Propagation behavior

    • REQUIRED

      Default option to create a transaction if the current method does not have one, or join if the current method has one.

    • SUPPORTS

      Current transactions are supported, and if there are none, they are executed nontransactionally.

    • MANDATORY

      Transactions that use the current method and throw an exception if the current method does not have a transaction.

      @Transactional(propagation = Propagation.MANDATORY)
      @Override
      public void studyTransaction(a) {
          Student studentInfo = new Student();
          studentInfo.setId(1);
          studentInfo.setName("ddd");
          studentInfoDao.updateById(studentInfo);
      }
      Copy the code

      Results:

        @Transactional(rollbackFor = Exception.class)
        @Override
         public void testTransaction(a) {
              studyTransaction(); // So there is no error
         }
      Copy the code
    • REQUIRES_NEW

      Create a new transaction, and suspend the current transaction if one exists. Analogous to the EJB transaction attribute of the same name.

      Creates a new transaction, suspends the transaction to which it belongs if it is already in one, with a name similar to the EJB transaction’s property.

      NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available to it (which is server-specific in standard Java EE).

      Note that not all transaction managers recognize this property, and the suspend property is only recognized by the JtaTransactionManager transaction manager. Pending is also understood as not committing and waiting for other transactions to commit. I think this understanding is also correct. JtaTransactionManager is a distributed transaction manager,)

      So I measured, there is no hanging phenomenon. Now let’s see if we started a new transaction.

      SELECT TRX_ID FROM information_schema.INNODB_TRX  whereTRX_MYSQL_THREAD_ID = CONNECTION_ID(); // You can view the transaction IDCopy the code

      MyBatis: transaction ID of two methods:

        @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
         @Override
          public void studyTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              studentInfoDao.selectById(1);
              System.out.println(studentInfoDao.getTrxId());
          }
      
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              studentInfoDao.selectById(1);
              System.out.println(studentInfoDao.getTrxId());
              studyTransaction();
          }
      Copy the code

      Results:

      TestTransaction () updates the database, studyTransaction updates the database, and studyTransaction updates the database. The studyTransaction method throws the exception to see if it can be rolled back. Let’s use another test, testTransaction, to see if it can be checked if it should be checked in a transaction. If the update is not found then it is no longer in a transaction.

      @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
      @Override
      public void studyTransaction(a) {
          // Execute either statement first, otherwise no transaction ID will be generated
          System.out.println(studentInfoDao.selectById(2).getNumber());
          System.out.println(studentInfoDao.getTrxId());
      }
      
      @Transactional(rollbackFor = Exception.class)
      @Override
      public void testTransaction(a) {
          // Execute either statement first, otherwise no transaction ID will be generated
          Student student = new Student();
          student.setId(1);
          student.setNumber("LLL");
          studentInfoDao.updateById(student);
          studyTransaction();
      }
      Copy the code

      Then there is no output LLL, it looks like a new transaction.

    • NOT_SUPPORTED

      Execute non-transactionally, suspend the current transaction if one exists. Analogous to EJB transaction attribute of the same name. NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available to it (which is server-specific in standard Java EE).

      Runs nontransactionally and suspends if the current method has a transaction. Supported only by JtaTransactionManager.

          @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
          @Override
          public void studyTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              System.out.println("StudyTransaction method transaction ID:"+studentInfoDao.getTrxId());
          }
      
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              studentInfoDao.selectById(1);
              System.out.println("Transaction Id of testTransactiond:"+studentInfoDao.getTrxId());
              studyTransaction();
          }
      Copy the code

      Verification results:

      It seems to have joined testTransaction and is not running in a non-transactional way, so I’m going to try again.

        @Override
          public void studyTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              Student student = new Student();
              student.setId(1);
              student.setNumber("cccc");
              studentInfoDao.updateById(student);
              // If the method is running nontransactionally, the other methods should be able to query the data immediately after the method executes.
              try {
                  TimeUnit.SECONDS.sleep(30);
              } catch(InterruptedException e) { e.printStackTrace(); }}@Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction(a) {
              // Execute either statement first, otherwise no transaction ID will be generated
              System.out.println(studentInfoDao.selectById(1).getNumber());
          }
      Copy the code

      Output: The testTransaction method outputs the bit CCCC. It does run in a non-transactional manner.

    • NEVER

      Execute non-transactionally, throw an exception if a transaction exists

      Runs in a non-transactional manner, throwing an exception if the current method has a transaction.

      	@Transactional(propagation = Propagation.NEVER)
          @Override
          public void studyTransaction(a) {
              System.out.println("hello world");
          }
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction(a) {
              studyTransaction();
          }
      Copy the code

      I didn’t throw an exception, because I didn’t update the statement? The same is true for the studyTransaction method (studyTransaction), which is called from the same class in the other interface implementation class, and raises an exception as follows:

      @Service
      public class StuServiceImpl implements  StuService{
          @Autowired
          private StudentService studentService;
          
          @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
          @Override
          public void test(a) { studentService.studyTransaction(); }}Copy the code

      The following exception is thrown:

      Is this a transaction failure? We will discuss the failure scenario of the following transactions.

    • NESTED

      Execute within a nested transaction if a current transaction exists, behave like REQUIRED otherwise. There is no analogous feature in EJB. Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to the JDBC DataSourceTransactionManager. Some JTA providers might support nested transactions as well. See Also:org.springframework.jdbc.datasource.DataSourceTransactionManager

      If a transaction currently exists, consider it a child of that transaction, similar to REQUIRED. Note the fact that this feature is only supported by some special transaction managers. In DataSourceTransactionManager can do it out of the box.

      How to understand the nested subtransactions? Remember the savepoint mentioned in MySQL Transaction Notes (1)? NESTED means that if method A calls method B, the propagation behavior of method A is REQUIRED, while that of method B is NESTED. When A calls B, an exception occurs in B, and only the behavior of B’s method is rolled back, but A is not involved.

      @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
      @Override
      public void studyTransaction(a) {
          Student student = new Student();
          student.setId(1);
          student.setNumber("bbbb");
          studentInfoDao.updateById(student);
          int i = 1 / 0;
      }
      @Transactional(rollbackFor = Exception.class)
      @Override
      public void testTransaction(a) {
          // Execute either statement first, otherwise no transaction ID will be generated
          Student student = new Student();
          student.setId(1);
          student.setNumber("LLL");
          studentInfoDao.updateById(student);
          studyTransaction();
      }
      Copy the code

      The whole thing is still rolled back because the exception thrown by the studyTransaction method is also handled by testTransaction(). However, if you call studyTransaction and testTransaction in another service, you can do a partial rollback. Something like this:

      @Service
      public class StuServiceImpl implements  StuService{
          @Autowired
          private StudentService studentService;
      
          @Autowired
          private StudentInfoDao studentInfoDao;
      
          @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
          @Override
          public void test(a) {
              Student student = new Student();
              student.setId(1);
              student.setNumber("jjjjj");
              studentInfoDao.updateById(student);
              try {
                  studentService.studyTransaction();
              }catch (Exception e){
      
              }
          }
      Copy the code

      You can also perform a local rollback by injecting yourself into studyTransaction, as follows:

      @Service
      public class StudentServiceImpl implements StudentService.ApplicationContextAware {
      
          @Autowired
          private StudentInfoDao studentInfoDao;
      
          @Autowired
          private StudentService studentService
          
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction(a) {
              Student student = new Student();
              student.setId(1);
              student.setNumber("qqqq");
              studentInfoDao.updateById(student);
              try {
                  studentService.studyTransaction();
              }catch (Exception e){
      
              }
          }
        }
      Copy the code
  • Isolation Isolation level

Is an enumerated value, which we discussed in MySQL Transaction Notes (1). You can specify the isolation level by using this property. There are four:

  • DEFAULT follows the isolation level of the database
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE
  • Timeout Indicates the timeout period.

If it has not been committed for more than a period of time, it is automatically rolled back.

  • rollbackFor

  • rollbackForClassName

  • noRollbackFor

  • noRollbackForClassName

 @Transactional(noRollbackForClassName = "ArithmeticException",rollbackFor = ArithmeticException.class )
   @Override
    public void studyTransaction(a) {
        Student studentInfo = new Student();
        studentInfo.setId(1);
        studentInfo.setName("ddd");
        studentInfoDao.updateById(studentInfo);
        int i = 1 / 0;
    }
Copy the code

NoRollback and RollbackFor specify the same class, and RollbackFor takes precedence.

Transaction failure scenario

Above we have actually discussed a transactional invalidation scenario where methods are decorated in a way that is private. If you want the private method level to work, you need to turn on the AspectJ proxy mode. Spring Boot tutorial (20) — AOP internal invocation with AspectJ, which explains how to open, here will not be described.

Another is to set NOT_SUPPORTED in the transaction propagation behavior.

The Transactional method we discussed above will not work if the transaction manager is not included in Spring’s jurisdiction.

Class methods call themselves, like this:

 @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public void studyTransaction(a) {
        Student student = new Student();
        student.setId(1);
        student.setNumber("aaaaaaLLL");
        studentInfoDao.updateById(student);
        int i = 1 / 0;
    }
  @Override
  public void testTransaction(a) {
        studyTransaction();
 }
Copy the code

This still won’t roll back. Spring makes it possible to use Transactional annotations on methods and classes to manage transactions. When you call a @Transactional method, you actually call a proxy class, like a method without Transactional annotations. Spring does not invoke the proxy class. Transactional methods that call non-transactional methods are not invalidated for the same reason. Calls called by @Transactional are actually called by a proxy class, which opens the transaction.

  • The corresponding database is not enabled to support transactions, such as in MySQL database table specified engine MyIsam.
  • Annotations marked with transactions do not use a database connection, that is, a multithreaded call. Something like this:
   @Override
    @Transactional
    public void studyTransaction(a) {
       // Two threads may use different connections, similar to MySQL with two black Windows, which do not affect each other.
        new Thread(()-> studentInfoDao.insert(new Student())).start();
        new Thread(()-> studentInfoDao.insert(new Student())).start();
    }
Copy the code

Introduction to programmatic transactions

In contrast to declarative transactions, declarative transactions are like automatic gear, with Spring helping us start, commit, and roll back transactions. Programmatic transactions, on the other hand, are like automatic gears that we start, commit, and roll back. If there are blocks of code that require transactions, adding transactions to methods is too cumbersome to do, and adding transactions to extracted methods will fail, then we can consider using programmatic transactions here. The Spring framework provides two types of programmatic transaction management:

  • TransactionTemplate(Spring automatically rolls back freed resources for us)
  • PlatformTransactionManager (we need to manually release resources)
@Service
public class StudentServiceImpl implements StudentService{

    @Autowired
    private StudentInfoDao studentInfoDao;


    @Autowired
    private TransactionTemplate transactionTemplate;


    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Override
    public void studyTransaction(a) {
        // transactionTemplate can set properties such as isolation level, propagation behavior, etc
        String result = transactionTemplate.execute(status -> {
            testUpdate();
            return "AAA";
        });
        System.out.println(result);
    }

    @Override
    public void testTransaction(a) {
        / / defaultTransactionDefinition can set the isolation level, such as propagation behavior properties
        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus status = platformTransactionManager.getTransaction(defaultTransactionDefinition);
        try {
            testUpdate();
        }catch (Exception e){
            // Specify rollback
            platformTransactionManager.rollback(status);
        }
        studyTransaction();/ / submit
        platformTransactionManager.commit(status);
    }

    private void testUpdate(a) {
        Student student = new Student();
        student.setId(1);
        student.setNumber("aaaaaaLLL111111qqqw");
        studentInfoDao.updateById(student);
        int i = 1 / 0; }}Copy the code

To summarize

Spring provides unified transaction management for us. There are roughly two ways to manage transactions provided by Spring:

  • Transactional Transactional
  • Programmatic transaction TransactionTemplate and PlatformTransactionManager

If you want to enjoy the transaction management traversal provided by Spring, you need to include the transaction manager in the container’s management scope.

The resources

  • Explanation of Spring’s transaction management PlatformTransactionManager www.jianshu.com/p/903c01cb2…
  • To reassure spring nested transaction blog.csdn.net/z69183787/a…
  • In what situations do Spring transactions fail? www.zhihu.com/question/50…
  • In what situations do Spring transactions fail? www.zhihu.com/question/50…
  • Spring zhuanlan.zhihu.com/p/398713906 programmatic transaction management