Understanding and Simple Trial of Transaction Propagation Mechanism in Spring

Table of contents

I. Introduction

Second, the transaction propagation behavior in the Spring framework

Three, transaction propagation behavior test

Propagation.REQUIRED

Propagation.SUPPORTS

Propagation.MANDATORY

Propagation.REQUIRES_NEW

Propagation.NOT_SUPPORTED

Propagation.NEVER

Propagation.NESTED

4. Summary of transaction failure scenarios

Five, transaction propagation mechanism usage scenarios


 

I. Introduction

I believe that everyone can think of transactions in the database and their corresponding ACID characteristics when they talk about transactions. But in fact, it is not only in the database that there is a transaction, its core role also has its own unique role in the Spring framework. Maybe some friends have heard about Spring's transactions in interviews or studies, but they don't really understand its specific functions and usage scenarios, so that every time they use Spring transactions, they directly add @Transactional( rollbackFor = Exception.class), it doesn’t matter anymore after that, I didn’t study the propagation behavior of the transaction in detail, which made it difficult to use when using it. So today I will do some simple summarization of the learning and understanding of the Spring transaction mechanism.

Tips: The transactions in the database must meet 4 conditions (ACID): 原子性( Atomicity , or indivisibility), 一致性( Consistency), ( I solation, also known as independence), ( Durability ).隔离性持久性

  • Atomicity: All operations in a transaction are either completed or not completed, and will not end in a certain link in the middle. If an error occurs during the execution of the transaction, it will be rolled back (Rollback) to the state before the transaction started, as if the transaction had never been executed.

  • Consistency: The integrity of the database is not violated before the transaction begins and after the transaction ends. This means that the written data must fully comply with all preset rules, including data accuracy, seriality, and the subsequent database can spontaneously complete the scheduled work.

  • Isolation: The ability of the database to allow multiple concurrent transactions to read, write and modify its data at the same time. Isolation can prevent data inconsistency caused by cross-execution when multiple transactions are executed concurrently. Transaction isolation is divided into different levels, including read uncommitted (Read uncommitted), read committed (read committed), repeatable read (repeatable read) and serial (Serializable).

  • Persistence: After the transaction processing ends, the modification to the data is permanent, even if the system fails, it will not be lost.

Second, the transaction propagation behavior in the Spring framework

In the Spring framework, the transaction propagation mechanism defines how to deal with existing transactions when calling methods with transaction functions.

Spring provides a variety of transaction propagation behaviors, each of which is applicable to different development scenarios and business requirements. The following are common transaction propagation behaviors and their descriptions:

Transaction Propagation Behavior Type explain
Requied(默认) If there is no current transaction, a new one is created and used during method execution. If a transaction currently exists, the method joins it and becomes part of it. This is the most commonly used transaction propagation behavior, used to ensure that the method is executed in a transaction. If there is no transaction, create a new transaction; if there is a transaction, join the transaction.
Supports If a transaction currently exists, the method will execute within that transaction. If there is no current transaction, the method will execute non-transactionally. This transaction propagation behavior is suitable for situations where it is not mandatory for methods to be executed within a transaction.
Mandatory If a transaction currently exists, the method will execute within that transaction. An exception is thrown if there is no current transaction. This transaction propagation behavior applies to the case where a method is required to be executed in a transaction, indicating that the method must be executed in a transaction.
Requires_New If a transaction currently exists, suspend the current transaction and create a new one. method is executed in a new transaction. If there is no current transaction, the method will be executed in a new transaction. This transaction propagation behavior is suitable for cases where methods are required to be executed within a new transaction. Regardless of whether there is a current transaction, the method will be executed in a new transaction and the current transaction will be suspended.
Not_Supported method will be executed in a non-transactional manner. If a transaction currently exists, it is suspended. This transaction propagation behavior is suitable for situations that require methods not to be executed within a transaction. Regardless of whether a transaction currently exists, the method executes in a non-transactional manner and suspends the current transaction.
Never method will be executed in a non-transactional manner. An exception is thrown if a transaction currently exists. This transaction propagation behavior is suitable for situations where methods are required to never be executed within a transaction. An exception is thrown if a transaction currently exists.
Nested If a transaction currently exists, the method will be executed in a nested transaction. If there is no current transaction, a new transaction is created and used during method execution. Nested transactions are part of the outer transaction and can be rolled back to a savepoint of the transaction state. This transaction propagation behavior is suitable for situations where partial rollback is required, and outer transactions can be rolled back and affect the state of inner nested transactions.

The above 7 propagation mechanisms can be divided into the following 3 categories according to the dimension of "whether the current transaction is supported":

dc5f5aad6c7b4f76a4248d83f5da8fac.png

In the project, you can @Transactionalstart the transaction by using this annotation

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
​
    @AliasFor("value")
    String transactionManager() default "";
​
    Propagation propagation() default Propagation.REQUIRED;
​
    Isolation isolation() default Isolation.DEFAULT;
​
    int timeout() default -1;
​
    boolean readOnly() default false;
​
    Class<? extends Throwable>[] rollbackFor() default {};
​
    String[] rollbackForClassName() default {};
​
    Class<? extends Throwable>[] noRollbackFor() default {};
​
    String[] noRollbackForClassName() default {};
}

In this annotation source code, you can see that it is currently the default REQUIRED.

If you need to change the type of transaction propagation used, you can use Propagationthis enumeration class to mark it in the form of annotations:

package org.springframework.transaction.annotation;
​
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
​
    private final int value;
​
    private Propagation(int value) {
        this.value = value;
    }
​
    public int value() {
        return this.value;
    }
}

The benefits of the transaction propagation mechanism are:

  • Simplify code logic: The transaction propagation mechanism allows developers to manage multiple method calls as a transaction if needed, without having to manually handle operations such as transaction start and commit.

  • Provide transaction isolation: By controlling the propagation behavior of transactions, you can ensure that methods executed in a transaction can share the same transaction context, thereby maintaining transaction isolation and consistency.

  • Support for nested transactions: The NESTED option in the transaction propagation mechanism allows methods to be executed in nested transactions, provides higher-level transaction control, and supports partial rollback.

What exactly is the function of the transmission mechanism?

The author's personal point of view is that transactions are a feature of databases, and for Spring只是封装这个特性our convenience, the final execution is actually completed in the database, but for databases, transactions are single and there are not so many business scenarios, but for For Spring, it will face a variety of business needs, so it needs to have a set that can control transactions from the code level to meet our scenario needs, so there is a propagation mechanism.

Since it is "transaction propagation", the number of transactions should be two or more. The Spring transaction propagation mechanism was born to specify the behavior of multiple transactions during the propagation process. For example, method A starts a transaction, and calls method B that starts the transaction during execution, so should the transaction of method B be added to the transaction of A? Or do the two transactions execute without affecting each other, or is the B transaction nested into the A transaction for execution? So at this time, a mechanism is needed to specify and constrain the behavior of these two transactions, which is the problem solved by the Spring transaction propagation mechanism.

In layman's terms, when our project is relatively simple and the business logic is not complicated, when a business method processes a data operation, such as simply checking data, deleting data, etc., and processing a single data layer operation within a method, then this is not used Spring transactions are necessary, because a single data operation is considered a basic database transaction in the database, either succeeding or failing. But if the business processing is complicated, it’s a different matter. For example, a method of processing order business includes two steps of deduction after the order payment is completed and order submission after the deduction is completed, just like Taobao. , there will be steps such as order information when the goods are received. At this time, if there is no transaction intervention to ensure that the deduction and order information are consistent with the new one, it may cause you to pay, but the database operation in the middle of the method execution (two steps) may be abnormal, and the order status is not updated as a result. Didn't you just break it off? Therefore, when dealing with complex business logic, it is absolutely necessary to learn to add transaction processing to ensure the consistency of business method execution.

How is Spring transaction implemented?

Because Spring itself has no transactions, only the database will have transactions, and Spring的事务是借助AOP,通过动态代理的方式when we want to operate the database, Spring actually expands the function through the dynamic proxy, and opens the database through the database client before our code operates the database Transaction, if there is no exception information after the code is executed or there is no exception information to be captured by Spring, then submit the transaction through the database client program. If there is exception information or there is exception information to be captured by Spring, then return to Roll transactions, so as to achieve the purpose of controlling database transactions. Attached below is a flow chart of creating a transaction in Spring for everyone to understand. Interested partners can also view it through the source code, which is not detailed here:

b5d3c676e2e54deca0d6e1067055bf9f.png

The following is a simple usage understanding for each different propagation behavior.

Three, transaction propagation behavior test

Therefore, the number of transactions should be two or more, so first prepare two methods for testing in the project.

@Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
/**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取结果-》{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    /**
     * 被调用方法
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

At this time insertData, as the caller method, updateSatusas the method called by insertData. In this way, after adding different transaction propagation mechanisms for transactions, test the impact of different transaction propagation behaviors on method execution. Let's take a look at what happens if you don't join the transaction:

Add an id that does not exist in the admin database to the test data and test.

@Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(103).setName("张三");
    }
    @Test
     void requiredResult(){
        System.out.println(testUserService.insertData(tu));
    }

When the method is executed at this time insertData, because it first calls the method of updating the state to query the database and update it, but the database does not have data with an id of 103, the update fails and an exception is thrown, so what will happen to the final database result. . .

JDBC Connection [HikariProxyConnection@1266035080 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 103(Integer), 张三(String), 2023-07-06 20:01:27.666(Timestamp)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51172948]
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》103的状态
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@736f8837] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1233162869 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 103(Integer)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@736f8837]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c3b0cc8] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@43924520 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 103(Integer)
<==    Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c3b0cc8]
2023-07-06 20:01:28.693  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
​
模拟异常
MyException(code=501)

It can be seen that after the data is successfully inserted, if there is an exception in the subsequent operation, the database operation will not be rolled back. This is like deducting money without order payment completion information. This is definitely not possible in daily business development, so we need to use business to handle.

Propagation.REQUIRED

This means that if there is no transaction, add the transaction, and if there is, execute according to the transaction. As the default propagation method of transactions, this is also the most common one. Here, we can consider the transaction processing relationship when multiple methods have a nested relationship based on the default propagation behavior of things.

When the nesting relationship of this method appears in the class, will it be 被调用的方法加入事务处理rolled back? That is, a method with things is nested in a method without transactions.

/**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Then insertDatacall the method again by testing:

@Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(104).setName("张三");
    }
    @Test
     void requiredResult(){
        System.out.println("无事务处理的执行结果:"+testUserService.insertData(tu));
    }

The data with id 104 is still inserted into the database, and the rollback has not been implemented. This is because after the class that has not joined the transaction is proxied, the original method of updateStatus is still called in the proxy class.

Conversely, what if it is 有事物的方法里调用一个没有事务的方法?

/**
     * insertData作为方法调用者,加入事务
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    //@Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

At this point, test and insert a data with an id of 105, although the method still fails to execute, this is for sure, after all, there is an exception.

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
JDBC Connection [HikariProxyConnection@1120514542 wrapping com.mysql.cj.jdbc.ConnectionImpl@217c6a1e] will be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 105(Integer), 张三(String), 2023-07-06 21:01:31.562(Timestamp)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》105的状态
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 105(Integer)
<==      Total: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 105(Integer)
<==    Updates: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 21:01:31.768  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
​
模拟异常
MyException(code=501)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.updateStatus(TestUserServiceImpl.java:76)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.insertData(TestUserServiceImpl.java:60)

However, the database operation in this method has actually been rolled back. Because the database has not inserted the data with id 105!

 e1692656109841b5ae72d30a9cfb2b7f.png

The reason is that when proxying, the method without transactions is directly wrapped in the proxy method with transactions, so the effect of the transaction is realized, and it will be rolled back if it fails.

Similar to the following form:

//被代理后的方法
proxyInsertData(){
        //开启事务
        原始类.insertData(){
            原始类.updateStatus();
        }
    }

In this way, even if an exception occurs in the executed method, you will not be charged if the execution of the business method fails. So if the transaction is nested on the same class, the result depends on the propagation characteristics of the outer method thing. And when an exception occurs in the internal method, even if the external method catches and handles the exception, the data will still be rolled back. This is also the role of Required: Propagation.REQUIREDwhen there is a transaction in the external method and the modification is used, all internal methods will not create a new transaction and run directly in the current transaction ( provided that no other unique propagation characteristics are specified, continue to see this point later )

Propagation.SUPPORTS

If a transaction exists, the method will execute within a transaction; if no transaction exists, the method will execute non-transactionally. That is to say, he follows the external method. When the external method specifies this propagation behavior, it is equivalent to no transaction.

/**
     * insertData作为方法调用者,加入SUPPORTS
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

At this point it's like the first test, inside updateStatus方法有Propagation.REQUIRED修饰, but outside insertData没有事务. Therefore, when inserting data, even if there is an abnormal situation later, the result of the execution operation will not trigger the transaction rollback mechanism.

Propagation.MANDATORY

Mandatory means that the modified method must run in a transaction. It is easy to understand that it must be used in the transaction method, otherwise an exception will be thrown directly.

/**
     * insertData作为方法调用者,MANDATORY
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = MyException.class)
    public String insertDataMANDATORY(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Running the test in this way throws an exception directly:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
​
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:362)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:574)
    ……

So if there is something outside, it will be fine if the method inside uses Mandatory to spread the behavior. When the method inside is executed, it will be added to the transaction of the outside method and executed according to the rules of the transaction.

Propagation.REQUIRES_NEW

Indicates that the decorated method must run in its own transaction. A new transaction will be started. If the caller has a current transaction, the current transaction will be suspended during the execution of this method. That is to say, the propagation behavior of the internal call is that this method will first override the transaction of the external method, and then create its own transaction execution method independently, isolated from each other, and finally go to the external transaction:

/**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRES_NEW
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Test again, insert a data with an id of 107, and throw an exception when updating the status fails:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
JDBC Connection [HikariProxyConnection@1120514542 wrapping com.mysql.cj.jdbc.ConnectionImpl@217c6a1e] will be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 107(Integer), 第107号测试(String), 2023-07-06 22:52:50.194(Timestamp)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.279  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
[org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.283  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 22:52:50.283  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》107的状态
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 107(Integer)
<==      Total: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 107(Integer)
<==    Updates: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.357  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
MyException(code=501)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.updateStatus(TestUserServiceImpl.java:83)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.insertData(TestUserServiceImpl.java:62)

Here it is obvious that the database inserts the test data with id 107, but the update fails because there is no such id during the update, but the two transactions run independently of each other and are isolated from each other, so the insert operation is performed normally, and the rollback mechanism is triggered by the exception. Here is the problem mentioned above using Required : 如果内部的方法有其他事务传播行为,那么外部方法即使标名使用Required,内部也会执行它自己的传播行为.

Propagation.NOT_SUPPORTED

Indicates that the modified method will not run in a transaction. If the called method already has a transaction, it will also be suspended, and the current transaction will be suspended. Before we test this transaction propagation behavior, let's think about it 同一个类中出现事务嵌套和不同类中的事务嵌套是否是一致的问题. The above also mentioned that when the external method has a transaction and uses Propagation.REQUIREDmodification, all internal methods will not create a new transaction and run directly in the current transaction , but our two methods are in the same class during the test, so the point of view is actually limited. So this time, let's look at it with the help of the Not_Supported propagation behavior.

Create a new external class AdminServiceImpland add a test method with an exception, and specify its transaction propagation behavior as Not_Supported:

@Service
@Slf4j
public class  extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
  @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        //更新信息
        adminDao.updateById(status);
        //模拟异常
        int a =10/0;
    }
}

slightly 修改测试类TestUserServiceImpl:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者,测试同类下的事务嵌套
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //同类下的updateStatus方法
            updateStatus(testUser.getId());
         //另一个类下的updateStatus方法
         //   adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
        
    }
​
    
    /**
     * 被调用方法,加入Propagation.NOT_SUPPORTED
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id));
        //模拟异常
        int a = 10/0;
    }
    
}

At this point 同一个方法内部存在事务嵌套时->insertData调用同类下的updateStatus方法, test the id that exists in a database, check the results of data insertion and update:

 @Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(202).setName("第202号测试");
    }
    
    @Test
     void requiredResult(){
        System.out.println("处理的执行结果:"+testUserService.insertData(tu));
    }

703b407e6b4f4bc4ad4de04be8af42ae.png

 Obviously, although Not_Supportedthe method will not run in the transaction, but because insertDatathere is a transaction in the external method, the following method still joins the transaction, so the whole test method is rolled back. Therefore, if transactions are nested in the same class, the final result depends on the propagation characteristics of the outer method transaction .

Then test if the nested method is a method in another class:

 /**
     * insertData作为方法调用者,测试不同类下的事务嵌套
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //同类下的updateStatus方法
         //   updateStatus(testUser.getId());
         //另一个类下的updateStatus方法
         adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
    }

9bce36d471174add85df491e8f057447.png

 It can be seen that although the update method has an exception, the update is still successful, and the insertData method is rolled back. The reason is: after the method of inserting data is successfully executed, it is executed in a non-transactional state because it is modified, that is, it is updated first, and then an exception occurs without rollback 被调用的不同类下的更新操作的方法updateStatus. Not_SupportedThen when the external insertData method was executed, the exception was found. Although the exception was thrown, because the transaction propagation behavior of the external method was yes REQUIRED, it still took the transaction as a whole, so it was rolled back. But the update still performed successfully.

That is to say当不同类的事务存在嵌套的时候,外层方法按照外层的事务传播行为执行,内层的方法按照内层的传播行为去执行。同类与不同类下的事务嵌套执行方式是不同的

Propagation.NEVER

This is obvious. The method modified by this propagation behavior cannot run in the context of a transaction, otherwise an exception will be thrown.

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        //这里并没有捕获可能出现的异常,不然就看不到结果了
        adminService.updateStatus(testUser.getId());
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NEVER
     * @param id
     */
    @Transactional(propagation = Propagation.NEVER,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        int a =10/0;
    }
}

Since the external method has a transaction, and the called method cannot be executed in a method with a transaction context, the following exception is directly thrown:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
​
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:413)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:352)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:574)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:361)
    ……

Propagation.NESTED

This transaction propagation behavior is also called nested transaction, and its specific functions are as follows:

  1. Indicates that a transaction already exists for the current method, then the method will run in a nested transaction.

  2. Nested transactions can be committed or rolled back independently of the current transaction.

  3. If the current transaction does not exist, it behaves like Propagation_Required.

The concept of nested transactions is that the inner transaction depends on the outer transaction. When the outer transaction fails, the action done by the inner transaction will be rolled back. The failure of the inner transaction operation will not cause the rollback of the outer transaction.

Therefore, when there is no external transaction and the internal method is NESTEDmodified, it is consistent with what 3 said:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
            adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NESTED
     * @param id
     */
    @Transactional(propagation = Propagation.NESTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        int a =10/0;
    }
}

6a30afc4b5d84306a8c2b894f70ebdea.png

 When the external method has things, at this time,如果外部方法发生异常,则内部事务一起发生回滚操作;2,如果外部无异常情况,内部被调用方法存在异常情况,则内部方法独立回滚;

Here we focus on testing the second case of internal independent rollback. The test conditions are as follows:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //外部捕获异常情况
            adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
             e.printStackTrace();
        }
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NESTED
     * @param id
     */
    @Transactional(propagation = Propagation.NESTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        //内部方法模拟抛出异常
        throw new MyException(507,"模拟异常");
    }
}

At this time, there is a transaction in the external method, and the internal method is NESTEDmodified, then 执行后外部的业务会正常执行,而有异常抛出却被调用的内部方法会独立回滚.  af5230b2afce49a484a69fc01a270800.png

4. Summary of transaction failure scenarios

In actual JavaWeb development, there may be scenarios where Spring transactions fail. Some common situations include:

  1. The method is not marked with the @Transactional annotation: Spring uses the annotation @Transactional to declare the transaction. If the method is not marked with the annotation, the transaction will not take effect.

    Solution: Add the @Transactional annotation to the method that needs to open the transaction to ensure that the transaction can work normally.

  2. The exception is not handled by the catch and the transaction rollback is invalid: when an exception is thrown, Spring will automatically roll back the transaction, but if the exception is caught and processed, and it is not thrown again, the transaction will not be rolled back. For example, the following:

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
        public void updateStatus(Integer id) {
            try {
                Admin status = Admin.builder().id(id).status((byte) 1).build();
                adminDao.updateById(status);
                throw new MyException(507,"模拟异常");
            }catch (Exception e){
            }
         }

    Although the exception was thrown above, it was caught again, so the exception supported by Spring transaction must be thrown again. (Written stupidly, just for demonstration)

    Solution: Use throw to throw an exception in the catch block to ensure that the transaction can be rolled back.

  3. Transaction spans multiple methods or classes: When a transaction involves multiple methods or classes, if one of the methods or classes does not configure the transaction correctly, the entire transaction may fail.

    Solution: Make sure that all methods or classes involved in transactions are properly configured with transactions, and use appropriate transaction propagation mechanisms.

  4. Multiple data sources are used: When using multiple data sources, transactions may not work properly if the transaction manager is not properly configured or does not specify which data source to use.

    Solution: Configure the corresponding transaction manager for each data source, and ensure that the transaction manager correctly specifies which data source to use.

  5. The transaction method access modifier is not public, otherwise it will cause the transaction to fail

    The reason is also very simple, because as mentioned above, Spring's transaction mechanism is essentially implemented by dynamic proxy. For JDK's dynamic proxy, it can only proxy classes that implement interfaces, and interfaces are public by default. Cglib cannot proxy the pricate method, so the transaction will fail.

  6. The transaction method is static, finalthe modified transaction will also be invalid

    Also because Spring's declarative transactions are implemented based on dynamic proxies, it is impossible to rewrite final modified methods; whether it is JDK dynamic proxies or Cglib dynamic proxies, the specific object of the proxy must be obtained through proxy, and the static method The modified method belongs to the class object, not to any instance object, so the static method cannot be overridden, that is to say, the static method cannot be dynamically proxied.

  7. If the database table being operated does not support transactions, the configuration of Spring transactions will also fail. For example, your mysql storage engine is MyISAM.

Five, transaction propagation mechanism usage scenarios

The transaction propagation mechanism can provide convenience in the following development scenarios:

  • In the case of high data consistency requirements among multiple methods, the REQUIRED propagation mechanism can be used to ensure that the methods are executed in the same transaction.

  • If you need to open a new transaction in a method and manage it independently of external transactions, you can use the REQUIRES_NEW propagation mechanism.

  • When nested transactions are required and can be rolled back independently of external transactions, the NESTED propagation mechanism can be used.

In short, the transaction propagation mechanism in the Spring framework provides flexible transaction control means, which can select the appropriate propagation behavior according to specific needs, simplify the development process, ensure data consistency, and provide high-level transaction management functions. Inadequacies are welcome to correct ~

 

Guess you like

Origin blog.csdn.net/qq_42263280/article/details/131605310