2023.12.6 关于 Spring Boot 事务的基本概念

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

事务基本概念

前置准备

Spring Boot 事务使用 

编程式事务

声明式事务

@Transactional 注解参数说明

@Transational 对异常的处理

解决方案一

解决方案二

@Transactional 的工作原理

面试题 

Spring Boot 事务失效的场景有那些


事务基本概念

  • 事务指一组操作这些操作要么全部成功要么全部失败
  • 如果在一组操作中有一个操作失败了那么整个事务便会回滚即撤销已经执行的操作从而保证数据的一致性和完整性

实例理解

  • 典型实际场景为 银行转账操作
两个步骤
  1. 从源账户扣除指定金额
  2. 将该金额添加到目标账户
分析原因
  • 这两个步骤必须保证同时执行成功如果其中任意一个步骤失败便必须撤销整个操作以保持数据的一致性
  • 即 在扣款成功后如果存款时发生错误如网络问题那么我们必须要回滚扣款操作以确保不会错误地从源账户中扣款

前置准备

  •  下述实例均基于 实现根据用户 id 删除用户信息功能

  • 创建一个 user 表并添加几条用户信息

  • 创建 User 实体类 与 数据库的 user 表字段名相对应
import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private int age;
    private String password;
    private int state;
}
  • 初始化 UserMapper 接口此处我们添加一个 del 方法
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;


//添加 @Mapper 注解 代表该接口会伴随这 项目的启动而注入到容器中
@Mapper
public interface UserMapper {

//    根据用户id 删除用户信息
    int del(@Param("user_id") int id);
}
  • 在与 接口相对应的 XML 文件中添加上与 del 方法 相对应的 sql 语句
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

    <delete id="del">
        delete from user where id = #{user_id}
    </delete>
</mapper>

Spring Boot 事务使用 

编程式事务

  • Spring Boot 中内置了两个对象
  • DataSourceTransactionManager 用来获取事务开启事务、提交或回滚事务
  • TransactionDefinition 为事务的属性在获取事务的时候需要将其 传递进去从而获得一个事务 TransactionStatus
实例理解
  • 我们在 UserController 中 使用编程式事务给 根据用户id 删除用户信息  这一功能加上事务
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

//    编程式 事务
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public String del(Integer id) {
        if(id < 0 || id == null)  return "请输入正确的用户 id";
        TransactionStatus transactionStatus = null;
        int result = 0;
        try {
//            1. 开启事务
            transactionStatus = transactionManager.getTransaction(transactionDefinition);
//            2. 业务操作 删除用户
            result = userService.del(id);
            System.out.println("del 方法" + (result == 1 ? "删除成功": "删除失败" ));
//            3. 提交事务
            transactionManager.commit(transactionStatus); // 提交事务
        }catch (Exception e) {
            if(transactionStatus != null){
//              发生异常 回滚事务
                transactionManager.rollback(transactionStatus); // 回滚事务
            }
        }
            return "del 方法" + (result == 1 ? "删除成功": "删除失败" );
    }
}

测试结果

  • 在浏览器的 URL 地址框中输入相对应地址来调用上述代码的方法


声明式事务

  • Spring Boot 提供了 @Transactional 注解实现事务
  • 只需在需要的方法上添加 @Transaction 注解即可
  • 无需手动开启事务和提交事务进入方法时自动开启事务方法执行完会自动提交事务
  • 如果中途发生了没有处理的异常会自动回滚事务

注意

  • @Transactional 注解可以用来修饰方法或类
  • 修饰方式时该方法必须为 public 否则不生效
  • 修饰类时表明该注解对该类中所以的 public 方法都生效

实例理解

  • 我们在 UserController2 中 使用声明式事务给 根据用户id 删除用户信息  这一功能加上事务
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;
    
    @RequestMapping("/del")
    @Transactional
    public String del(Integer id) {
        if(id < 0 || id == null) return "请输入正确的用户 id";
        int result = userService.del(id);
        return "del 方法" + (result == 1 ? "删除成功": "删除失败" );
    } 
}

测试结果

  • 在浏览器的 URL 地址框中输入相对应地址来调用上述代码的方法


@Transactional 注解参数说明

参数作用
value当配置了多个事务管理器时可以使用该属性指定选择哪个事务管理器
transactionManager当配置了多个事务管理器时可以使用该属性指定选择哪个事务管理器
propagation事务的传播行为默认值为 Propagation.REQUIRED
isolation事务的隔离级别默认值为 Isolation.DEFAULT
timeout事务的超时时间默认值为 -1 如果超过该事件限制但事务还没有完成则自动回滚事务
readOnly指定事务是否为只读事务 默认值为 false 为了忽略那些不需要事务的方法 比如读取事务
rollbackFor用于指定能够触发事务回滚的异常类型 可以指定多个异常类型
rollbackForClassName用于指定能够触发事务回滚的异常类型 可以指定多个异常类型
noRollbackFor抛出指定的异常类型不会滚事务也可以指定多个异常类型
noRollbackForClassName抛出指定的异常类型不会滚事务也可以指定多个异常类型

注意

  • 区别 只读事务 和 无事务
  • 只读事务 可以设置隔离级别默认为 repeatable read 可设置 isolation 更改隔离级别

  • 无事务 仅为默认的隔离级别 repeatable read

@Transational 对异常的处理

实例理解
  • 此处我们故意在 UserController 中加入异常代码并手动捕获该 算数异常
  • 那么此处 @Transational 是否会回滚 del 操作呢
package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;

    @RequestMapping("/del")
    @Transactional // 在方法执行前开启事务 方法正常执行完后提交事务 执行途中发生异常回滚事务
    public String del(Integer id) {
        if(id < 0 || id == null) return "请输入正确的用户 id";
        int result = userService.del(id);
        try {
            int num = 10/0;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "del 方法" + (result == 1 ? "删除成功": "删除失败" );
    }
}

执行结果

  • 在浏览器的 URL 地址框中输入相对应地址来调用上述代码的方法

  • 由上图可知此处我们的 @Transational 并未回滚 del 操作
  • 即 @Transactional 在异常被捕获的情况下不会进行事务的自动回滚
解决方案一
  • 捕获到异常后再重新抛出异常让框架感知到异常如果框架感知到异常便会自动回滚事务
@RequestMapping("/del")
@Transactional // 在方法执行前开启事务 方法正常执行完后提交事务 执行途中发生异常回滚事务public String del(Integer id) {
    if(id < 0 || id == null) return "请输入正确的用户 id";
    int result = userService.del(id);
    try {
        int num = 10/0;
    } catch (Exception e) {
        e.printStackTrace();
//        抛出异常
        throw e;
    }
    return "del 方法" + (result == 1 ? "删除成功": "删除失败" );
}

执行结果

  • 在浏览器的 URL 地址框中输入相对应地址来调用上述代码的方法

  • 数据库中 id = 1 的 xiaolin 未被删除说明此时 @Transational 进行了回滚操作
解决方案二
  • 捕获到异常后手动回滚事务此处框架是感知不到异常的
@RequestMapping("/del")
@Transactional // 在方法执行前开启事务 方法正常执行完后提交事务 执行途中发生异常回滚事务public String del(Integer id) {
    if(id < 0 || id == null) return "请输入正确的用户 id";
    int result = userService.del(id);
    try {
        int num = 10/0;
    } catch (Exception e) {
        e.printStackTrace();
//        手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "del 方法" + (result == 1 ? "删除成功": "删除失败" );
}
  • TransactionAspectSupport 为 Spring 框架中的一个类提供了事务相关的支持和功能
  • currentTransactionStatus 为 TransactionAspectSupport 类的一个静态方法用于获取当前事务的状态对象
  • setRollbackOnly 为 TransactionStatus 接口的一个方法用于将当前事务标记为回滚状态0

执行结果

  • 在浏览器的 URL 地址框中输入相对应地址来调用上述代码的方法

  • 数据库中 id = 1 的 xiaolin 未被删除说明此时 @Transational 进行了回滚操作

重点理解

  • 此处为什么会返回一个 删除成功
  • 代码从上到下顺序执行先执行了 del 操作
  • 即此处的 result 值已经成功被赋值为 1 返回值为 del 操作影响的行数
  • 然后我们才对 算数异常进行捕获捕获之后再进行的 回滚操作
  • 且异常捕获之后我们并未抛出异常从而不会出现方案一的服务器错误
  • 在捕获完异常后代码将继续向下执行此时便返回 "del 方法" + (result == 1 ? "删除成功": "删除失败" )
  • 因为此处的 result 等于 1所以返回了一个 删除成功
  • 但是我们要明白的是 我们在捕获异常后在处理异常时进行了事务的回滚
  • 所以此处数据库中的  id = 1 的 xiaolin 未被删除

@Transactional 的工作原理

  • 此处声明式事务的实现方式 可使用 Spring AOP 来实现
  • 执行目标方法之前 先开启事务类似于前置通知
  • 执行完目标方法之后 再提交事务类似于后置通知
  • 如果在执行中途发生了没有处理的异常 便回滚事务
  • 综上 我们可以直接将目标方法 写入环绕通知中 
/*
* 环绕通知
* 此处的 joinPoint 就是连接点即方法本身
* */
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    Object obj = null;
    System.out.println("执行目标方法之前 这里开启事务");
    try {
//        此处执行目标方法
        obj = joinPoint.proceed();
    }catch (Exception e) {
        System.out.println("执行目标方法出现异常 这里回滚事务");
    }
    System.out.println("执行目标方法之后 这里提交事务");
//    最后将执行的结果交给框架
    return obj;
}
  • 上述代码仅为 实现声明式事务 的大致思路

面试题 

Spring Boot 事务失效的场景有那些

  1. @Transactional 修饰的方法为非 public 导致事务失效
  2. @Transactional 设置了一个较小的超时时间如果方法本身的执行时间超过了设置的 timeout 超时时间那么就会导致本来应该正常插入数据的方法执行失败
  3. 代码中有 try/catch 语句仅捕获异常不进行额外处理则导致 @Transactional  不会自动回滚事务
  4. 数据库不支持事务我们程序中的 @Transactional 只是给调用的数据库发生了开始事务、提交事务 或 回滚事务 的之类但是如果数据库本身不支持事务如 MySQL 中设置了使用 MySAM 引擎那么它本身是不支持事务的在这种情况下即使在程序中添加了 @Transactional 注解那么依然不会有事务行为
  5. 当调用类内部的 @Transactional 修饰的方法时事务是不会生效的
@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {
    return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    int num = 10 / 0; // 此处设置一个异常
    return result;
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6