SpringBoot 系列教程之声明式事务 Transactional

当我们希望一组操作,要么都成功,要么都失败时,往往会考虑利用事务来实现这一点;之前介绍的 db 操作,主要在于单表的 CURD,本文将主要介绍声明式事务@Transactional的使用姿势

<!-- more -->

I. 配置

本篇主要介绍的是jdbcTemplate配合事务注解@Transactional的使用姿势,至于 JPA,mybatis 在实际的使用区别上,并不大,后面会单独说明

创建一个 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. 初始化

为了体现事务的特点,在不考虑 DDL 的场景下,DML 中的增加,删除 or 修改属于不可缺少的语句了,所以我们需要先初始化几个用于测试的数据

@Service
public class SimpleDemo {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        String sql = "replace into money (id, name, money) values (120, '初始化', 200)," +
                "(130, '初始化', 200)," +
                "(140, '初始化', 200)," +
                "(150, '初始化', 200)";
        jdbcTemplate.execute(sql);
    }
}

我们使用replace into语句来初始化数据,每次 bean 创建之后都会执行,确保每次执行后面你的操作时,初始数据都一样

2. transactional

这个注解可以放在类上,也可以放在方法上;如果是标注在类上,则这个类的所有公共方法,都支持事务;

如果类和方法上都有,则方法上的注解相关配置,覆盖类上的注解

下面是一个简单的事务测试 case

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;
}

/**
 * 运行异常导致回滚
 *
 * @return
 */
@Transactional
public boolean testRuntimeExceptionTrans(int id) {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new RuntimeException("更新失败,回滚!");
}

在我们需要开启事务的公共方法上添加注解@Transactional,表明这个方法的正确调用姿势下,如果方法内部执行抛出运行异常,会出现事务回滚

注意上面的说法,正确的调用姿势,事务才会生效;换而言之,某些 case 下,不会生效

3. 测试

接下来,测试一下上面的方法事务是否生效,我们新建一个 Bean

@Component
public class TransactionalSample {
    @Autowired
    private SimpleDemo simpleService;

    public void testSimpleCase() {
        System.out.println("============ 事务正常工作 start ========== ");
        simpleService.query("transaction before", 130);
        try {
            // 事务可以正常工作
            simpleService.testRuntimeExceptionTrans(130);
        } catch (Exception e) {
        }
        simpleService.query("transaction end", 130);
        System.out.println("============ 事务正常工作 end ========== \n");
    }
}

在上面的调用中,打印了修改之前的数据和修改之后的数据,如果事务正常工作,那么这两次输出应该是一致的

实际输出结果如下,验证了事务生效,中间的修改 name 的操作被回滚了

https://www.imdb.com/list/ls080444490/
https://www.imdb.com/list/ls080444484/
https://www.imdb.com/list/ls080444953/
https://www.imdb.com/list/ls080444917/
https://www.imdb.com/list/ls080444966/
https://www.imdb.com/list/ls080444949/
https://www.imdb.com/list/ls080444856/
https://www.imdb.com/list/ls080444819/
https://www.imdb.com/list/ls080444893/
https://www.imdb.com/list/ls080449003/
https://www.imdb.com/list/ls080444490/?publish=publish
https://www.imdb.com/list/ls080444484/?publish=publish
https://www.imdb.com/list/ls080444953/?publish=publish
https://www.imdb.com/list/ls080444917/?publish=publish
https://www.imdb.com/list/ls080444966/?publish=publish
https://www.imdb.com/list/ls080444949/?publish=publish
https://www.imdb.com/list/ls080444856/?publish=publish
https://www.imdb.com/list/ls080444819/?publish=publish
https://www.imdb.com/list/ls080444893/?publish=publish
https://www.imdb.com/list/ls080449003/?publish=publish
https://www.imdb.com/list/ls080444490/?mode=desktop
https://www.imdb.com/list/ls080444484/?mode=desktop
https://www.imdb.com/list/ls080444953/?mode=desktop
https://www.imdb.com/list/ls080444917/?mode=desktop
https://www.imdb.com/list/ls080444966/?mode=desktop
https://www.imdb.com/list/ls080444949/?mode=desktop
https://www.imdb.com/list/ls080444856/?mode=desktop
https://www.imdb.com/list/ls080444819/?mode=desktop
https://www.imdb.com/list/ls080444893/?mode=desktop
https://www.imdb.com/list/ls080449003/?mode=desktop

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值