Hand-held Seven Transaction Dissemination Behaviors of Spring in Practice

Posted by Paul1893 on Thu, 14 Nov 2019 03:51:21 +0100

Catalog

This article introduces Spring's seven transaction propagation behaviors and demonstrates them in code.

1. What is transactional communication behavior?

Transaction propagation behavior refers to how a transaction method should run when it is called by another transaction method.

For example, when the methodA method calls the methodB method, does methodB continue to run in the caller's methodA transaction or start a new transaction for itself, which is determined by the transaction propagation behavior of methodB.

2. Seven Dissemination Acts of Transactions

Spring specifies seven types of transaction propagation behavior in the TransactionDefinition interface.Transaction propagation behavior is a unique transaction enhancement feature of the Spring framework.This is a powerful toolbox Spring provides for us, and using transactional communication behavior can provide us with a lot of convenience for our development work.

The seven transaction propagation behaviors are as follows:

1.PROPAGATION_REQUIRED

If there is no current transaction, create a new one and join it if one exists. This is the most common choice and Spring's default transaction propagation behavior.

2.PROPAGATION_SUPPORTS

Supports the current transaction, joins it if it exists, and executes as a non-transaction if it does not currently exist.

3.PROPAGATION_MANDATORY

Supports the current transaction, joins it if it exists, and throws an exception if it does not exist.

4.PROPAGATION_REQUIRES_NEW

Create a new transaction, regardless of whether there is no transaction in the current memory.

5.PROPAGATION_NOT_SUPPORTED

Perform the operation non-transactionally and suspend the current transaction if one exists.

6.PROPAGATION_NEVER

Executes non-transactionally and throws an exception if a transaction currently exists.

7.PROPAGATION_NESTED

If a transaction currently exists, it executes within a nested transaction.If there is currently no transaction, execute as REQUIRED property.

Actually, I don't understand in 7, but don't worry, let's see the effect directly next.

3. 7 Actual Acts of Communication

Build two tables before the presentation, the user table and the user role table. There is no data in the first two tables.

Note that to make the data more intuitive, empty the data in the user and user_role tables each time the code is executed.

user table:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `sex` int(11) DEFAULT NULL,
  `des` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

The user_role table:

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.PROPAGATION_REQUIRED Test

If there is no current transaction, create a new one and join it if one exists. This is the most common choice and Spring's default transaction propagation behavior.

Scenario 1:

The scene periphery method did not open a transaction.

1. Validation methods

The two implementation classes UserServiceImpl and UserRoleServiceImpl formulate the propagation=Propagation.REQUIRED of the propagating behavior of things, then call both methods in the test method and throw an exception at the end of the call.

2. Main Code

Outer call method code:

/**
     * Test PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_REQUIRED() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

UserServiceImpl code:

/**
     * Increase Users
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl code:

    /**
     * Increase user role
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3. Database Screenshot after Code Execution

Both tables were added successfully, with the following screenshots:

4. Result analysis

The peripheral method does not open the transaction. The methods of inserting the user table and user role table run independently in their own transaction. The peripheral method exception does not affect the internal insertion, so both records were added successfully.

Scenario 2:

This scenario peripheral method opens the transaction.

1. Main Code

The test method code is as follows:

/**
     * Test PROPAGATION_REQUIRED
     *
     * @Author: java_suisui
     */
    @Transactional
    @Test
    void test_PROPAGATION_REQUIRED() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

2. Database Screenshot after Code Execution

Both tables are empty, with the following screenshots:

3. Result analysis

The perimeter method opens the transaction, the internal method joins the perimeter method transaction, the perimeter method rolls back, and the internal method rolls back, so both records failed to insert.

Conclusion: The above results prove that the internal method modified by Propagation.REQUIRED will join the transaction of the periphery method when the periphery method opens the transaction, so both the internal and periphery methods modified by Propagation.REQUIRED belong to the same transaction. As long as one method is rolled back, the whole transaction is rolled back.

2.PROPAGATION_SUPPORTS Test

Supports the current transaction, joins it if it exists, and executes as a non-transaction if it does not currently exist.

Scenario 1:

The scene periphery method did not open a transaction.

1. Validation methods

The two implementation classes UserServiceImpl and UserRoleServiceImpl formulate the propagation=Propagation.SUPPORTS for the propagating behavior of things, then call both methods in the test method and throw an exception at the end of the call.

2. Main Code

Outer call method code:

    /**
     * Test PROPAGATION_SUPPORTS
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_SUPPORTS() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

UserServiceImpl code:

/**
     * Increase Users
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl code:

    /**
     * Increase user role
     */
    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3. Database Screenshot after Code Execution

Both tables were added successfully, with the following screenshots:

4. Result analysis

The peripheral method does not open a transaction, and the methods for inserting the user table and user role table run independently in a non-transactional manner. The peripheral method exception does not affect the internal insertion, so both records were added successfully.

Scenario 2:

This scenario peripheral method opens the transaction.

1. Main Code

Add the comment @Transactional ly to the test_PROPAGATION_SUPPORTS method.

2. Database Screenshot after Code Execution

Both tables are empty, with the following screenshots:

3. Result analysis

The perimeter method opens the transaction, the internal method joins the perimeter method transaction, the perimeter method rolls back, and the internal method rolls back, so both records failed to insert.

CONCLUSION: The above results show that the Propagation.SUPPORTS modified internal method joins the transaction of the periphery method when the periphery method opens the transaction, so the Propagation.SUPPORTS modified internal method and periphery method belong to the same transaction, as long as one method rolls back, the whole transaction rolls back.

3.PROPAGATION_MANDATORY Test

Supports the current transaction, joins it if it exists, and throws an exception if it does not exist.

By passing the above test,'Support the current transaction, join the transaction if it exists', this statement has verified that both records failed to be added after the @Transactional annotation was added to the outer layer, so this propagation only tests scenarios where the outer layer does not start the transaction.

Scenario 1:

The scene periphery method did not open a transaction.

1. Validation methods

The two implementation classes UserServiceImpl and UserRoleServiceImpl formulate the transactional propagation = Propagation.MANDATORY, the main code is as follows.

2. Main Code

Outer call method code:

    /**
     * Test PROPAGATION_MANDATORY
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_MANDATORY() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

UserServiceImpl code:

/**
     * Increase Users
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl code:

    /**
     * Increase user role
     */
    @Transactional(propagation = Propagation.MANDATORY)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3. Database Screenshot after Code Execution

Both tables are empty, with the following screenshots:

4. Result analysis

The run log below shows that an error was reported when userService.add() was called, so no new data was added to either table, verifying that "throw an exception if no transaction currently exists".

at com.example.springboot.mybatisannotation.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$50090f18.add(<generated>)
    at com.example.springboot.mybatisannotation.SpringBootMybatisAnnotationApplicationTests.test_PROPAGATION_MANDATORY(SpringBootMybatisAnnotationApplicationTests.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

4.PROPAGATION_REQUIRES_NEW Test

Create a new transaction, regardless of whether there is no transaction in the current memory.

This creates a transaction each time, so we can just validate one.

Scenario 1:

This scenario peripheral method opens the transaction.

1. Validation methods

The two implementation classes UserServiceImpl and UserRoleServiceImpl formulate the transactional propagation = Propagation.REQUIRES_NEW, the main code being as follows.

2. Main Code

Outer call method code:

    /**
     * Test REQUIRES_NEW
     *
     * @Author: java_suisui
     */
    @Test
    @Transactional
    void test_REQUIRES_NEW() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

UserServiceImpl code:

/**
     * Increase Users
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl code:

    /**
     * Increase user role
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3. Database Screenshot after Code Execution

Both tables were added successfully, with the following screenshots:

4. Result analysis

A new transaction is created regardless of whether there is no transaction in the current memory, so both data are added successfully.

5.PROPAGATION_NOT_SUPPORTED Test

Perform the operation non-transactionally and suspend the current transaction if one exists.

Scenario 1:

This scenario peripheral method does not open transactions.

1. Validation methods

The two implementation classes UserServiceImpl and UserRoleServiceImpl formulate the transactional propagation = Propagation.NOT_SUPPORTED, the main code being as follows.

2. Main Code

Outer call method code:

    /**
     * Test PROPAGATION_NOT_SUPPORTED
     *
     * @Author: java_suisui
     */
    @Test
    void test_PROPAGATION_NOT_SUPPORTED() {
        // Add User Table
        User user = new User();
        user.setName("Java Broken thoughts");
        user.setPassword("123456");
        userService.add(user);
        // Add User Role Table
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getId());
        userRole.setRoleId(200);
        userRoleService.add(userRole);
        //Throw an exception
        throw new RuntimeException();
    }

UserServiceImpl code:

/**
     * Increase Users
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

UserRoleServiceImpl code:

    /**
     * Increase user role
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public int add(UserRole userRole) {
        return userRoleMapper.add(userRole);
    }

3. Database Screenshot after Code Execution

Both tables were added successfully, with the following screenshots:

4. Result analysis

The data was added successfully because it was executed non-transactionally.

Scenario 2:

This scenario peripheral method opens the transaction.

1. Main Code

Add the comment @Transactional ly to the test_PROPAGATION_NOT_SUPPORTED method.

2. Database Screenshot after Code Execution

Both tables were added successfully, with the following screenshots:

3. Result analysis

If a transaction exists, the current transaction is suspended, which is equivalent to non-transactional execution, so the two data are added successfully.

6.PROPAGATION_NEVER Test

Executes non-transactionally and throws an exception if a transaction currently exists.

There is already a similar situation where no transaction on the outer layer will run non-transactionally, two tables are added successfully, transactions throw exceptions, and neither table has new data.

7.PROPAGATION_NESTED Test

If a transaction currently exists, it executes within a nested transaction.If there is currently no transaction, execute as REQUIRED property.

There is already a similar situation where no transaction on the outer layer runs as a REQUIRED attribute, and both tables are added successfully; there is a transaction but a transaction is used, and an exception is thrown by the method causing a rollback, and neither table has new data.

By this time Spring's seven transaction communication behaviors have all been introduced. If you have any questions, please leave a message to communicate.

Full source address: https://github.com/suisui2019/springboot-study

Recommended reading

1.SpringBoot Series - Integrated Mybatis
2.SpringBoot Series - Integrate Mybatis (XML Configuration)
3. Print logs in Java, these 4 points are important!
4.SpringBoot Integrated JWT Implements Permission Authentication
5.1 minute to learn about JWT certification!

Free Java-related materials are available within a time limit, covering technologies such as Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo/Kafka, Hadoop, Hbase, Flink, high concurrent distribution, big data, machine learning, etc.
Pay free attention to the following public numbers:

Topics: Java Spring Database SpringBoot