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 或者有更好的建议,欢迎批评指正,不吝感激

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

SpringBoot 系列教程之编程式事务使用姿势介绍篇的更多相关文章

  1. SpringBoot系列教程JPA之新增记录使用姿势

    SpringBoot系列教程JPA之新增记录使用姿势 上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第 ...

  2. SpringBoot系列教程之事务传递属性

    200202-SpringBoot系列教程之事务传递属性 对于mysql而言,关于事务的主要知识点可能几种在隔离级别上:在Spring体系中,使用事务的时候,还有一个知识点事务的传递属性同样重要,本文 ...

  3. SpringBoot 系列教程之事务隔离级别知识点小结

    SpringBoot 系列教程之事务隔离级别知识点小结 上一篇博文介绍了声明式事务@Transactional的简单使用姿势,最文章的最后给出了这个注解的多个属性,本文将着重放在事务隔离级别的知识点上 ...

  4. SpringBoot 系列教程之事务不生效的几种 case

    SpringBoot 系列教程之事务不生效的几种 case 前面几篇博文介绍了声明式事务@Transactional的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑.本文 ...

  5. Spring编程式事务管理及声明式事务管理

    本文将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. Spring 事务属性分析 事务管理 ...

  6. 全面分析 Spring 的编程式事务管理及声明式事务管理

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  7. 全面分析 Spring 的编程式事务管理及声明式事务管理--转

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  8. spring事务管理——编程式事务、声明式事务

    本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 ...

  9. Java工程师之SpringBoot系列教程前言&目录

    前言 与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代步遍布的时候,你就行该想到我能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的时间在应用中,而不用担心技术的更新换代.但 ...

随机推荐

  1. 非阻塞多路IO

    socket.listen() rfds=[] wfds=[] while(select(rfds,wfds,timeout)){//事件循环 client=socket.accept(timeout ...

  2. memset函数总结

    之前有一个程序栽在了memset函数上面,对memset函数一直耿耿于怀,于是想总结一下这个常用但是总出错的函数. memset在string.h文件中是这么定义的: void*代表这个函数的返回值可 ...

  3. vue使用videojs控制后台m3u8数据请求

    关于Video.js的使用方法就不再说了,有兴趣的请迁跃:https://videojs.com/ VideoJS中并没有stop之类控制后台数据请求的参数,只有暂停 video.pause()方法 ...

  4. Oracle错误览表

    Oracle 错误总结及问题解决 ORA     本文转自:https://www.cnblogs.com/zhangwei595806165/p/4972016.html  作者@承影剑 ORA-0 ...

  5. python练习题4

    1.将字符串“老男孩”转换成UTF-8编码的字节类型 s = "老男孩" bytes(s,'utf8') s.encode('utf8') 2.简述globals(),locals ...

  6. 003-explode分割字符串

    <?php $str = "品牌电脑@品牌手机@高档男士衬衫@高档女士挎包"; //定义字符串常量 $str_arr = explode("@", $st ...

  7. BFC的基础理解及应用场景

    最近学习了BFC,开始学习的时候,单纯看概念,有种云里雾里的感觉,字都认识,凑一起啥意思大致也知道,但是具体有什么用呢? 这个就有点迷迷糊糊的,经过老师的讲解,以及自己课后的代码实验与总结,就拨云见日 ...

  8. 2 JVM 运行机制

  9. spring boot 接口service有多个实现类

    接口.java public interface DeService { } 接口实现类1.java @Service("ud")public class DeServiceImp ...

  10. Ubuntu19.04的安装过程详解以及操作系统初始化配置

    Ubuntu19.04的安装过程详解以及操作系统初始化配置                                                                       ...