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

栏目: IT技术 · 发布时间: 6年前

内容简介:当我们希望一组操作,要么都成功,要么都失败时,往往会考虑里利用事务来实现这一点;之前介绍的db操作,主要在于单表的CURD,本文将引入声明式事务本篇主要介绍的是创建一个SpringBoot项目,版本为

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

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相关的信息

```properties
## 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的操作被回滚了

============ 事务正常工作 start ========== 
transaction before >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=130, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
============ 事务正常工作 end ==========

4. 注意事项

a. 适用场景

在使用注解 @Transactional 声明式事务时,其主要是借助AOP,通过代理来封装事务的逻辑,所以aop不生效的场景,也适用于这个事务注解不生效的场景

简单来讲,下面几种case,注解不生效

  • private方法上装饰 @Transactional ,不生效
  • 内部调用,不生效
    • 举例如: 外部调用服务A的普通方法m,而这个方法m,调用本类中的声明有事务注解的方法m2, 正常场景下,事务不生效

b. 异常类型

此外,注解 @Transactional 默认只针对运行时异常生效,如下面这种case,虽然是抛出了异常,但是并不会生效

@Transactional
public boolean testNormalException(int id) throws Exception {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new Exception("声明异常");
}

如果需要它生效,可以借助 rollbackFor 属性来指明,触发回滚的异常类型

@Transactional(rollbackFor = Exception.class)
public boolean testSpecialException(int id) throws Exception {
    if (this.updateName(id)) {
        this.query("after updateMoney name", id);
        if (this.updateMoney(id)) {
            return true;
        }
    }

    throw new IllegalArgumentException("参数异常");
}

测试一下上面的两种case

public void testSimpleCase() {
    System.out.println("============ 事务不生效 start ========== ");
    simpleService.query("transaction before", 140);
    try {
        // 因为抛出的是非运行异常,不会回滚
        simpleService.testNormalException(140);
    } catch (Exception e) {
    }
    simpleService.query("transaction end", 140);
    System.out.println("============ 事务不生效 end ========== \n");
    
    
    System.out.println("============ 事务生效 start ========== ");
    simpleService.query("transaction before", 150);
    try {
        // 注解中,指定所有异常都回滚
        simpleService.testSpecialException(150);
    } catch (Exception e) {
    }
    simpleService.query("transaction end", 150);
    System.out.println("============ 事务生效 end ========== \n");
}

输出结果如下,正好验证了上面提出的内容

============ 事务不生效 start ========== 
transaction before >>>> {id=140, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=140, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=140, name=更新, money=210, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
============ 事务不生效 end ========== 

============ 事务生效 start ========== 
transaction before >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
after updateMoney name >>>> {id=150, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0}
transaction end >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0}
============ 事务生效 end ==========

c. @Transactional 注解的属性信息

上面的内容,都属于比较基本的知识点,足以满足我们一般的业务需求,如果需要进阶的话,有必要了解一下属性信息

以下内容来自: 透彻的掌握 Spring 中@transactional 的使用

属性名 说明
name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation 事务的传播行为,默认值为 REQUIRED。
isolation 事务的隔离度,默认值采用 DEFAULT。
timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

关于上面几个属性的使用实例,以及哪些情况下,会导致声明式事务不生效,会新开坑进行说明,敬请期待。。。

II. 其他

0. 系列博文&源码

系列博文

源码

1. 一灰灰Blog

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

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

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

打赏 如果觉得我的文章对您有帮助,请随意打赏。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Philosophy of Software Design

A Philosophy of Software Design

John Ousterhout / Yaknyam Press / 2018-4-6 / GBP 14.21

This book addresses the topic of software design: how to decompose complex software systems into modules (such as classes and methods) that can be implemented relatively independently. The book first ......一起来看看 《A Philosophy of Software Design》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具