preface

  • In the previous Article, Spring AOP Principles: I use my prioristic powers to help you understand the principles of @EnableAspectJAutoProxy AOP annotations, we learned how Spring finds aspects, how to find advice, how to generate proxy objects, and the order in which proxy objects are executed. Now, let’s take a look at Spring transactions again. This article is simple and suitable for getting started, mainly to learn how to use Spring transactions and related propagation mechanism features.

First, understand Spring’s transaction mechanism in the way of test cases

  • Case background: Take the transfer business of payment system as an example, our transfer business must be an atomic operation. A transfers money to B, A’s account is deducted, B’s account is added, and either they succeed or fail together.

1.1 Preview the test case project structure

  • The project Entry is entry.java

    public class Entry {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
            TransferService transferService = context.getBean(TransferService.class);
            // Call the transfer interface, avengerEug transfers 1 yuan to Zhangsan
            transferService.transfer("avengerEug"."zhangsan".new BigDecimal("1"));
        }
    
        @ComponentScan("com.eugene.sumarry.transaction")
        @Configuration
        @EnableTransactionManagement
        @EnableAspectJAutoProxy(exposeProxy = true)
        public static class AppConfig {
    
            @Bean
            public DataSource dataSource(a) {
                DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
                driverManagerDataSource.setUrl("JDBC: mysql: / / 127.0.0.1:3306 / transaction_test? useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true");
                driverManagerDataSource.setUsername("root");
                driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
                driverManagerDataSource.setPassword("");
                return driverManagerDataSource;
            }
    
            @Bean
            public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
                DataSourceTransactionManager manager = new DataSourceTransactionManager();
                manager.setDataSource(dataSource);
                return manager;
            }
    
            @Bean
            public JdbcTemplate jdbcTemplate(DataSource dataSource) {
                JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
                returnjdbcTemplate; }}}Copy the code
  • Transfer interface transfer.java

    public interface TransferService {
    
        // Transfer operation
        void transfer(String outAccountId, String inAccountId, BigDecimal amount);
    
        // Add money operation
        void incrementAmount(String accountId, BigDecimal amount);
    
    }
    Copy the code
  • Transfer interface implementation class transferImp.java

    @Component
    public class TransferServiceImpl implements TransferService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public void transfer(String outAccountId, String inAccountId, BigDecimal amount) {
            / / into the money
            this.incrementAmount(inAccountId, amount);
    
            / /... Various other business logic @extension1 can be added
            
            / / pay
            jdbcTemplate.update("UPDATE account SET amount = amount - ? WHERE id = ?", amount, outAccountId);
            
            / /... Various other business logic @extension2 can be added
        }
    
        @Override
        public void incrementAmount(String accountId, BigDecimal amount) {
            // Add the amount directly without considering any concurrency
            jdbcTemplate.update("UPDATE account SET amount = amount + ? WHERE id = ?", amount, accountId); }}Copy the code
  • Database & database table data initialization script

    #Creating a database
    CREATE DATABASE transaction_test;
    
    #Using a database
    USE transaction_test;
    
    #Create a table
    CREATE TABLE account(
      id VARCHAR(255) PRIMARY KEY,
      amount Decimal NULL
    );
    
    #Initialize data
    INSERT INTO account(id, amount) VALUES
    ('avengerEug', 100),
    ('zhangsan', 20);
    Copy the code
  • Project structure analysis:

    First, a Spring configuration class, AppConfig, is defined in the Entry class. In this configuration class, we specify the scan path for the Spring application, start transactions, enable AOP functionality and expose proxy objects, specify a DataSource, add a transaction manager, and initialize the JdbcTemplate.

    Secondly, the Transfer interface transfer. Java is defined to simulate the Transfer business.

    Finally, the initialization script of database and database table data is provided, which is convenient to build database environment quickly.

    The Transfer transfer interface method is called in the Entry entry. Java class to simulate an operation that avengerEug accounts to transfer money to Zhangsan accounts.

1.2 Use the @transactional annotation to enable transactions for the transfer interface

  • In the above case, our transfer interface is not transaction-capable, if I add additional business logic processing at @extension1 and throw an exception while processing that business logic. In this case, the amount of money in database inAccountId increases, but the amount of money in database outAccountId does not decrease. The above analysis shows that the transfer operation is an atomic operation. To do this, we can add the @Transactional annotation to the transfer interface to tell Spring that my transfer interface needs to enable transactions, and that subsequent transactions will be opened, committed, and rolled back by Spring.

  • Modify the TransferServiceImpl. Java class to look like this:

    @Component
    public class TransferServiceImpl implements TransferService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Transactional(rollbackFor = Exception.class)  / / @ 1
        @Override
        public void transfer(String outAccountId, String inAccountId, BigDecimal amount) {
            / / into the money
            this.incrementAmount(inAccountId, amount);
    
            / /... Various other business logic @extension1 can be added
            
            / / pay
            jdbcTemplate.update("UPDATE account SET amount = amount - ? WHERE id = ?", amount, outAccountId);
            
            / /... Various other business logic @extension2 can be added
        }
    
        @Override
        public void incrementAmount(String accountId, BigDecimal amount) {
            // Add the amount directly without considering any concurrency
            jdbcTemplate.update("UPDATE account SET amount = amount + ? WHERE id = ?", amount, accountId); }}Copy the code

    The code at @1 indicates that the Transfer interface is an atomic interface, and Spring will submit an instruction to MySQL to start the transaction before executing the Transfer interface. If an Exception of type Exception is thrown in the Transfer interface, Spring will send a rollback operation instruction to MySQL after catching the Exception; otherwise, it will send a transaction commit instruction. We can simply think of the execution process as the following pseudocode:

    try {
        // Start the transaction
        begin transaction;
        
        // Perform the transfer interface
        transfer();
        
        // Commit the transaction
        commit transaction;
    } catch(Exception ex) {
        // Catch the exception of transfer logic execution and perform the rollback operation
        rollback transaction;
    }
    Copy the code
  • Let’s take the above code for the TransferServiceImp.Java class and add a few test cases to understand Spring’s transaction capabilities.

1.2.1 Test Case 1: Directly execute the main method of entry.java
  • Under normal circumstances, no exception will be thrown inside the Transfer interface, so the transfer service can be executed successfully. The result of test case 1 is as follows: Transfer cny 1 from the avengerEug account to the Zhangsan account. => Successful, avengerEug account is added cny 1 and Zhangsan account is reduced CNY 1
1.2.1 Test Case 2: Modify TransferServiceImpl to simulate throwing an exception at @Extension1
  • The changed code looks like this:

    @Component
    public class TransferServiceImpl implements TransferService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Transactional(rollbackFor = Exception.class)  / / @ 1
        @Override
        public void transfer(String outAccountId, String inAccountId, BigDecimal amount) {
            / / into the money
            this.incrementAmount(inAccountId, amount);
    
    	    int result = 1 / 0;   // @extension1
            
            / / pay
            jdbcTemplate.update("UPDATE account SET amount = amount - ? WHERE id = ?", amount, outAccountId);
            
            / /... Various other business logic @extension2 can be added
        }
    
        @Override
        public void incrementAmount(String accountId, BigDecimal amount) {
            // Add the amount directly without considering any concurrency
            jdbcTemplate.update("UPDATE account SET amount = amount + ? WHERE id = ?", amount, accountId); }}Copy the code

    Obviously, an arithmetic exception is thrown at @extension1, so test case 2 executes with avengerEug transferring $1 to Zhangsan –> Failing, avengerEug does not deduct money and Zhangsan does not add money.

  • Now that you are familiar with the above two test cases, let’s take a look at Spring’s transaction propagation mechanism.

Second, understand the transaction propagation mechanism of Spring

  • **Spring’s transaction propagation mechanism and MySQL’s transaction isolation mechanism are two completely different things, there is no direct relationship between them. ** The simple way to understand this is: Spring’s transaction propagation mechanism is in the method call stack, if there are multiple methods with transaction capabilities, to confirm whether they use the same transaction or use separate transactions or other strategies, etc. As shown in the figure below:

  • What is Spring’s transaction propagation mechanism? We see the org. Springframework. Transaction. The annotation. The Propagation, a total of 7 defines the transaction Propagation in the source mechanism, they respectively have what features? I will introduce them to you in ** different poses (test cases) **.

  • Continue modifying the TransferServiceImpl. Java class with the following template code:

    @Component
    public class TransferServiceImpl implements TransferService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
        @Override
        public void transfer(String outAccountId, String inAccountId, BigDecimal amount) {
            / / into the money
    	   ((TransferService) AopContext.currentProxy()).incrementAmount(inAccountId, amount);
    
            / /... Extension code @1 can be added
    
            / / pay
            jdbcTemplate.update("UPDATE account SET amount = amount - ? WHERE id = ?", amount, outAccountId);
    
            / /... The extension code @2 can be added
        }
    
        / / @ 3
        @Override
        public void incrementAmount(String accountId, BigDecimal amount) {
            // Add the amount directly without considering any concurrency
            jdbcTemplate.update("UPDATE account SET amount = amount + ? WHERE id = ?", amount, accountId);
            
            / /... The extension code @4 can be added}}Copy the code

    In the above template, you specify that the Transfer interface is transaction enabled and the transaction propagation mechanism is specified as REQUIRED. At the same time, four extension points are defined, where **@3 is the transaction feature that defines the method, and @1, @2, and @4 are the locations where the throw exception is tested. Next test the case where the propagation mechanism of the incrementAmount method is set to REQUIRED_NEW** (refer to the corresponding test case for the remaining six propagation mechanisms)

2.1 Sets the transaction propagation mechanism of REQUIRED_NEW with the incrementAmount method

  • Modify the TransferServiceImpl. Java class to look like this:

    @Component
    public class TransferServiceImpl implements TransferService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
        @Override
        public void transfer(String outAccountId, String inAccountId, BigDecimal amount) {
            / / into the money
           ((TransferService) AopContext.currentProxy()).incrementAmount(inAccountId, amount);
    
            / /... Extension code @1 can be added
    
            / / pay
            jdbcTemplate.update("UPDATE account SET amount = amount - ? WHERE id = ?", amount, outAccountId);
    
            / /... The extension code @2 can be added
        }
    
        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) / / @ 3
        @Override
        public void incrementAmount(String accountId, BigDecimal amount) {
            // Add the amount directly without considering any concurrency
            jdbcTemplate.update("UPDATE account SET amount = amount + ? WHERE id = ?", amount, accountId);
            
            / /... The extension code @4 can be added}}Copy the code

    At @3, the propagation mechanism for the transaction is specified as REQUIRES_NEW

2.1.1 Test Case 1: Simulate an exception thrown at @1
  • Add the following code at @1:

    int result = 1 / 0;
    Copy the code

    And continue executing the entry.java main method. You’ll find that the Zhangsan account has been added, but avengerEug account has not been deducted.

  • One may ask: this is different from what we said in 1.2.1 Test Case 2. The exception was thrown internally, why not roll back at the same time? This is because during execution, we call the transaction-specific incrementAmount method from within the transaction-specific transfer method, but the transaction-specific propagation mechanism of incrementAmount method is REQUIRES_NEW. Spring starts a separate transaction for the incrementAmount method during execution. When we throw the exception at @1, the incrementAmount method’s transaction has already committed. Therefore, the result of the incrementAmount method is not affected in this case.

2.1.2 Test Case 2: Simulate throwing exception at @2
  • Add the following code at @2:

    int result = 1 / 0;
    Copy the code

    The results of this run are consistent with the results of test case 1.

2.1.3 Test Case 3: Simulate throwing exception at @4
  • Add the following code at @4

    int result = 1 / 0;
    Copy the code

    And continue executing the entry.java main method. You’ll find that the Money in the Zhangsan account is not increased and that the avengerEug account is not reduced. The reason is that exceptions thrown within the incrementAmount method, transactions started in the incrementAmount method, are also rolled back. But since the exception is not caught, the exception is thrown to the Transfer method, and the Transfer interface does nothing to handle the exception, and the exception is eventually thrown up. Spring will sense an exception and roll back the Transfer method, so that the money in the Zhangsan account is not increased and the money in the avengerEug account is not decreased.

2.2 Other six transaction propagation mechanism features

  • The above summarizes the features of the REQUIRES_NEW propagation mechanism, and looks at the related features from the perspective of three test cases. Given the length of this article, you have summarized the features of spring’s 7 transaction propagation mechanisms, and you can use the above test case template to verify the remaining 6 propagation mechanisms.

    Transaction propagation mechanism type The characteristics of For example,
    REQUIRED Two methods with transaction annotations are shared, and a new one is created if the transaction does not exist Condition: The transaction propagation mechanism for the downstream method is REQUIRED.

    Case: If the upstream methods have transactions, they share one transaction. Otherwise, start a transaction yourself
    NOT_SUPPORTED Transactions not supported Condition: The downstream method’s transaction propagation mechanism is NOT_SUPPORTED.

    Example: If an upstream method starts a transaction, the upstream method will not roll back the transaction even if the downstream method throws an exception internally.
    REQUIRES_NEW A new transaction is started regardless of whether there was a previous transaction, and the previous transaction is executed after the new transaction has completed Condition: When the downstream method’s transaction propagation mechanism is REQUIRES_NEW.

    Case study:

    1. Initiates a separate transaction regardless of whether the upstream method has a transaction.
    MANDATORY Must be executed with a transaction, otherwise throw an exception Condition: The transaction propagation mechanism for the downstream method is REQUIRES_NEW.

    Case study:

    1. If the upstream method does not open the transaction, throw an exception

    2. If the upstream method starts the transaction, the transaction is executed normally
    NEVER Must execute in a no transaction, otherwise an exception is thrown (as opposed to MANDATORY) Condition: When the downstream method’s transaction propagation mechanism is NEVER.

    Case study:

    1. If the upstream method starts a transaction, throw an exception

    2. If the upstream method does not enable the transaction, the transaction is executed normally
    SUPPORTS Depending on whether the caller has the transaction enabled, if so, the transaction is used Contrary to NOT_SUPPORTED.

    Condition: The transaction propagation mechanism of the downstream method is SUPPORTED.

    Case study:

    1. If the upstream method has no transactions, it has no transactions.

    2. If the upstream method has transactions, it also has transactions.
    NESTED Executes within a nested transaction, if one currently exists. If there is no current transaction, the transaction is started Condition: The transaction propagation mechanism of the downstream method is NESTED.

    Case study:

    1. If the upstream method has a transaction enabled, the downstream method will start a subtransaction when executing the downstream method. Whether the downstream method’s transaction commits depends on the upstream method’s operation. If the upstream method performs a commit transaction, the subtransaction will be committed as well. Similarly, if the upstream method rolls back, the subtransaction will be committed.

    2. If the upstream method does not have a transaction, the downstream method will open a transaction of its own when executing the downstream method. It’s going to be something likeREQUIRES_NEW

    For example, the upstream method is transfer, and the downstream method is incrementAmount. That is, downstream methods are called inside upstream methods.

Third, summary

  • Have you learned Spring’s transaction posture and propagation mechanism features?
  • Feel free to like, bookmark and follow my posts if you find them useful. :laughing:
  • I’m a slow walker, but I never walk backwards