Spring—事务及事务的传播机制

news2024/11/29 10:47:27

Spring—事务及事务的传播机制

  • 🔎事务的定义
  • 🔎Spring—事务的实现
    • 铺垫
    • Spring 编程式事务
    • Spring 声明式事务
      • @Transactional 的参数
      • 注意事项
      • @Transactional 的工作原理
  • 🔎Spring—事务的隔离级别
    • MySQL—事务的隔离级别
    • Spring—事务的隔离级别
    • Spring—设置事务的隔离级别
  • 🔎Spring—事务的传播机制
    • Spring—事务传播机制的分类
    • 对比加入事务与嵌套事务
      • 总结加入事务与嵌套事务之间的区别

🔎事务的定义


将一组操作封装成一个执行单元, 即这一组操作一同成功 / 一同失败

举个栗子🌰

未使用事务

滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520, 而滑稽老哥却被扣款 520

使用事务

滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520
因为使用事务, 所以滑稽老哥的钱也被重新打回账户上
(一同成功 / 一同失败)

🔎Spring—事务的实现


Spring—事务的实现有 2 种方式

  1. 通过代码的方式手动实现事务(即 Spring 编程式事务)
  2. 通过注解的方式实现事务(即 Spring 声明式事务)

铺垫


后续内容针对数据表 userinfo 进行演示🍂

-- 创建用户表
drop table if exists userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';

-- 添加一个用户信息
INSERT INTO `excnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) 
VALUES (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

在这里插入图片描述


Controller 层(UserController) → Service 层(UserService) → Mapper 层(UserMapper)🍂

在这里插入图片描述

UserController

代码在 Spring 编程式事务

UserService

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public Integer add(UserInfo userInfo) {
        return userMapper.add(userInfo);
    }

}

UserMapper

@Mapper
public interface UserMapper {

    int add(UserInfo userInfo);

}

UserMapper 对应的 SQL 语句

<insert id="add">
    insert into userinfo(username, password) values(#{username}, #{password})
</insert>

UserInfo

@Data
public class UserInfo {

    private Integer id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private Integer state;

}

Spring 编程式事务


Spring 编程式事务与 MySQL 操作事务类似, 分为 3 个部分

  1. 开启事务
  2. 提交事务
  3. 回滚事务
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public Integer add(UserInfo userInfo) {
        // 非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        // 1. 开启事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        // 执行相关业务代码
        int ret = userService.add(userInfo);
        System.out.println("add : " + ret);
        // 2. 提交事务
        transactionManager.commit(transactionStatus);
        // 3. 回滚事务
        transactionManager.rollback(transactionStatus);
        return ret;
    }

}

验证效果(开启事务 + 提交事务)🍂

在这里插入图片描述

验证效果(开启事务 + 回滚事务)🍂

在这里插入图片描述

Spring 声明式事务


利用注解 @Transactional 实现 Spring 声明式事务

@Transactional 的特点

  • 可以添加在类 / 方法上(默认情况下 @Transactional 针对的是 public 修饰的方法)
  • 方法执行前自动开启事务, 方法执行期间出现异常 → 自动回滚事务, 方法执行完(无异常) → 自动提交事务

举个栗子🌰

方法执行期间出现异常 → 自动回滚事务

在这里插入图片描述

在这里插入图片描述

方法执行完(无异常) → 自动提交事务

在这里插入图片描述

在这里插入图片描述

@Transactional 的参数


在这里插入图片描述

参数作用
value配置多个事务管理器时, 可指定需要的事务管理器
transactionManager配置多个事务管理器时, 可指定需要的事务管理器
propagation事务的传播行为, 默认值为 Propagation.REQUIRED
isolation事务的隔离级别, 默认值为 Isolation.DEFAULT
timeout事务的超时时间, 默认值为 -1, 表示超时时间为无限大, 即事务将一直持续直到完成或手动回滚(如果超出设置的时间限制事务仍未完成, 则自动回滚事务)
readOnly指定事务是否为只读, 默认值为 false(为了忽略不需要事务的方法, 例如读取数据, 设置 read-only 为 true)
rollbackFor用于指定能够触发事务回滚的异常类型(可指定多个异常类型)
rollbackForClassName用于指定能够触发事务回滚的异常类型(可指定多个异常类型)
noRollbackFor抛出指定的异常类型, 不回滚事务(可指定多个异常类型)
noRollbackForClassName抛出指定的异常类型, 不回滚事务(可指定多个异常类型)

注意事项


方法内部存在异常, 但被捕获(try-catch)时, 仍会提交事务

即方法执行完(无异常) → 自动提交事务

代码示例如下

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

    @RequestMapping("/insert")
    @Transactional
    public Integer insert(UserInfo userInfo) {
        // 非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int ret = userService.add(userInfo);
        System.out.println("insert : " + ret);

        try {
            int num = 2 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        return ret;
    }

}

针对上述情况, 想要回滚事务, 有 2 种解决方式

  1. 继续抛出异常
  2. 手动回滚事务(推荐)

继续抛出异常🍂

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

    @RequestMapping("/insert")
    @Transactional
    public Integer insert(UserInfo userInfo) {
        // 非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int ret = userService.add(userInfo);
        System.out.println("insert : " + ret);

        try {
            int num = 2 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());

            // 抛出异常
            throw e;
        }

        return ret;
    }

}

验证效果(抛出异常)

在这里插入图片描述

手动回滚事务🍂

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

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

    @RequestMapping("/insert")
    @Transactional
    public Integer insert(UserInfo userInfo) {
        // 非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int ret = userService.add(userInfo);
        System.out.println("insert : " + ret);

        try {
            int num = 2 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());

            // 手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

        return ret;
    }

}

验证效果(手动回滚事务)

在这里插入图片描述

@Transactional 的工作原理


@Transactional 是基于 AOP 实现, AOP 使用动态代理实现

目标对象实现了接口, 默认采用 JDK 的动态代理 / 目标对象未实现接口, 默认采用 CGLIB 的动态代理

@Transactional 在开始执行业务之前, 通过代理开启事务, 执行成功之后提交事务(执行期间遇到异常回滚事务)


在这里插入图片描述

🔎Spring—事务的隔离级别


事务有 4 大特性(ACID)

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

原子性🍂

定义

⼀个事务(Transaction)中的所有操作, 要么全部完成, 要么全部不完成, 不会结束在中间某个环节. 事务在执⾏过程中发⽣错误, 会被回滚(Rollback)到事务开始前的状态, 就像这个事务从来没有执⾏过⼀样

翻译

用户 A 给用户 B 转账
要么 A 成功转账给 B, B 收到转账的钱
要么 A 没能转账给 B, B 未收到转账的钱
不能出现 A 成功转账给 B, B 未收到钱等类似的情况

一致性🍂

定义

在事务开始之前和事务结束以后, 数据库的完整性没有被破坏. 这表示写⼊的资料必须完全符合所有的预设规则, 这包含资料的精确度, 串联性以及后续数据库可以⾃发性地完成预定的工作

翻译

用户 A 给用户 B 转账 520
A 账户被扣款 520, B 账户增加 520
不能出现 A 账户扣款 1000, B 账户增加 520 等类似的情况

隔离性🍂

定义

数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒, 隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不⼀致. 事务隔离分为不同级别, 包括读未提交(Read Uncommitted), 读已提交(Read Committed), 可重复读(Repeatable Read)和串行化(Serializable)

翻译(摘抄自网络)

将点餐过程理解为一个事务
制作 → 出餐 → 送餐 → 结算作为一个事务

不同的客户进行点餐是相互独立的, 并不会彼此影响 → 事物的隔离性

当一个事务影响其他事务时, 其他事务将会回滚

今日份的猪脚饭已经全部卖完, 再次点餐将会影响后续事务(制作 → 出餐 → 送餐 → 结算), 此时后续事务将会回滚

持久性🍂

定义

事务处理结束后, 对数据的修改就是永久的, 即便系统故障也不会丢失

翻译

用户 A 执行转账操作, 银行系统会开启事务, 将转账金额从 A 账户扣除, 将对应金额添加至 B 账户
当事务提交成功后, 系统会将更改持久化到数据库
即使系统发生故障, 数据库仍然能够恢复并保持转账操作的结果

MySQL—事务的隔离级别


MySQL—事务的隔离级别🍂

  1. READ UNCOMMITTED → 读未提交
  2. READ COMMITTED → 读已提交
  3. REPEATABLE READ → 可重复读(MySQL 默认的事务隔离级别)
  4. SERIALIZABLE → 串行化
事务隔离级别脏读不可重复读幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED)
可重复读(REPEATABLE READ)
串行化(SERIALIZABLE)

Spring—事务的隔离级别


在这里插入图片描述


Spring—事务的隔离级别🍂

  1. Isolation.DEFAULT → 以链接数据库的事务隔离级别为主
  2. Isolation.READ_UNCOMMITTED → 读未提交
  3. Isolation.READ_COMMITTED → 读已提交
  4. Isolation.REPEATABLE_READ → 可重复读
  5. Isolation.SERIALIZABLE → 串行化

Spring—设置事务的隔离级别


@Transactional(isolation = Isolation.DEFAULT)
public void setIsolationLevel() {

}

🔎Spring—事务的传播机制


事务的传播机制 → 事务的隔离级别 Plus 版

事务的隔离级别🍂

解决多个事务同时调用数据库的问题

在这里插入图片描述

事务的传播机制

解决一个事务在多个方法中传递的问题

在这里插入图片描述

Spring—事务传播机制的分类


Spring—事务传播机制的分类🍂

  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

在这里插入图片描述


举个栗子🌰

在这里插入图片描述


对比加入事务与嵌套事务


在这里插入图片描述


UserController

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

    @Autowired
    private UserService userService;
    @Autowired
    private LoginService loginService;

    @RequestMapping("/insert")
    @Transactional
    public Integer insert(UserInfo userInfo) {
        // 非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
        || !StringUtils.hasLength(userInfo.getPassword())) {
            return 0;
        }

        int ret = userService.add(userInfo);
        if(ret > 0) {
            loginService.add();
        }

        return ret;
    }

}

加入事务🍂

@Transactional(propagation = Propagation.REQUIRED)为例

UserService

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer add(UserInfo userInfo) {
        int ret = userMapper.add(userInfo);
        System.out.println("添加 : " + ret);
        return ret;
    }

}

LoginService

@Service
public class LoginService {

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer add() {
        try {
            int num = 1 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return -1;
    }

}

验证效果(执行期间出现异常 → 回滚全部事务)

在这里插入图片描述

嵌套事务🍂

@Transactional(propagation = Propagation.为例

UserService

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NESTED)
    public Integer add(UserInfo userInfo) {
        int ret = userMapper.add(userInfo);
        System.out.println("添加 : " + ret);
        return ret;
    }

}

LoginService

@Service
public class LoginService {

    @Transactional(propagation = Propagation.NESTED)
    public Integer add() {
        try {
            int num = 1 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return -1;
    }

}

验证效果(执行期间出现异常 → 回滚部分事务)

在这里插入图片描述

总结加入事务与嵌套事务之间的区别


加⼊事务(REQUIRED) 和 嵌套事务(NESTED) 的区别

  • 整个事务如果全部执行成功,结果均相同
  • 如果事务执行期间失败了, 那么加入事务会将整个事务全部回滚;嵌套事务则会局部回滚,不会影响上⼀个方法中执行的结果

🌸🌸🌸完结撒花🌸🌸🌸

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/741542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指 Offer 04. 二维数组中的查找(java)

二维数组中的查找 剑指 Offer 04. 二维数组中的查找题目描述抽象 BST 解题 二叉树专题 剑指 Offer 04. 二维数组中的查找 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof 题目描述…

js为啥是设计成单线程而不是多线程呢

了解这个问题之前&#xff0c;需要先了解一下以下问题&#xff1a; 什么是进程&#xff1f;什么是线程&#xff1f;二者有啥关联&#xff1f;任务队列是什么&#xff1f;宏任务&#xff1f;微任务&#xff1f;eventloop&#xff1f;为什么说js是单线程&#xff0c;为什么要设计…

UWB高精度定位标签方案,厘米级室内测距,实时人员物品位置确定

随着科技的不断进步&#xff0c;UWB技术正逐渐成为各个领域的定位解决方案的首选。其高精度、安全、实时的特性使其在安全免提访问控制、实时室内定位等应用领域发挥着重要的作用。 安全免提访问控制是目前应用UWB技术的重要领域之一。通过将UWB标签&#xff08;如手机、钥匙扣…

Django如何删除数据库表中的数据【不断积累】

这篇博客积累Django的数据库常用删除方法。 假设有表模型Author定义如下&#xff1a; class Author(models.Model):name models.CharField(max_length100)def __str__(self):return self.name01-根据记录的id号来删除指定的记录 Django 默认为每个模型添加一个名为 id 的自…

RPA赋能日化产业,实在智能广东日化共推行业数字化转型

广东日化&#xff0c;因其独特的地域、产能优势&#xff0c;成为广东制造业的支柱产业之一&#xff0c;占据了全国日化行业的半壁江山。作为本土最具影响力商会组织之一&#xff0c;广东省日化商会凝聚了一批具有影响力的日化企业&#xff0c;其经济总量、市场占有率、品牌知名…

python网站创建:初识网站(001)

1. 初识网站&#xff1a;首先来认识一下&#xff0c;前端、后端、数据库它是怎么分工合作来形成网站的 使用python创建网站之前&#xff0c;需要先稍微认识一下两个最流行python web框架&#xff1a;(Flask)和(Django) Flask是一个轻量级的框架&#xff0c;适用于比较轻巧&…

【*1900倍数遍历】CF1627 D

Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a; 在枚举数列子集的gcd时&#xff0c;通常可以枚举倍数 对于这道题要注意&#xff0c;j/i的gcd要为1&#xff0c;这样才能保证i是这个子集的最大公约数 Code&#xff1a; #include <bits/stdc.h>//#define…

三菱FX3U简单工程编程

1.简单工程编程 1.1.元件 常开触点&#xff08;ld&#xff09; 选中位置&#xff0c;点击图标&#xff0c;输入软元件&#xff0c;完成添加。 选中位置&#xff0c;快捷键F5添加。 选中位置&#xff0c;输入ld 软元件添加。常闭触点&#xff08;ldi&#xff09;横线、竖线 …

红黑树增删操作详解(相信我,这次你一定会弄懂)

前言&#xff1a; 网上众多关于红黑树的讲解&#xff0c;但大多数都是重复的&#xff0c;只列出了几种简单情况&#xff0c;逻辑和思维深度都不足以解答吾之困惑。。。 直到看到张彦峰先生的 对红黑树的认识总结&#xff0c;基本可以说是集大成者&#xff0c;本文会基于…

MySQL原理探索——28 读写分离有哪些坑

在上一篇文章中&#xff0c;介绍了一主多从的结构以及切换流程。今天我们就继续聊聊一主多从架构的应用场景&#xff1a;读写分离&#xff0c;以及怎么处理主备延迟导致的读写分离问题。 我们在上一篇文章中提到的一主多从的结构&#xff0c;其实就是读写分离的基本结构了。这里…

linux环境下使用jmeter进行分布式测试

目录 1、前言 2、环境准备 3、分布式配置 总结&#xff1a; 1、前言 熟练使用jmeter进行性能测试的工程师都知道&#xff0c;jmeter的客户端性能是有点差的。这会导致一个问题&#xff0c;其客户端的性能损耗会干扰到性能测试的结果&#xff0c;而且当线程数/并发大到一定程…

我爱学QT-仿写智能家居界面 上 中 下

学习链接&#xff1a; 仿写一个智能家居界面&#xff08;上&#xff09;_哔哩哔哩_bilibili 上 给QT工程添加资源文件 在这里 然后选这个&#xff0c;choose后会有起名&#xff0c;之一千万不能是中文&#xff0c;要不就等报错吧 然后把你要添加的图片托到文件夹下&#xf…

FPGA好找工作吗?薪资待遇怎么样?

FPGA&#xff1a;即现场可编程门阵列&#xff0c;它是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的&#xff0c;既解决了定制电路的不足&#xff0c;又克服了原有可编程器件门电路数有限的缺点。 FPGA太…

Codeforces round 883 div3

A. Rudolph and Cut the RopeA. Rudolph and Cut the Rope 题目大意 有 n 个钉子钉在墙上&#xff0c;第 i 个钉子被钉在离地面 ai 米高的位置&#xff0c;一根长度为 bi 米的绳子的一端被绑在它上面。所有钉子都悬挂在不同的高度上。糖果同时被绑在所有绳子的末端&#xff0…

浅谈关于智慧校园安全用电监测系统的设计

0引言 人生人身安全是大家关注的话题&#xff0c;2019年12月中国消防统计近五年发生在全国学生宿舍的火灾2314起&#xff08;中国消防2019.12.应急管理部消防救援局官方微博&#xff09;&#xff0c;违规电器是引发火灾的主因。如果在各寝室安装智能用电监测器实时监督线路参数…

【力扣算法05】之 _1911_ 最大子序列交替和- python

文章目录 问题描述示例 1示例2示例3提示 思路分析代码分析完整代码运行示例代码示例1示例2示例3 完结 问题描述 一个下标从 0 开始的数组的 交替和 定义为 偶数 下标处元素之 和 减去 奇数 下标处元素之 和 。 比方说&#xff0c;数组 [4,2,5,3] 的交替和为 (4 5) - (2 3) 4…

804. n的阶乘

链接&#xff1a; https://www.acwing.com/problem/content/806/ 题目&#xff1a; 输入一个整数 nn&#xff0c;请你编写一个函数&#xff0c;int fact(int n)&#xff0c;计算并输出 nn 的阶乘。 输入格式 共一行&#xff0c;包含一个整数 nn。 输出格式 共一行&#xff0c;包…

OpenCV(图像处理)-图片搜索

图片搜索 1.知识介绍2.实现流程2.1 计算特征点与描述子2.2 描述子的匹配2.3 求出单应性矩阵并画出轮廓2.4 将特征点标出 此篇博客作者仍在探索阶段&#xff0c;还有一些模糊的概念没有弄懂&#xff0c;请读者自行分辨。 1.知识介绍 Opencv进行图片搜索需要的知识有&#xff1…

多个input框或其他框的值相加之和并且处理精度问题

需求&#xff1a;实付金额不能手动输入&#xff0c;并且等于购买数量✖优惠价➖平台补贴➖店铺补贴 把需要处理的这几个框绑上change事件等于同一个方法名 change"handleChange" handleChange(value){let _this this;let isNull validatenull;_this.ruleForm.pre…

【JavaEE】项目的部署-让网络上的人都能访问你的网站

项目的部署-让网络上的人都能访问你的网站 文章目录 【JavaEE】项目的部署-让网络上的人都能访问你的网站1. 搭建环境1.1 jdk1.2 Tomcat1.2.1 上传tomcat程序1.2.2 给启动脚本加上可执行权限1.2.3 启动Tomcat1.2.4 让服务器运行8080端口的流量通过 1.3 MySQL 2. 代码修改2.1 修…