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。

相关推荐
大学生资源网2 小时前
基于Javaweb技术的宠物用品商城的设计与实现(源码+文档)
java·mysql·毕业设计·源码·springboot
小无名呀2 小时前
视图(View)
数据库·mysql
汤姆yu2 小时前
基于springboot的热门文创内容推荐分享系统
java·spring boot·后端
星光一影2 小时前
教育培训机构消课管理系统智慧校园艺术舞蹈美术艺术培训班扣课时教务管理系统
java·spring boot·mysql·vue·mybatis·uniapp
lkbhua莱克瓦242 小时前
MySQL介绍
java·开发语言·数据库·笔记·mysql
武昌库里写JAVA2 小时前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql
董世昌412 小时前
什么是事件冒泡?如何阻止事件冒泡和浏览器默认事件?
java·前端
好度2 小时前
配置java标准环境?(详细教程)
java·开发语言
teacher伟大光荣且正确2 小时前
关于Qt QReadWriteLock(读写锁) 以及 QSettings 使用的问题
java·数据库·qt