【JAVA】Spring事务的传播行为

前言

Spring为我们提供了巨大的便利,其中的事务增强特性,使得程序出错时,不需要我们进行手动回滚。Spring在Propagation定义了7种事务的传播行为:

public enum Propagation {

	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

	NEVER(TransactionDefinition.PROPAGATION_NEVER),

	NESTED(TransactionDefinition.PROPAGATION_NESTED);

}

一上来,我不想全部抛出各个行为的定义以及说明,我们用代码来验证


代码环境搭建

使用SpringBoot+Mybatis-plus进行搭建,目录结构大致如下:

(1)在数据库中创建User表

CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增序号',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  PRIMARY KEY (`id`)
) ENGINE = InnoDB ;

对应的实体类:

package com.example.transaction.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

public class User {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String name;

    public User() {
    }

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

   //get、set
}

(2)pom.xml引入以下依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>

(3)application.yaml

server:
  port: 8080

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: a123
    url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&characterEncoding=utf8

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

(4)启动类加上:

@MapperScan(basePackages = {"com.example.transaction.dao"})

(5)UserMapper

package com.example.transaction.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.transaction.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

因为使用的Mybatis-plus,我们其实不需要在UserMapper新增curd方法,BaseMapper里面基本上都有了

(6)UserService

package com.example.transaction.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.transaction.entity.User;

public interface UserService extends IService<User> {

    public void insertChild1();

    public void insertChild2();
}

(7)UserServiceImpl

package com.example.transaction.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.transaction.dao.UserMapper;
import com.example.transaction.entity.User;
import com.example.transaction.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
    }
}

UserServiceImpl我们增加3个insert方法,用户姓名与方法名对应


REQUIRED

(1)insertChild1与insertChild2方法开启事务。外围方法不开启事务,且抛出异常

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
    }

测试方法不开启事务

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
        int a = 1 / 0;
    }

运行后,外围方法(测试方法)抛出异常,但数据库中有child1与child2数据。可以发现,insertChild1与insertChild2方法在自己的事务中运行,外围方法出现异常不影响这两个彼此独立的事务。

(2)insertChild1与insertChild2方法开启事务,但insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中只有child1数据。insertChild1与insertChild2分别在自己的事务中运行,insertChild2抛出异常不影响insertChild1事务,但insertChild2方法会回滚。

(3)insertChild1与insertChild2方法开启事务,且他们都不抛出异常。外围方法也开启事务,并抛出异常

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
    }

测试方法:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
        int a = 1 / 0;
    }

运行后,外围方法抛出异常,数据库中没有child1与child2数据,说明insertChild1与insertChild2被外围事务影响,都进行了回滚操作。可以看得出来,insertChild1与insertChild2放弃了自己的事务,都加入了外围事务。

(4)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常。外围方法也开启事务,不抛出异常

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中没有一条数据。insertChild1与insertChild2放弃了自己的事务,加入到了外围事务中,insertChild2方法抛出异常,被外围事务感知,致使整体进行回滚。

(5)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常并捕获异常。外围方法开启事务

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        try {
            int a = 1 / 0;
        } catch (Exception e) {
        }
    }

测试方法:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后发现,没有出现报错,但数据库依然没有一条数据。可以发现,两个子方法加入了外围事务,insertChild2捕获了自己的异常不被外围方法感知,但整个事务依然被回滚。

当然,如果insertChild2没有捕获自己的异常,但在外围方法捕获insertChild2的异常,整个事务依然被回滚。

到这里,我们可以总结REQUIRED的特性:

外围方法没开启事务的情况下,内部方法使用PROPAGATION_REQUIRED会开启自己独立的一个事务,多个内部方法开启的事务互不影响。

外围事务开启事务的情况下,内部方法使用PROPAGATION_REQUIRED,则内部方法会放弃自己的事务,一起加入到外围事务中,任意的一个内部方法抛出异常,就算自己捕获异常,同样都会使得整个事务回滚。


SUPPORTS

(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,但数据库中有child1与child2数据。可以发现,当内部方法开启SUPPORTS时,外界方法不开启事务时,则内部方法都不使用事务。

(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库没有任何数据。可以发现,当内部方法开启SUPPORTS时,外界方法也开启事务时,则内部方法一起加入到外围事务中。

到这里,我们可以总结SUPPORTS的特性:

外围方法没有开启事务,内部方法尽管开启SUPPORTS,最后都不会使用事务。

外围事务开启事务时,内部方法开启SUPPORTS,则一起加入到外围事务中,是一个整体事务。


MANDATORY

(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,直接抛出了一个新异常:

org.springframework.transaction.IllegalTransactionStateException: 
No existing transaction found for transaction marked with propagation 'mandatory'

内部方法使用mandatory时,外围方法必须使用事务,否则直接报错。

(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务

与上一个情景比,仅仅是测试方法加了一个事务

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中没有任何数据。显然,此时insertChild1与insertChild2全部加入到了外围事务中,变成了一个整体事务。

到这里,我们可以总结MANDATORY的特性:

外围方法没有开启事务,子方法使用MANDATORY时,运行时直接报错,子方法需要外围方法开启事务。

外围方法开始事务时,子方法使用MANDATORY时,所有子方法一起加入到外围事务中,形成一个整体事务。


REQUIRES_NEW

(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中只有child1数据。说明当insertChild1与insertChild2使用REQUIRES_NEW时,外围方法没开启事务,则子方法会创建独立的事务,互不干扰。insertChild2抛出异常便只回滚了child2数据。

(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务

与上一个情景相比,仅仅是测试方法开启了事务:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中还是只有child1数据。说明当insertChild1与insertChild2使用REQUIRES_NEW时,外围方法开启事务时,子方法还是会创建独立的事务,互不干扰。insertChild2抛出异常便只回滚了child2数据。

到这里,我们可以总结REQUIRES_NEW的特性:

不管外围方法有没有开始事务。子方法使用REQUIRES_NEW时,都会开启自己独立的事务,这些子方法的独立事务互不干扰,与外围方法也互不干扰。


NOT_SUPPORTED

(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中有child1与child2两条数据。insertChild1与insertChild2使用NOT_SUPPORTED时,外围方法不开启事务,则子方法也不开启事务。

(2)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法开启事务

与上一个情景相比,仅仅是测试方法开启了事务:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中依然有child1与child2两条数据。insertChild1与insertChild2使用NOT_SUPPORTED时,外围方法开启事务,子方法还是不开启事务。

到这里,我们可以总结NOT_SUPPORTED的特性:

外围事务不开启事务,子方法使用NOT_SUPPORTED时,子方法都不使用事务。

外围方法开启事务时,子方法使用NOT_SUPPORTED时,子方法将外围事务挂起,也就是不使用事务。


NEVER

(1)insertChild1使用NEVER,外围方法开启事务

    @Override
    @Transactional(propagation = Propagation.NEVER)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

测试方法:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
    }

运行后,直接抛出了一个新异常:

org.springframework.transaction.IllegalTransactionStateException: 
Existing transaction found for transaction marked with propagation 'never'

内部方法使用NEVER时,如果外围方法开启事务后,则直接报错,内部方法强制不使用事务。

到这里,我们可以直接总结NEVER的特性:

子方法使用NEVER,则子方法需要以非事务方式执行,如果外围方法开启事务,则直接报错


NESTED

(1)insertChild1与insertChild2方法开启事务,且insertChild2抛出异常,外围方法不开启事务

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
        int a = 1 / 0;
    }

测试方法:

    @Test
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

运行后,insertChild2抛出异常,数据库中仅仅有child1数据,说明insertChild2方法被回滚了。可以看出,insertChild1与insertChild2方法使用NESTED时,而外围方法不开始事务,则insertChild1与insertChild2方法会创建相互独立的事务,insertChild2抛出异常,因此回滚了child2数据。

(2)insertChild1与insertChild2方法开启事务,外围方法开启事务

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void insertChild1() {
        User user = new User();
        user.setName("child1");
        userMapper.insert(user);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void insertChild2() {
        User user = new User();
        user.setName("child2");
        userMapper.insert(user);
    }

测试方法:

    @Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void testUser() {
        userService.insertChild1();
        userService.insertChild2();
    }

虽然没有出现任何异常,但所有数据都被回滚了,控制台打印出如下信息

 Rolled back transaction for test: [DefaultTestContext@4229bb3f testClass = TransactionApplicationTests, testInstance = com.example.transaction.TransactionApplicationTests@7c711375, testMethod = testUser@TransactionApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@56cdfb3b testClass = TransactionApplicationTests, locations = '{}', classes = '{class com.example.transaction.TransactionApplication, class com.example.transaction.TransactionApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@17046283, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@5a63f509, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@7a69b07, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@c33b74f], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

猜测可能是哪个地方不支持NESTED,或者说我哪里写错了?这里无法试验出NESTED完整的特性。

如果,支持NESTED的话,可以总结NESTED的特性:

如果外围方法没开启事务,此时同REQUIRED

如果外围方法开启事务,外围事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务


总结

我们使用一张表,来总结七种行为的特性(传播行为是针对于子方法的):

                 传播行为

                                                                        特性

 

               REQUIRED

  1. 外围方法开启事务,子方法直接加入到外围事务中,形成一个整体事务。
  2. 外围方法没开启事务,则子方法创建独立的事务,不同子方法创建的事务互不干扰,可以独立回滚或提交。

 

             SUPPORTS

  1. 外围方法开启事务,子方法一起加入到外围事务中,形成一个整体事务。
  2. 外围方法没开启事务,则子方法直接不使用事务。

 

            MANDATORY

  1. 外围方法开启事务,子方法一起加入到外围事务中,形成一个整体事务。
  2. 外围方法没开启事务,则子方法直接抛出异常,强制需要外围方法有事务。

         REQUIRES_NEW

  1. 不管外围方法有没有开启事务,子方法都会创建一个属于自己的事务,子方法创建的事务互不干扰,与外围方法也互不干扰。

         NOT_SUPPORTED

  1. 不管外围方法有没有开启事务,子方法都不想去使用事务,外围方法回滚时,不会回滚子方法。

                    NEVER

  1. 外围方法开启事务,则子方法直接抛出异常。
  2. 外围方法没开启事务,子方法也不会使用事务。

 

                  NESTED

  1. 外围方法开启事务,外围事务回滚时,子事务会全部回滚。但某一个子事务由于异常回滚时,不会影响外围事务与其他子事务。
  2. 外围方法没开启事务,同REQUIRED2)。
发布了184 篇原创文章 · 获赞 621 · 访问量 117万+

猜你喜欢

转载自blog.csdn.net/qq_33591903/article/details/105573727