zoukankan      html  css  js  c++  java
  • SpringBoot系列教程之事务传递属性

    200202-SpringBoot系列教程之事务传递属性

    对于mysql而言,关于事务的主要知识点可能几种在隔离级别上;在Spring体系中,使用事务的时候,还有一个知识点事务的传递属性同样重要,本文将主要介绍7中传递属性的使用场景

    I. 配置

    本文的case,将使用声明式事务,首先我们创建一个SpringBoot项目,版本为2.2.1.RELEASE,使用mysql作为目标数据库,存储引擎选择Innodb,事务隔离级别为RR

    1. 项目配置

    在项目pom.xml文件中,加上spring-boot-starter-jdbc,会注入一个DataSourceTransactionManager的bean,提供了事务支持

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    

    2. 数据库配置

    进入spring配置文件application.properties,设置一下db相关的信息

    ## DataSource
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=
    

    3. 数据库

    新建一个简单的表结构,用于测试

    CREATE TABLE `money` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
      `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
      `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
      `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `name` (`name`)
    ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;
    

    II. 使用说明

    0. 准备

    在正式开始之前,得先准备一些基础数据

    @Component
    public class PropagationDemo {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @PostConstruct
        public void init() {
            String sql = "replace into money (id, name, money) values (420, '初始化', 200)," + "(430, '初始化', 200)," +
                    "(440, '初始化', 200)," + "(450, '初始化', 200)," + "(460, '初始化', 200)," + "(470, '初始化', 200)," +
                    "(480, '初始化', 200)," + "(490, '初始化', 200)";
            jdbcTemplate.execute(sql);
        }
    }
    

    其次测试事务的使用,我们需要额外创建一个测试类,后面的测试case都放在类PropagationSample中; 为了使输出结果更加友好,提供了一个封装的call方法

    @Component
    public class PropagationSample {
        @Autowired
        private PropagationDemo propagationDemo;
        
        private void call(String tag, int id, CallFunc<Integer> func) {
            System.out.println("============ " + tag + " start ========== ");
            propagationDemo.query(tag, id);
            try {
                func.apply(id);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            propagationDemo.query(tag, id);
            System.out.println("============ " + tag + " end ========== 
    ");
        }
    
    
        @FunctionalInterface
        public interface CallFunc<T> {
            void apply(T t) throws Exception;
        }
    }
    

    1. REQUIRED

    也是默认的传递属性,其特点在于

    • 如果存在一个事务,则在当前事务中运行
    • 如果没有事务则开启一个新的事务

    使用方式也比较简单,不设置@Transactional注解的propagation属性,或者设置为 REQUIRED即可

    /**
     * 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
     *
     * @param id
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void required(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("required: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    
        throw new Exception("事务回滚!!!");
    }
    

    上面就是一个基础的使用姿势

    private void testRequired() {
        int id = 420;
        call("Required事务运行", id, propagationDemo::required);
    }
    

    输出结果如下

    ============ Required事务运行 start ========== 
    Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    required: after updateMoney name >>>> {id=420, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    事务回滚!!!
    Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    ============ Required事务运行 end ========== 
    

    2. SUPPORTS

    其特点是在事务里面,就事务执行;否则就非事务执行,即

    • 如果存在一个事务,支持当前事务
    • 如果没有事务,则非事务的执行

    使用姿势和前面基本一致

    @Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void support(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("support: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    
        throw new Exception("事务回滚!!!");
    }
    

    这个传递属性比较特别,所以我们的测试case需要两个,一个事务调用,一个非事务调用

    测试事务调用时,我们新建一个bean: PropagationDemo2,下面的support方法支持事务运行

    @Component
    public class PropagationDemo2 {
        @Autowired
        private PropagationDemo propagationDemo;
    
        @Transactional(rollbackFor = Exception.class)
        public void support(int id) throws Exception {
            // 事务运行
            propagationDemo.support(id);
        }
    }
    

    对于非事务调用,则是直接在测试类中调用(请注意下面的call方法,调用的是两个不同bean中的support方法)

    private void testSupport() {
        int id = 430;
        // 非事务方式,异常不会回滚
        call("support无事务运行", id, propagationDemo::support);
    
        // 事务运行
        id = 440;
        call("support事务运行", id, propagationDemo2::support);
    }
    

    输出结果如下:

    ============ support无事务运行 start ========== 
    support无事务运行 >>>> {id=430, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    support: after updateMoney name >>>> {id=430, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    事务回滚!!!
    support无事务运行 >>>> {id=430, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    ============ support无事务运行 end ========== 
    
    ============ support事务运行 start ========== 
    support事务运行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    support: after updateMoney name >>>> {id=440, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    事务回滚!!!
    support事务运行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    ============ support事务运行 end ========== 
    

    从上面的输出,也可以得出结果:非事务执行时,不会回滚;事务执行时,回滚

    3. MANDATORY

    需要在一个正常的事务内执行,否则抛异常

    使用姿势如下

    @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void mandatory(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("mandatory: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    
        throw new Exception("事务回滚!!!");
    }
    

    这种传播属性的特点是这个方法必须在一个已有的事务中运行,所以我们的测试case也比较简单,不再事务中运行时会怎样?

    private void testMandatory() {
        int id = 450;
        // 非事务方式,抛异常,这个必须在一个事务内部执行
        call("mandatory非事务运行", id, propagationDemo::mandatory);
    }
    

    输出结果

    ============ mandatory非事务运行 start ========== 
    mandatory非事务运行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    No existing transaction found for transaction marked with propagation 'mandatory'
    mandatory非事务运行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    ============ mandatory非事务运行 end ========== 
    

    从上面的输出可知,直接抛出了异常,并不会执行方法内的逻辑

    4. NOT_SUPPORT

    这个比较有意思,被它标记的方法,总是非事务地执行,如果存在活动事务,则挂起

    (实在是没有想到,有什么场景需要这种传播属性)

    一个简单的使用case如下:

    @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
    public void notSupport(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("notSupport: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
        throw new Exception("回滚!");
    }
    

    接下来需要好好的想一下我们的测试用例,首先是它需要在一个事务中调用,外部事物失败回滚,并不会影响上面这个方法的执行结果

    我们在PropagationDemo2中,添加测试case如下

    @Transactional(rollbackFor = Exception.class)
    public void notSupport(int id) throws Exception {
        // 挂起当前事务,以非事务方式运行
        try {
            propagationDemo.notSupport(id);
        } catch (Exception e) {
        }
    
        propagationDemo.query("notSupportCall: ", id);
        propagationDemo.updateName(id, "外部更新");
        propagationDemo.query("notSupportCall: ", id);
        throw new Exception("回滚");
    }
    

    输出结果如下

    ============ notSupport start ========== 
    notSupport >>>> {id=460, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    notSupport: after updateMoney name >>>> {id=460, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    notSupportCall:  >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    notSupportCall:  >>>> {id=460, name=外部更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    回滚
    notSupport >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    ============ notSupport end ========== 
    

    从上面输出可以看出

    • NOT_SUPPORT 标记的方法,属于非事务运行(因为抛异常,修改没有回滚)
    • 外部事务回滚,不会影响其修改

    5. NEVER

    总是非事务地执行,如果存在一个活动事务,则抛出异常。

    使用姿势如下

    /**
     * 总是非事务地执行,如果存在一个活动事务,则抛出异常。
     *
     * @param id
     * @throws Exception
     */
    @Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    public void never(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("notSupport: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    }
    

    我们的测试就比较简单了,如果在事务中运行,是不是会抛异常

    PropagationDemo2中,添加一个事务调用方法

    @Transactional(rollbackFor = Exception.class)
    public void never(int id) throws Exception {
        propagationDemo.never(id);
    }
    

    测试代码

    private void testNever() {
        int id = 470;
        call("never非事务", id, propagationDemo2::never);
    }
    

    输出结果

    ============ never非事务 start ========== 
    never非事务 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    Existing transaction found for transaction marked with propagation 'never'
    never非事务 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    ============ never非事务 end ==========
    

    直接抛出了异常,并没有执行方法内的业务逻辑

    6. NESTED

    其主要特点如下

    • 如果不存在事务,则开启一个事务运行
    • 如果存在事务,则运行一个嵌套事务;

    上面提出了一个嵌套事务的概念,什么是嵌套事务呢?

    • 一个简单的理解:外部事务回滚,内部事务也会被回滚;内部事务回滚,外部无问题,并不会回滚外部事务

    接下来设计两个测试用例,一个是内部事务回滚;一个是外部事务回滚

    a. case1 内部事务回滚

    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void nested(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("nested: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    
        throw new Exception("事务回滚!!!");
    }
    

    PropagationDemo2这个bean中,添加一个外部事务,捕获上面方法的异常,因此外部执行正常

    @Transactional(rollbackFor = Exception.class)
    public void nested(int id) throws Exception {
        propagationDemo.updateName(id, "外部事务修改");
        propagationDemo.query("nestedCall: ", id);
        try {
            propagationDemo.nested(id);
        } catch (Exception e) {
        }
    }
    

    测试代码

    private void testNested() {
        int id = 480;
        call("nested事务", id, propagationDemo2::nested);
    }
    

    输出结果如下

    ============ nested事务 start ========== 
    nested事务 >>>> {id=480, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    nestedCall:  >>>> {id=480, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    nested: after updateMoney name >>>> {id=480, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    nested事务 >>>> {id=480, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    ============ nested事务 end ==========
    

    仔细看一下上面的结果,外部事务修改的结果都被保存了,内部事务的修改被回滚了,没有影响最终的结果

    b. case2 外部事务回滚

    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void nested2(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("nested: after updateMoney name", id);
            if (this.updateMoney(id)) {
                return;
            }
        }
    }
    

    PropagationDemo2这个bean中,添加一个外部事务,内部事务正常,但是外部事务抛异常,主动回滚

    @Transactional(rollbackFor = Exception.class)
    public void nested2(int id) throws Exception {
        // 嵌套事务,外部回滚,会同步回滚内部事务
        propagationDemo.updateName(id, "外部事务修改");
        propagationDemo.query("nestedCall: ", id);
        propagationDemo.nested2(id);
        throw new Exception("事务回滚");
    }
    

    测试代码

    private void testNested() {
        int id = 490;
        call("nested事务2", id, propagationDemo2::nested2);
    }
    

    输出结果如下

    ============ nested事务2 start ========== 
    nested事务2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    nestedCall:  >>>> {id=490, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    nested: after updateMoney name >>>> {id=490, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
    事务回滚
    nested事务2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
    ============ nested事务2 end ========== 
    

    仔细看上面的输出,对别case1,其特别在于全部回滚了,内部事务的修改也被回滚了

    7. REQUIRES_NEW

    这个和上面的NESTED有点相似,但是又不一样

    • 当存在活动事务时,新创建一个事务执行
    • 当不存在活动事务时,和REQUIRES效果一致,创建一个事务执行

    注意

    REQUIRES_NEWNESTED相比,两个事务之间没有关系,任何一个回滚,对另外一个无影响

    测试case和前面差不多,不多做细说...

    8. 小结

    前面介绍了7中传播属性,下面简单对比和小结一下

    事务 特点
    REQUIRED 默认,如果存在事务,则支持当前事务;不存在,则开启一个新事务
    SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
    MANDATORY 需要在一个正常的事务内执行,否则抛异常
    REQUIRES_NEW 不管存不存在事务,都开启一个新事务
    NOT_SUPPORTED 不管存不存在,都以非事务方式执行,当存在事务时,挂起事务
    NEVER 非事务方式执行,如果存在事务,则抛异常
    NESTED 如果不存在事务,则开启一个事务运行;如果存在事务,则运行一个嵌套事务

    II. 其他

    0. 系列博文&源码

    系列博文

    源码

    1. 一灰灰Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

  • 相关阅读:
    敏捷软件开发——迭代计划版本号
    01背包问题
    南邮算法分析和实验设计1 分而治之
    PowerShell与Unix Shell对比:八大实例
    grep、sed、awk、perl、js、vim等对正则表达式的支持的差别
    perl的一些基本用法
    Lua学习笔记1
    在eclipse中建立lua开发环境
    MYSQL设置远程账户登陆总结,mysql修改、找回密码、增加新用户,MySQL数据库的23个注意事项
    LuaForWindows_v5.1.4-45和lua-5.1.4.tar.gz
  • 原文地址:https://www.cnblogs.com/yihuihui/p/12254880.html
Copyright © 2011-2022 走看看