Spring事务和事务传播机制

1. 事务回顾

1.1 什么是事务?

事务是一组操作的集合,是一个不可分割的操作。

事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。

1.2 为什么需要事务

我们在进行程序开发时,也会有事务的需求。

比如转账操作:第一步:A账户-100元。

第二步:B账户+100元。

如果没有事务,第一步执行成功了,第二步执行失败了,那么A账户的100元就平白无故消失了。如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。

1.3 事务的操作

事务操作的主要有三步:

  1. 开启事务start transaction/begin(一组操作前开启事务)。

  2. 提交事务:commit(这组操作全部成功,提交事务)。

  3. 回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)。

sql 复制代码
-- 开启事务 
start transaction;
-- 提交事务 
commit;
-- 回滚事务 
rollback;

2. Spring中事务的实现

Spring对事务也进行了实现。

Spring中的事务操作分为两类:

  1. 编程式事务:(手动写代码操作事务)。

  2. 声明式事务:(利用注解自动开启和提交事务)。

示例:用户注册,注册时在日志中插入一条操作记录。

数据准备:

sql 复制代码
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;

-- 使用该数据库
USE trans_test;

-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
    `id` INT NOT NULL AUTO_INCREMENT,
    `user_name` VARCHAR(128) NOT NULL,
    `password` VARCHAR(128) NOT NULL,
    `create_time` DATETIME DEFAULT NOW(),
    `update_time` DATETIME DEFAULT NOW() ON UPDATE NOW(),
    PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '用户表';

-- 操作日志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `user_name` VARCHAR(128) NOT NULL,
    `op` VARCHAR(256) NOT NULL,
    `create_time` DATETIME DEFAULT NOW(),
    `update_time` DATETIME DEFAULT NOW() ON UPDATE NOW()
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

代码准备:

1. 创建项目 spring-trans,引入Spring Web,Mybatis,mysql等依赖。

2. 配置文件

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

3. 实体类

java 复制代码
@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Date createTime;
    private Date updateTime;
}
java 复制代码
@Data
public class LogInfo {
    private Integer id;
    private String userName;
    private String op;
    private Date createTime;
    private Date updateTime;
}

4. Mapper

java 复制代码
@Mapper
public interface LogInfoMapper {
    @Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
    Integer insertLog(String name,String op);
}

@Mapper
public interface UserInfoMapper {
    @Insert("insert into user_info(`user_name`,`password`)values(#{name},#{password})")
    Integer insert(String username,String password);
}

5. Service

java 复制代码
@Slf4j
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    public void insertLog(String name,String op){
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤⼾注册");
    }
}
java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public void registryUser(String name,String password){
        userInfoMapper.insert(name,password);
    }
}

6. Controller

java 复制代码
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/registry")
    public String registry(String name,String password){
        userService.registryUser(name,password);
        return "注册成功";
    }
}

3. @Transactional详解

@Transactional注解当中的三个常见属性:

  1. rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类型。

  2. Isolation:事务的隔离级别。默认值为Isoation.DEFAULT

3.propagation:事务的传播机制。默认值为Propagation.REQUIRED

3.1 rollbackFor

@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不会滚。即Exception的子类中,除了RuntimeException及其子类。

3.2 事务的隔离级别

3.2.1 MySQL事务的隔离级别

读未提交、读已提交、可重复读、串行化

3.2.2 Spring事务隔离级别

Spring中事务的隔离级别有5种:

  1. solation.DEFAULT:以连接的数据库的事务隔离级别为主。

  2. Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中READ UNCOMMITTED。

  3. Isolation.READ_COMMITTED:读已提交,对应SQL标准中READ COMMITTED。

  4. Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ。

  5. Isolation.SERIALIZABLE:串行化,对应SQL标准中SERIALIZABLE。

3.3 Spring事务传播机制

3.3.1 什么是事务的传播机制

事务传播机制就是:多个事务方法存在调用关系时,事务是如何在这些方法间传播的。

比如有两个方法A,B都被 @Transactional 修饰,A方法调用B方法A方法运行时,会开启一个事务。当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,还是创建一个新的事务呢?

比如公司流程管理,执行任务之前,需要先写执行文档,任务执行结束,再写总结汇报。

此时A部门有一项工作,需要B部门的支援,此时B部门是直接使用A部门的文档,还是新建一个文档呢?

事务隔离级别解决的是多个事务同时调用一个数据库的问题

而事务传播机制解决的是一个事务在多个节点方法种传递的问题

3.3.2 事务的传播机制有哪些

@Transactional注解支持事务传播机制的设置,通过propagation属性来指定传播行为。

Spring事务传播机制有以下7种:

  1. Propagation.REQUIRED:默认的事务传播级别,如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务。

  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。

  3. Propagation.MANDATORY:强制性,如果当前存在事务,则加入该事务。

  4. Propagation.REQUIRES_NEW:创建一个新的事务。如果当前存在事务,则把当前事务挂起。不管外部方法是否开启事务,它都会新开启自己的事务,且开启的事务互相独立,互不干扰。

  5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

  7. Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED 。

3.3.3 Spring事务传播机制的使用和演示

演示两个:REQUIRED、REQUIRES_NEW

3.3.3.1 REQUIRED(加入事务)

看下面代码如何实现:

  1. 用户注册,插入一条数据

  2. 记录操作日志,插入一条数据(出现异常)

java 复制代码
@RestController
@RequestMapping("/propaga")
public class PropagationController {

    @Autowired
    private UserService userService;

    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/p1")
    public String r3(String name, String password) {
        // 用户注册
        userService.registryUser(name, password);
        // 记录操作日志
        logService.insertLog(name, "用户注册");
        return "r3";
    }
}
java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}
java 复制代码
@Slf4j
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name, String op) {
        int a = 10 / 0; // 💥 必然抛出 ArithmeticException(RuntimeException)
        logInfoMapper.insertLog(name, "用户注册");
    }
}

运行程序,发现数据库没有插入任何数据。

流程描述:

  1. p1方法开启事务

  2. 用户注册,插入一条数据(执行成功)

  3. 记录操作日志,插入一条数据(出现异常,执行失败)(和p1使用同一个事务)

  4. 因为步骤3出现异常,事务回滚,步骤2和3使用同一个事务,所以步骤2的数据也回滚了

3.3.3.2 REQUIRES_NEW(新建事务)
java 复制代码
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}
java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
    int a = 10 / 0; // 💥 抛出 ArithmeticException(RuntimeException)
    logInfoMapper.insertLog(name, "用户注册");
}

运行程序,发现用户数据插入成功了,日志表数据插入失败。

LogService方法中的事务不影响UserService中的事务。

4. 总结

  1. Spring中使用事务,有两种方式:编程式事务(手动操作)和声明式事务。其中声明式事务使用较多,在方法上添加@Transactional 就可以实现了。

  2. 通过@Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别。Spring中的事务隔离级别有5种。

  3. 通过@Transactional(propagation =Propagation.REQUIRED)设置事务的传播机制,Spring中的 事务传播级别有7种,重点关注REQUIRED (默认值)和REQUIRES_NEW。

相关推荐
扶苏-su5 分钟前
Java--打印流
java·开发语言
n***33355 分钟前
MySQL数据可视化实战指南
数据库·mysql·信息可视化
Kevin-anycode5 分钟前
如何将自己的应用上传文件功能对接到群辉的NAS上
java·unix
幽络源小助理5 分钟前
SpringBoot+Vue旅游推荐系统源码 | 幽络源
java·开发语言·spring boot
忧郁的Mr.Li7 分钟前
Redis的过期删除策略和内存淘汰策略
数据库·redis·缓存
丶小鱼丶9 分钟前
Java基础之【排序算法】
java·算法
csdnfanguyinheng10 分钟前
生产级的考试系统
java·springboot·考试
時肆48511 分钟前
MySQL数据可视化实战指南
数据库·mysql·信息可视化
yumgpkpm13 分钟前
基于GPU的Spark应用加速 Cloudera CDP/华为CMP鲲鹏版+Nvidia英伟达联合解决方案
大数据·数据库·人工智能·hadoop·elasticsearch·spark·cloudera
康小庄14 分钟前
通过NGINX实现将小程序HTTPS请求转为内部HTTP请求
java·spring boot·nginx·spring·http·小程序