• SpringBoot 系列教程之编程式事务使用姿势介绍篇


    SpringBoot 系列教程之编程式事务使用姿势介绍篇

    前面介绍的几篇事务的博文,主要是利用@Transactional注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用了事务);然而缺点也比较明显,不够灵活,稍不注意,可能就因为姿势不对,导致事务不生效

    本文将介绍另外一种事务的使用姿势,借助TransactionTemplate的编程式事务

    I. 配置

    本篇主要介绍的是jdbcTemplate+transactionTemplate来完成一个编程式事务的实例 demo

    创建一个 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. 使用说明

    1. 初始化

    创建几条数据,用于事务操作

    @Service
    public class ManualDemo {
        @Autowired
        private TransactionTemplate transactionTemplate;
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @PostConstruct
        public void init() {
            String sql = "replace into money (id, name, money) values (220, '初始化', 200)";
            jdbcTemplate.execute(sql);
        }
    }
    

    2. 使用 case

    为了演示事务的特性,我们设计几个简单的 sql 操作,并抛出异常,引发回滚,如下

    • 在 doUpdate 方法中,显示更新 name,输出更新的结果,然后再更新 money 的值,最后抛出一个异常,希望事务回滚
    private boolean doUpdate(int id) throws Exception {
        if (this.updateName(id)) {
            this.query("after updateMoney name", id);
            if (this.updateMoney(id)) {
                return true;
            }
        }
    
        throw new Exception("参数异常");
    }
    
    
    private boolean updateName(int id) {
        String sql = "update money set `name`='更新' where id=" + id;
        jdbcTemplate.execute(sql);
        return true;
    }
    
    public void query(String tag, int id) {
        String sql = "select * from money where id=" + id;
        Map map = jdbcTemplate.queryForMap(sql);
        System.out.println(tag + " >>>> " + map);
    }
    
    private boolean updateMoney(int id) {
        String sql = "update money set `money`= `money` + 10 where id=" + id;
        jdbcTemplate.execute(sql);
        return false;
    }
    

    上面这一端逻辑,如果看了前面几篇博文,会比较熟悉,区别在于 doUpdate 方法上面没有添加@Transactional注解,当下它的调用并不会在事务中执行

    接下来我们看一下编程式事务的核心写法

    public void testTransaction(int id) {
        transactionTemplate.execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus transactionStatus) {
                try {
                    return doUpdate(id);
                } catch (Exception e) {
                    transactionStatus.setRollbackOnly();
                    return false;
                }
            }
        });
    }
    

    如上,将方法的调用,封装在transactionTemplate.execute的调用中,通过设置transactionStatus.setRollbackOnly()来标记回滚

    通过前面几篇博文的学习我们知道实际使用时,事务的隔离级别,传递属性也很重要,在编程式事务中,当然也是可以设置的

    // 设置隔离级别
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
    // 设置传播属性
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    

    最后写一个测试代码,验证一下是否生效

    @Component
    public class TransactionalSample {
        @Autowired
        private ManualDemo manualDemo;
    
        public void testManualCase() {
            System.out.println("======= 编程式事务 start ========== ");
            manualDemo.query("transaction before", 220);
            manualDemo.testTransaction(220);
            manualDemo.query("transaction end", 220);
            System.out.println("======= 编程式事务 end ========== ");
        }
    }
    

    输出结果如下,最终数据 big 没有被修改

    ======= 编程式事务 start ==========
    transaction before >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
    after updateMoney name >>>> {id=220, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
    transaction end >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
    ======= 编程式事务 end ==========
    

    III. 其他

    0. 系列博文&源码

    系列博文

    源码

    1. 一灰灰 Blog

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

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

    一灰灰blog

  • 相关阅读:
    Linux文本检索命令grep笔记
    Linux文本检索命令grep笔记
    Linux文件查询笔记
    Linux文件查询笔记
    Linux文件默认权限和umask笔记
    Linux文件默认权限和umask笔记
    Linux关于文件的权限笔记
    Linux关于文件的权限笔记
    Linux文件操作实用笔记
    6.创建自定义菜单
  • 原文地址:https://www.cnblogs.com/yihuihui/p/12262467.html
Copyright © 2020-2023  润新知