头图

foreword

In "Introduction to Database Transactions" and "MySQL Transaction Study Notes (1)", we have discussed transaction-related concepts, transaction-related operations, how to start a transaction, rollback, etc. So how do we do business operations in the program? In Java, the JDBC API is used to control transactions. This method is relatively primitive. In the modern Java Web field, the shadow of the Spring framework is generally indispensable. Spring provides us with an introduction scheme to control transactions. Let's introduce the Spring Framework. How to control transactions. It is recommended to pre-read before reading this article:

  • "Agent Mode - Introduction to AOP"
  • "Welcome to Spring Times (2) AOP Biography of Shangzhu Kingdom"

Declarative transactions: @Transational annotation

Simple usage example

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired
    private StudentInfoDao studentInfoDao;
    
    @Transactional(rollbackFor = Exception.class) // 代表碰见任何Exception和其子类都会回滚
    @Override
    public void studyTransaction() {
        studentInfoDao.updateById(new StudentInfo());
    }
}

This is an elegant solution for transaction control provided by Spring, but there is a small pit here that if the modifier of the method is not public, @Transational will fail. The reason is that Spring uses TransactionInterceptor to intercept classes and methods annotated with @Transactional.

Spring中的事务控制

Note that TransactionInterceptor implements the MethodInterceptor interface. If you are familiar with Spring, you can know that this 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:

事务拦截调用链

计算事务属性

When I learned the truth, my tears fell. I remember when I was interviewing, which interviewer asked me. At that time, I didn't know that Spring's transaction interceptor would have such an operation, because I subconsciously felt that, The principle of AOP is dynamic proxy, no matter what method, I can proxy. I don't see the annotation in the @Transational annotation, so why the method modifier can take effect.

Featured Lectures

Above we only used one attribute of @Transational, rollbackFor, which is used to control the rollback of the method in the event of an exception. Now let's go to @Transational to simply see what other attributes are:

  • value and transactionManager are synonyms used to specify the transaction manager available since version 4.2

The transaction of the database is controlled by the framework in the Java domain, and there are different implementations. Such as Jdbc transactions, Hibernate transactions, etc.

Spring has carried out a unified abstraction, and formed the second top-level interface of two transaction managers, PlatformTransactionManager and ReactiveTransactionManage. Both classes inherit from TransactionManager.

事务管理器顶层接口

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

DataSourceTransactionManager

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

JpaTransationalManager

If you are using Spring-boot-jdbc-starter, Spring Boot will inject DataSourceTransactionManager by default as a transaction manager. If spring-boot-starter-data-jpa is used, then Spring Boot will use 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.

Define a transaction tag to describe some special transactions to be specially handled by some predefined transaction managers.

  • Propagation

    • REQUIRED

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

      Support the current transaction, if there is no current transaction, it will be executed in a non-transactional way.
    • MANDATORY

      Use the current method's transaction, or throw an exception if no transaction exists for the current method.
      @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="An exception was thrown" style="zoom:200%;" />

        @Transactional(rollbackFor = Exception.class)
        @Override
         public void testTransaction() {
              studyTransaction(); // 这样就不会报错了
         }
    • 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 currently in a transaction, suspend the owning transaction, which has a similar name to the property of the 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 property, the pending property is only recognized by the JtaTransactionManager transaction manager. (There is also an understanding of suspension that does not commit first, waits for the commit of other transactions, and then commits. I think this understanding is also correct. JtaTransactionManager is a distributed transaction manager,)

      So I tested, there is no hang phenomenon. Now let's see if a new transaction is started.

      SELECT TRX_ID FROM information_schema.INNODB_TRX  where TRX_MYSQL_THREAD_ID = CONNECTION_ID(); // 可以查看事务ID

      I did a test in myBatis and output the transaction ID of the two methods:

        @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
         @Override
          public void studyTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              studentInfoDao.selectById(1);
              System.out.println(studentInfoDao.getTrxId());
          }
      
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              studentInfoDao.selectById(1);
              System.out.println(studentInfoDao.getTrxId());
              studyTransaction();
          }

      Result: 也没开启事务

      Most of the other blogs on the Internet will start a transaction. It seems that there is no such thing now, but when someone is doing a test on the Internet, a rollback occurs. The principle of the test method is that testTransaction() executes to update the database, and studyTransaction also updates the database, studyTransaction The method throws an exception to see if it is rolled back. Let's use another test, testTransaction update, to see if it can be found in studyTransaction, if it should be found in a transaction. If the update is not found, it means that it is no longer in a transaction.

      @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
      @Override
      public void studyTransaction() {
          // 先执行任意一句语句,不然不会有事务ID产生
          System.out.println(studentInfoDao.selectById(2).getNumber());
          System.out.println(studentInfoDao.getTrxId());
      }
      
      @Transactional(rollbackFor = Exception.class)
      @Override
      public void testTransaction() {
          // 先执行任意一句语句,不然不会有事务ID产生
          Student student = new Student();
          student.setId(1);
          student.setNumber("LLL");
          studentInfoDao.updateById(student);
          studyTransaction();
      }

      Then there is no LLL output, it seems that a new transaction is indeed 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).

      Runs non-transactionally, suspending if a transaction exists for the current method. Only supported by JtaTransactionManager.

          @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
          @Override
          public void studyTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              System.out.println("studyTransaction方法的事务ID: "+studentInfoDao.getTrxId());
          }
      
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              studentInfoDao.selectById(1);
              System.out.println("testTransactiond的事务Id: "+studentInfoDao.getTrxId());
              studyTransaction();
          }

      Verification result: NotSupported

      It seems to be added to the testTransaction, not running in a non-transactional way, I don't give up, I will try again.

        @Override
          public void studyTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              Student student = new Student();
              student.setId(1);
              student.setNumber("cccc");
              studentInfoDao.updateById(student);
              // 如果是以非事务运行,那么方法执行完应当,别的方法应当立即能查询到这条数据。
              try {
                  TimeUnit.SECONDS.sleep(30);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction() {
              // 先执行任意一句语句,不然不会有事务ID产生
              System.out.println(studentInfoDao.selectById(1).getNumber());
          }

      Output: The testTransaction method outputs bits cccc. It is indeed running 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() {
              System.out.println("hello world");
          }
          @Transactional(rollbackFor = Exception.class)
          @Override
          public void testTransaction() {
              studyTransaction();
          }

      No exception was thrown, is it because I didn't execute the update statement? I found that the same thing happened when I wrote an update statement. Originally, these two methods were in one class, and the studyTransaction method was called in another interface implementation class, and an exception would 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:

      传播行为为NEVER

      So is this a transaction failure? We will discuss it in the failure scenario of the transaction below.

    • 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 regarded as a sub-transaction of the transaction, similar to REQUIRED. Note that in fact this feature is only supported by some special transaction managers. The DataSourceTransactionManager can do it out of the box.

      So how to understand this nested sub-transaction, do you still remember the save point mentioned in our "MySQL Transaction Study Notes (1) First Encounter"? This NESTED means save point. Assuming that method A calls method B, the propagation behavior of method A is REQUIRED, and method B is NESTED. A calls B, and an exception occurs in B, and only the behavior of the B method is rolled back, 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() {
          // 先执行任意一句语句,不然不会有事务ID产生
          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 even if you catch it, you will find that the entire rollback is still performed, but if you call studyTransaction successively in another service , testTransaction can do partial rollback. Like the following:

      @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 yourself can also have the effect of partial rollback, like the following:

      @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 isolation level

Is an enumeration value, we have discussed in "MySQL Transaction Study Notes (1) 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 submitted for a long time, it will be automatically rolled back.
  • 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 above, that is, the method modified by the method is private. If you want to take effect at the private method level, you need to enable AspectJ proxy mode. It is also more troublesome to open, know how to search: Spring Boot Tutorial (20) – Using AspectJ to implement AOP internal calls, which describes how to open it, so I won’t go into details here.

Then there is the set to NOT_SUPPORTED in the transaction propagation behavior.

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

The method in the class is called by itself, like the following:

 @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();
 }

Still no rollback will occur. The reason is still from the proxy mode. The reason why we can implement transaction management by adding transaction annotations to methods and classes is essentially that Spring helps us to enhance it. When we have @Transactional methods on calling methods , In fact, the proxy class is called. Like a method without transaction annotation, Spring does not use the proxy class when calling it. If a method with transaction annotation calls a method without transaction annotation, it will not fail. The reason is the same. The call is actually called by @Transactional, which is the proxy class, and the transaction is opened.

  • The corresponding database does not support transactions. For example, in MySQL, it is the engine MyIsam specified by the database table.
  • Annotated with a transaction does not use a database connection, which is a multi-threaded call. Like the following:
   @Override
    @Transactional
    public void studyTransaction() {
       // 两个线程可能使用不同的连接,类似于MySQL开了两个黑窗口,自然互相不影响。
        new Thread(()-> studentInfoDao.insert(new Student())).start();
        new Thread(()-> studentInfoDao.insert(new Student())).start();
    }

Introduction to Programmatic Transactions

In contrast to declarative transactions, declarative transactions are like automatic transmissions, and Spring helps us open, commit, and roll back transactions. Programmatic transactions, on the other hand, are like automatic transmissions, we open, commit, and roll back by ourselves. If there are some code blocks that need to use transactions, it is too cumbersome to add transactions to methods, and adding transactions to methods is no longer necessary, and adding them to extracted methods will lead to failure, then we can consider using programmatic transactions here. Spring Two kinds of programmatic transaction management are provided under the framework:

  • TransactionTemplate (Spring will automatically roll back and release resources for us)
  • PlatformTransactionManager (requires us 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() {
        // transactionTemplate可以设置隔离级别、传播行为等属性
        String result = transactionTemplate.execute(status -> {
            testUpdate();
            return "AAA";
        });
        System.out.println(result);
    }

    @Override
    public void testTransaction() {
        // defaultTransactionDefinition   可以设置隔离级别、传播行为等属性
        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus status = platformTransactionManager.getTransaction(defaultTransactionDefinition);
        try {
            testUpdate();
        }catch (Exception e){
            // 指定回滚
            platformTransactionManager.rollback(status);
        }
        studyTransaction();// 提交
        platformTransactionManager.commit(status);
    }

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

in conclusion

Spring manages transactions for us in a unified way. The ways of managing transactions provided by Spring can be roughly divided into two types:

  • Declarative transactions @Transactional
  • Programmatic Transaction TransactionTemplate and PlatformTransactionManager

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

References


北冥有只鱼
147 声望36 粉丝