自学内容网 自学内容网

SpringBoot 事务

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


为什么需要事务?

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

比如转账操作:

第一步:A 账户 -100 元.

第二步:B 账户 +100 元.

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

比如秒杀系统,

第一步: 下单成功

第二步: 扣减库存

下单成功后, 库存也需要同步减少. 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况. 所以就需要把这两步操作放在同一个事务中. 要么一起成功, 要么一起失败.

理解事务概念为主, 实际企业开发时, 并不是简单的通过事务来处理.

事务的操作

事务的操作主要有三步:

  1. 开启事start transaction/ begin (一组操作前开启事务)
  2. 提交事务: commit (这组操作全部成功, 提交事务)
  3. 回滚事务: rollback (这组操作中间任何一个操作出现异常, 回滚事务)
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;

Spring中事务的实现

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

  1. 编程式事务(手动写代码操作事务).
  2. 声明式事务(利用注解自动开启和提交事务).
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
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 CHARACTER
SET = 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()
) DEFAULT charset 'utf8mb4';

配置文件

# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/trans_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123
    driver-class-name: com.mysql.cj.jdbc.Driver
  # 设置动态代理的方式 true jdk代理, false cglib代理
  aop:
    proxy-target-class: true
mybatis:
  configuration:
    map-underscore-to-camel-case: true #配置驼峰自动转换
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
  mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:
  file:
    name: logger/spring-book.log

实体类

@Data
public class LogInfo {
    private Integer id;
    private String userName;
    private String op;
    private Date createTime;
    private Date updateTime;
}

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Date createTime;
    private Date updateTime;
}

mapper

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
    @Insert("insert into user_info(`user_name`,`password`)values(#{name},#
    {password})")
    Integer insert(String name,String password);
}

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
    @Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
    Integer insertLog(String name,String op);
}

service

@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public void registryUser(String name,String password){
//插入用户信息
        userInfoMapper.insert(name,password);
    }
}

@Slf4j
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    public void insertLog(String name,String op){
        //记录用户操作
        logInfoMapper.insertLog(name,"用户注册");
    }
}

controller

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

Spring编程式事务

Spring 手动操作事务和上面 MySQL 操作事务类似, 有 3 个重要操作步骤:

• 开启事务(获取事务)

• 提交事务

• 回滚事务

SpringBoot 内置了两个对象:

  1. DataSourceTransactionManager 事务管理器. 用来获取事务(开启事务), 提交或回滚事务的
  2. TransactionDefinition 是事务的属性, 在获取事务的时候需要将TransactionDefinition 传递进去从而获得一个事务 TransactionStatus

事务交提

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;
    @RequestMapping("/registry")
    public String registry(String name,String password){

        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //用户注册
        userService.registryUser(name,password);
        // 提交事务
        dataSourceTransactionManager.commit(transaction);

        //回滚事务
        //dataSourceTransactionManager.rollback(transactionStatus);
        return "注册成功";
    }
}

http://localhost:8080/user/registry?name=user1&password=123123


事务回滚

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;
    @RequestMapping("/registry")
    public String registry(String name,String password){

        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //用户注册
        userService.registryUser(name,password);
        //回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        return "注册成功";
    }
}

http://localhost:8080/user/registry?name=user2&password=123123

虽然返回结果时注册成功,但是数据库中并没有插入数据。

Spring声明式事务@Transactional

声明式事务的实现很简单, 只需要在需要事务的方法上添加 @Transactional 注解就可以实现了.无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务.

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name,String password){
        userService.registryUser(name,password);
        return "注册成功";
    }
}

http://localhost:8080/user/registry?name=user2&password=123123

数据库中插入了数据。


使程序出现异常

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name,String password){
        userService.registryUser(name,password);
        int a = 10 / 0;
        return "注册成功";
    }
}

事务会进行回滚,所以数据库中没有新插入的数据。

我们一般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, 一个业务功能可能会包含多个数据访问的操作. 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在一个事务范围内.

Transactional作用

@Transactional 可以用来修饰方法或类:

• 修饰方法时: 只有修饰public 方法时才生效(修饰其他方法时不会报错, 也不生效)[推荐]

• 修饰类时: 对@Transactional 修饰的类中所有的 public 方法都生效.

方法/类被 @Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结之后, 自动提交事务.如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作.如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务.

@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
    //用户注册
    userService.registryUser(name,password);
    log.info("用户数据插入成功");
    //对异常进行捕获
    try {
        //强制程序抛出异常
        int a = 10/0;
    }catch (Exception e){
        e.printStackTrace();
    }
    return "注册成功";
}

运行程序, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交.

如果需要事务进行回滚, 有以下两种方式:

  1. 重新抛出异常
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
    //用户注册
    userService.registryUser(name,password);
    log.info("用户数据插入成功");
    //对异常进行捕获
    try {
        //强制程序抛出异常
        int a = 10/0;
    }catch (Exception e){
        //将异常重新抛出去
        throw e;
    }
    return "注册成功";
}
  1. 手动回滚事务
// 使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并
// 使用 setRollbackOnly 设置setRollbackOnly
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
    //用户注册
    userService.registryUser(name,password);
    log.info("用户数据插入成功");
    //对异常进行捕获
    try {
        //强制程序抛出异常
        int a = 10/0;
    }catch (Exception e){
        // 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "注册成功";
}

Transactional详解

通过上面的代码, 我们学习了@Transactional 的基本使用. 接下来我们学习@Transactional注解的使用细节.
我们主要学习@Transactional 注解当中的三个常见属性:

  1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
  2. Isolation: 事务的隔离级别. 默认值为Isolation.DEFAULT
  3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

rollbackFor

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

修改一下代码

@Transactional
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
    //用户注册
    userService.registryUser(name,password);
    log.info("用户数据插入成功");
    if (true){
        throw new IOException();
    }
    return "r2";
}

发现虽然程序抛出了异常, 但是事务依然进行了提交.

如果我们需要所有异常都回滚, 需要来配置@Transactional 注解当中的rollbackFor 属性, 通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚.

@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
    //用户注册
    userService.registryUser(name,password);
    log.info("用户数据插入成功");
    if (true){
        throw new IOException();
    }
    return "r2";
}

数据回滚了。

在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚.

如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定.

事务隔离级别

MySQL事务隔离级别

  1. 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.(因为其他事务未提交的数据可能会发生回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数
    据称之为脏数据, 这个问题称之为脏读.)
  2. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,(该隔离级别不会有脏读的问题.但由于在事务的执行中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读)
  3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同一事务多次查询的结果一致, 但是其他事务新插入的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.(比如此级别的事务正在执行时, 另一个事务成功的插入了某条数据, 但因为它每次查询的结果都是一样的, 所以会导致查询不到这条数据, 自己重复插入时又失败(因为唯一约束的原因). 明明在事务中查询不到这条信息,但自己就是插入不进去, 这个现象叫幻读.)
  4. 串行化(SERIALIZABLE): 序列化, 事务最高隔离级别. 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读问题, 但因为执行效率低, 所以真正使用的场景并不多.

Spring事务隔离级别

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

  1. Isolation.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
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
    private final int value;
    private Isolation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置

@Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
    //... 代码省略
    return "r3";
}

Spring事务传播机制

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

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

这个就涉及到了事务的传播机制.

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

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

事务传播机制有哪些

@Transactional 注解支持事务传播机制的设置, 通过 propagation 属性来指定传播行为.
Spring 事务传播机制有以下 7 种:

  1. Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务.
  2. Propagation.SUPPORTS : 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行.
  3. Propagation.MANDATORY :强制性. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常.
  4. Propagation.REQUIRES_NEW : 创建一个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务, 且开启的事务相互独立, 互不干扰.
  5. Propagation.NOT_SUPPORTED : 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用).
  6. Propagation.NEVER : 以非事务方式运行, 如果当前存在事务, 则抛出异常.
  7. Propagation.NESTED : 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED .
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
    private final int value;
    private Propagation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

  1. Spring中使用事务, 有两种方式: 编程式事务(手动操作)和声明式事务. 其中声明式事务使用较多,在方法上添加 @Transactional 就可以实现了
  2. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别. Spring 中的事务隔离级别有 5 种
  3. 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的事务传播级别有 7 种, 重点关注 REQUIRED (默认值) 和 REQUIRES_NEW

原文地址:https://blog.csdn.net/weixin_73888239/article/details/144727995

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!