First encounter in Spring transaction learning notes

preface

In introduction to database transactions and learning notes on MySQL transactions (I), we have discussed transaction related concepts, transaction related operations, how to start a transaction, rollback, etc. How should we do the transaction operation in the program. In Java, transactions are controlled through JDBC API. This method is relatively primitive. The shadow of spring framework is generally indispensable in modern Java Web field. Spring provides us with an introduction scheme of controlling transactions. Let's introduce how to control transactions in spring. It is recommended to pre read this article before reading it:

  • Agent mode - AOP introduction
  • Welcome to the Spring era (II) biography of AOP in Shangzhu country

Declarative transaction: @ transitional annotation

Simple use example

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired
    private StudentInfoDao studentInfoDao;
    
    @Transactional(rollbackFor = Exception.class) // Represents that any Exception and its subclasses will be rolled back
    @Override
    public void studyTransaction() {
        studentInfoDao.updateById(new StudentInfo());
    }
}

This is an elegant transaction control scheme provided by Spring, but there is a small hole here. If the modifier of the method is not public, then @ transitional will fail. The reason is that Spring intercepts classes and methods annotated with @ Transactional through TransactionInterceptor,

Note that TransactionInterceptor implements the MethodInterceptor interface. If you are familiar with Spring, you can wait until it is a surround notification. When the method is to be executed, we can enhance this class and do some work before and after the method is executed.

The method call chain is as follows:

I shed tears when I learned the truth. I remember which interviewer asked me during my interview. At that time, I didn't know that Spring's transaction interceptor would operate like this, because I subconsciously thought that the principle of AOP is dynamic agent, and I can act as an agent no matter what method. I don't think the comments in the @ transitional annotation say why the method modifiers can take effect.

Attribute selection

Above, we only use one attribute of @ transitional, rollbackFor, which is used to control the rollback of the method in case of any exception. Now let's go to @ transitional and have a simple look at the other attributes:

  • value and transactionManager are synonyms for specifying that the transaction manager is available from version 4.2

Database transactions are controlled by the framework in the Java field and have different implementations. For example, Jdbc transaction, Hibernate transaction, etc

Spring carries out unified abstraction and forms two sub top-level interfaces of transaction manager, platform TransactionManager and reactivetransactionmanager. The two classes inherit from TransactionManager

When we integrate with Spring, if there is a connection pool to manage connections, Spring has a DataSourceTransactionManager to manage transactions.

If you use spring data JPA, spring data JPA also comes with a JPA transformation manager

If you use Spring Boot JDBC starter, Spring Boot will inject DataSourceTransactionManager as the transaction manager by default. If Spring Boot starter data JPA is used, Spring Boot will adopt JPA transaction manager by default.

  • label 5.3 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.

Define a transaction tag to describe some special transactions to be specially processed by some pre-defined transaction managers.

  • Propagation behavior

    • REQUIRED

      The default option is to create one if there is no transaction in the current method, and join it if there is a transaction in the current method.

    • SUPPORTS

      The current transaction is supported. If there is no current transaction, it will be executed in a non transaction manner.

    • MANDATORY

      Use the transaction of the current method. If there is no transaction in the current method, an exception will be thrown.

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

      result:

      <img src=" https://tva3.sinaimg.cn/large/006e5UvNly1gzk1u5wbqhj314g03zq8d.jpg "ALT =" threw an exception "style =" zoom: 200%; " />

        @Transactional(rollbackFor = Exception.class)
        @Override
         public void testTransaction() {
              studyTransaction(); // So you won't report an error
         }
    • REQUIRES_NEW

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

      Create a new transaction. If it is already in a transaction, suspend the transaction to which it belongs. It has a similar name to the attribute of EJB transaction.

      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 will recognize this attribute, and the pending attribute is only recognized by JTA transaction manager transaction manager. (there is also an understanding of suspension that do not commit first and wait for other transactions to commit before committing. I think this understanding is also correct. Jtatractionmanager is a distributed transaction manager.)

      So I measured that there was no hang. Now let's see if we can start a new transaction.

      SELECT TRX_ID FROM information_schema.INNODB_TRX  where TRX_MYSQL_THREAD_ID = CONNECTION_ID(); // You can view the transaction ID

      I tested in myBatis and output the transaction ID s of two methods:

        @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
         @Override
          public void studyTransaction() {
              // Execute any 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() {
              // Execute any statement first, otherwise no transaction ID will be generated
              studentInfoDao.selectById(1);
              System.out.println(studentInfoDao.getTrxId());
              studyTransaction();
          }

      result:

      Most other blogs on the Internet start a transaction, but it doesn't seem to be true now. But when someone does a test on the Internet, a rollback occurs. The principle of the test method is that testTransaction() executes to update the database, studyTransaction also updates the database, and studyTransaction throws an exception to see whether it is rolled back. Let's use another test, testTransaction update, See if it can be found in studyTransaction. If it can be found in a transaction. If the update cannot be found, it means that it is no longer in a transaction.

      @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
      @Override
      public void studyTransaction() {
          // Execute any 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() {
          // Execute any statement first, otherwise no transaction ID will be generated
          Student student = new Student();
          student.setId(1);
          student.setNumber("LLL");
          studentInfoDao.updateById(student);
          studyTransaction();
      }

      Then LLL is not output. It seems that a new transaction has been started.

    • 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).

      Run in a non transactional manner and suspend if there is a transaction in the current method. Only supported by jtatractionmanager.

          @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
          @Override
          public void studyTransaction() {
              // Execute any 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() {
              // Execute any statement first, otherwise no transaction ID will be generated
              studentInfoDao.selectById(1);
              System.out.println("testTransactiond Business of Id: "+studentInfoDao.getTrxId());
              studyTransaction();
          }

      Verification results:

      It seems that it has been added to testTransaction and does not run in a non transactional way. I won't give up. I'll try again.

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

      Output result: testTransaction method outputs bit cccc. It is indeed running in a non transactional manner.

    • NEVER

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

      Run in a non transactional manner. Throw an exception if there is a transaction in the current method.

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

      Is it because I didn't execute the update statement? I found that the same is true when I wrote the update statement. The original reason is that the two methods call the studyTransaction method in one class and in another interface implementation class. Exceptions will be thrown as follows:

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

      The following exception will be thrown:

      Is this a transaction failure? Let's discuss it in the following transaction failure scenarios.

    • 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, it is treated as a sub transaction of the transaction, which is similar to REQUIRED. Note that in fact, this feature is only supported by some special transaction managers. Data source transaction manager can be used out of the box.

      How do you understand this NESTED sub transaction? Do you remember the savepoint mentioned in our MySQL transaction learning notes (I) first encounter? The meaning of "saved" is "saved". Suppose method a calls method B, the propagation behavior of method a is REQUIRED, and method B is "saved". A calls B, and B has an exception, which will only roll back the behavior of B method, and a is not involved.

      @Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
      @Override
      public void studyTransaction() {
          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() {
          // Execute any statement first, otherwise no transaction ID will be generated
          Student student = new Student();
          student.setId(1);
          student.setNumber("LLL");
          studentInfoDao.updateById(student);
          studyTransaction();
      }

      In this way, we will find that the whole is rolled back because the exception thrown by the studytransaction method is also handled by testTransaction() But if you live in catch, you will find the whole rollback, but if you call studyTransaction and testTransaction in another service, you can make partial rollback. 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() {
              Student student = new Student();
              student.setId(1);
              student.setNumber("jjjjj");
              studentInfoDao.updateById(student);
              try {
                  studentService.studyTransaction();
              }catch (Exception e){
      
              }
          }

      Or when calling studyTransaction, injecting it yourself can also have the effect of local rollback, 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() {
              Student student = new Student();
              student.setId(1);
              student.setNumber("qqqq");
              studentInfoDao.updateById(student);
              try {
                  studentService.studyTransaction();
              }catch (Exception e){
      
              }
          }
        }
  • isolation level

It is an enumeration value, which has been discussed in MySQL transaction learning notes (I) first encounter. You can specify the isolation level through this attribute. There are four in total:

  • DEFAULT follows the isolation level of the database
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

  • Timeout timeout

If it has not been committed for more than a long time, it will be rolled back automatically.

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

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

Transaction failure scenario

In fact, we have discussed a transaction failure scenario, that is, the method modified by the method is private. If you want to take effect at the private method level, you need to turn on the AspectJ proxy mode. It's also troublesome to start. Search: Spring Boot tutorial (20) – using AspectJ to realize the internal call of AOP. It talks about how to start it, so I won't repeat it here.

Another is to set not in the transaction propagation behavior_ SUPPORTED.

For the transaction manager we discussed above, if the transaction manager is not included in the jurisdiction of Spring, the method @ Transactional will not take effect.

The method in the class is called by itself, as follows:

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

In this way, rollback will not occur. The reason is still from the proxy mode. The reason why we can manage transactions by adding transaction annotations to methods and classes is essentially that Spring can help us enhance. When we call methods with @ Transactional, in fact, we call proxy classes. For example, for methods without transaction annotations, Spring does not use proxy classes when calling. If a method with transaction annotation calls a method without transaction annotation, it will not fail. The reason is the same. In fact, what @ Transactional calls is a proxy class, which starts the transaction.

  • The corresponding database does not support transactions. For example, in MySQL, it is the engine MyIsam specified in the database table.
  • The transaction annotation does not use a database connection, that is, multi-threaded call. Like this:
   @Override
    @Transactional
    public void studyTransaction() {
       // Two threads may use different connections, which is similar to MySQL's opening two black windows. Naturally, they do not affect each other.
        new Thread(()-> studentInfoDao.insert(new Student())).start();
        new Thread(()-> studentInfoDao.insert(new Student())).start();
    }

Introduction to programming affairs

In contrast to declarative transactions, declarative transactions are like automatic files. Spring helps us start, commit and roll back transactions. Programming transactions are like automatic files. We start, commit and rollback ourselves. If there are some code blocks that need to use transactions, adding transactions to methods is too cumbersome to add methods, and adding transactions to extracted methods will lead to failure, then we can consider using programmatic transactions here Two types of programmatic transaction management are provided under the spring framework:

  • TransactionTemplate(Spring will automatically help us roll back and release resources)
  • Platformtransactionmanager (we need to release resources manually)
@Service
public class StudentServiceImpl implements StudentService{

    @Autowired
    private StudentInfoDao studentInfoDao;


    @Autowired
    private TransactionTemplate transactionTemplate;


    @Autowired
    private PlatformTransactionManager platformTransactionManager;

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

    @Override
    public void testTransaction() {
        // defaultTransactionDefinition can set attributes such as isolation level and propagation behavior
        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() {
        Student student = new Student();
        student.setId(1);
        student.setNumber("aaaaaaLLL111111qqqw");
        studentInfoDao.updateById(student);
        int i = 1 / 0;
    }
}

To sum up

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

  • Declarative transaction @ Transactional
  • Programming transaction template and PlatformTransactionManager

If you want to enjoy the transaction management traversal provided by Spring, you need to bring the transaction manager into the management scope of the container.

reference material

Keywords: Java

Added by Imperialdata on Sat, 26 Feb 2022 11:06:27 +0200