专栏首页小灰灰SpringBoot 系列教程之编程式事务使用姿势介绍篇

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

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

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

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

<!-- more -->

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. 其他

源码

1. 一灰灰 Blog

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring学习之事物的使用姿势一览

    主要记录下spring是如何支持事物的,以及在Spring结合mybatis时,可以怎么简单的实现数据库的事物功能

    一灰灰blog
  • SpringBoot 系列教程 Mybatis+注解整合篇

    上一篇博文介绍了 SpringBoot 整合 mybatis 的过程,但是 xml 的方式,总感觉让人有点蛋疼;本文将介绍一种 noxml 的使用姿势,纯用注解...

    一灰灰blog
  • SpringBoot 系列教程之事务隔离级别知识点小结

    上一篇博文介绍了声明式事务@Transactional的简单使用姿势,最文章的最后给出了这个注解的多个属性,本文将着重放在事务隔离级别的知识点上,并通过实例演示...

    一灰灰blog
  • Spring学习之事物的使用姿势一览

    主要记录下spring是如何支持事物的,以及在Spring结合mybatis时,可以怎么简单的实现数据库的事物功能

    一灰灰blog
  • SpringBoot 系列教程 Mybatis+注解整合篇

    上一篇博文介绍了 SpringBoot 整合 mybatis 的过程,但是 xml 的方式,总感觉让人有点蛋疼;本文将介绍一种 noxml 的使用姿势,纯用注解...

    一灰灰blog
  • 事务处理(一) - spring事务

    Spring是通过org.springframework.transaction.PlatformTransactionManager接口来实现事务的管理。同时...

    逝兮诚
  • 重学javascript 红皮高程

    最近的先行者计划的学习内容是JS高级程序设计,正好我也跟着再重过一遍JS基础的内容。 这行做到现在,我用JQ,写一般的应用,业务逻辑,插件、组件,只要逻辑清晰写...

    web前端教室
  • Yii2 学习笔记之数据库篇

    琯琯
  • iOS中通知中心(NSNotificationCenter)的使用总结

    @property (readonly, copy) NSString *name;

    珲少
  • R语言 | 根据数据框的顺序进行筛选

    这里有两个数据框,两者有相同的列(ID),这里想把第一个数据框,按照第二个数据框的ID列进行提取,顺序和第二个数据框一致。

    邓飞

扫码关注云+社区

领取腾讯云代金券