JavaEE高阶---Spring事务和事务传播机制

news2025/1/8 18:11:09

一:什么是事务?

事务定义:将⼀组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败。

在这里插入图片描述

二:Spring中事务的实现

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)。

实际项目开发中,我们更倾向于使用“声明式事务”,因为它既方便又高效。

三:编程式事务

3.1 步骤

  1. 开启事务;
  2. 提交事务;
  3. 回滚事务。

3.2 示例

在这里插入图片描述

在这里插入图片描述

application.yml

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1/myblog?characterEncoding=utf8
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

UserMapper.xml

<?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">

    <insert id="add">
        insert into userinfo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="username != null">
                username,
            </if>
            <if test="photo != null">
                photo,
            </if>
            <if test="password != null">
                password,
            </if>
        </trim>
        <trim prefix="values(" suffix=")" suffixOverrides=",">
            <if test="username != null">
                #{username},
            </if>
            <if test="photo != null">
                #{photo},
            </if>
            <if test="password != null">
                #{password},
            </if>
        </trim>
    </insert>

</mapper>


UserController [重点]

package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

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

    @RequestMapping("/add")
    public int add(String username,String password) {
        //非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        //事务[得到并开启事务]
        TransactionStatus transactionStatus =
                dataSourceTransactionManager.getTransaction(transactionDefinition);
        int result = userService.add(username,password,null);
        System.out.println("受影响的行数:" + result);
        //提交事务 or 回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        //dataSourceTransactionManager.commit(transactionStatus);
        return result;
    }

}

UserService

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public int add(String username,String password,String photo) {
        return userMapper.add(username,password,photo);
    }
//    public List<UserInfo> getAll() {
//
//    }
}


启动项目,先使用提交事务:

在这里插入图片描述

查看数据库中的数据:

在这里插入图片描述

在这里插入图片描述

查看结果:

在这里插入图片描述

说明事务提交成功。

使用回滚事务:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

再次查看数据库中的数据,并没有添加成功,说明事务实现了回滚。

解释:

在这里插入图片描述

四:声明式事务

4.1 示例

在这里插入图片描述
在这里插入图片描述

同时别忘记在UserMapper.xml中进行配置:

在这里插入图片描述

相比上面的编程式事务,声明式事务的写法就会优雅许多,我们来添加一条数据:

在这里插入图片描述

到底成功否?查看数据库揭晓答案:

在这里插入图片描述

现在我们手动添加一条异常并再次添加数据,如果事务实现了回滚,那么数据库中应该不会出现这条数据:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这很容易想到,都直接500错误了,必不可能添加成功了。

那如果我们将这个异常try/catch起来呢?

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

此时返回受影响的行数为1,且数据添加成功。

说明官方在设计出现异常回滚事务时,要看出现的异常有没有被捕获。如果异常被捕获,就认为你已经处理了这个异常,所以会被像正常情况一样添加成功。

那如果在这种情况下,我希望继续回滚事务呢?我介绍两种操作方法:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2 @Transactional的工作原理

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。

@Transactional 实现思路:

在这里插入图片描述

@Transactional 具体执行细节:

在这里插入图片描述

三:事务隔离级别

3.1 事务特性

事务有4 ⼤特性(ACID),原⼦性、持久性、⼀致性和隔离性,具体概念如下:

  1. Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  2. Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  3. Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  4. Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

在这里插入图片描述

设置事务的隔离级别,是为了保障多个并发事务执行更可控,更符合操作者预期,是为了防止其余事务影响当前事务的一种策略。

在这里插入图片描述

3.2 MySQL中的隔离级别

Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(比如Oracle,但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理期间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行,就好像出现幻觉一样。

Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

在这里插入图片描述

  • 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的。

  • 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。

  • 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务又新增了⼀部分数据。

总结:

  1. 脏读是可能读取到未被提交的数据的,而不可重复读和幻读读取到的一定是已提交的数据。
  2. 不可重复读侧重于两次查询中间数据被修改,而幻读侧重于两次查询中间数据的添加或删除。

3.3 Spring中的隔离级别

⽽ Spring 中事务隔离级别包含以下 5 种:

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
  3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
  5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。

从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。

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

在这里插入图片描述

四:Spring事务传播机制

4.1 什么是事务传播机制?

Spring事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。

4.2 为什么需要事务传播机制?

事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。

在这里插入图片描述

在这里插入图片描述

4.3 事务传播机制有哪些?

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

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以非事务的⽅式继续运⾏。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独⽴,互不干扰。
  5. Propagation.NOT_SUPPORTED:以非事务方式运⾏,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以非事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:

在这里插入图片描述

以情侣关系为例来理解以上分类:

在这里插入图片描述

4.4 实例

下面就演示一下默认的事务传播级别和嵌套事务传播级别,也是面试中最常考到的两个事务传播级别:

4.4.1 默认事务传播级别

UserService

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;



@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    //添加用户信息
    @Transactional(propagation = Propagation.REQUIRED)
    public int add(String username,String password,String photo) {
        return userMapper.add(username,password,photo);
    }

    //添加用户信息
    @Transactional(propagation = Propagation.REQUIRED)
    public int save(String username,String password,String photo) {
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            System.out.println("异常:" + e.getMessage());
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return userMapper.add(username,password,photo);
    }


}


UserController

package com.example.demo.controller;

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

    @Autowired
    private UserService userService;
   
    @RequestMapping("/add2")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add2(String username, String password) {
        // 非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return 0;
        }
        int result = userService.add(username, password, null);
        System.out.println("添加影响行数:" + result);

        int result2 = userService.save(username, password, null);
        System.out.println("添加影响行数:" + result2);

        return result;
    }
}

这里的add和save方法都是添加用户,可能业务比较奇葩,但是能说明问题就行。此时两个事务的传播级别都是默认事务传播级别,我在save方法中整个幺蛾子,根据“一荣俱荣,一损俱损”的原则,那么两条数据应该都不会添加成功。

在这里插入图片描述

查看数据库中数据:

在这里插入图片描述

没有发生变化。

4.4.2 嵌套事务传播级别

修改save方法为嵌套事务传播级别。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

查看数据库中数据,发现有一条数据添加成功了,这是因为第二次事务虽然失败,但由于其是默认事务传播级别,所以不影响第一次事务的执行。

总结:

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

整个事务如果全部执⾏成功,⼆者的结果是⼀样的。

如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上⼀个⽅法中执行的结果。

五:总结

在这里插入图片描述

本课内容到此结束!

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

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

相关文章

使用 Flink CDC 实现 MySQL 数据实时入 Apache Doris

简介 主要内容如下&#xff1a; MySQL 安装和开启binogFlink环境准备Apache Doris 环境准备启动Flink CDC作业 1. MySQL 安装和开启binog 参考文章&#xff1a;Ubuntu 安装 Mysql server, 这篇文章介绍了MySQL的安装&#xff0c;用户创建&#xff0c;Binlog开启等内容。 M…

Linux基础入门和常用命令

Linux基础入门和常用命令一. Linux介绍1.1 Linux的发行版本二. Linux环境搭建三. Linux的常用指令3.1 Linux下的目录结构3.2 ls命令3.3 pwd命令3.4 cd指令3.5 touch指令3.6 mkdir指令3.7 rmdir指令和 rm 指令3.8 man指令3.9 mv指令3.10 cp指令3.11 cat3.12 more指令3.13 less指…

基于机器学习组合模型的个人信用评估

《基于机器学习组合模型的个人信用评估》课程报告 摘要 个人信用评估在信用经济市场发挥着及其重要的基础作用&#xff0c;促进信用经济的发展&#xff0c;稳定经济市场。个人信用信息主要有个人基本信息、还款能力和还款意愿;个人基本信息主要由年龄、性别、地区等特征构成&…

C语言灵魂核心——指针深度修炼

&#x1f412;个人主页&#xff1a;平凡的小苏&#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情目录 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、…

【读论文】TCPMFNet

【读论文】TCPMFNet简单介绍网络结构编码器图像融合网络Vision Transformer特征融合网络网格连接解码器损失函数总结参考论文&#xff1a;https://www.sciencedirect.com/science/article/pii/S1350449522003863 如有侵权请联系博主 简单介绍 今天要介绍的是TCPMFNet&#xf…

六大排序算法

1. 插入排序步骤&#xff1a;1.从第一个元素开始&#xff0c;该元素可以认为已经被排序2.取下一个元素tem&#xff0c;从已排序的元素序列从后往前扫描3.如果该元素大于tem&#xff0c;则将该元素移到下一位4.重复步骤3&#xff0c;直到找到已排序元素中小于等于tem的元素5.tem…

如何使用 Pandas 清洗二手房数据并存储文件

目录 一、实战场景 二、知识点 python 基础语法 python 文件读写 pandas 数据清洗 三、菜鸟实战 清洗前的文件 读取源文件 对二手房数据进行清洗 清洗完成后保存到文件 运行结果 运行截图 结果文件 一、实战场景 如何使用 Pandas 清洗的二手房数据并存储文件 二…

初识结构体(详细版)

目录 一、结构体的声明 1、结构的基础知识 2、结构的声明 3、结构成员的类型 4、结构体变量的定义和初始化 二、结构体成员的访问 1、点操作符访问 2、->操作符访问 3、解引用访问 三、结构体嵌套 四、结构体传参 1、传值调用 2、传址调用 一、结构体的声明 1、结构的基…

Vue2前端路由(vue-router的使用)

一、vue2axiosExpressMySQL实现前后端交互1、后台&#xff1a;&#xff08;1&#xff09;确定MySQL的表格&#xff1a;明确数据库 &#xff08;mvc&#xff09; —- 数据表(ssm_book)&#xff08;2&#xff09;创建Express项目&#xff1a;mysql2、cors、Sequelize(ORM)、nodem…

imx6ull Linux sdk下载验证

本文章是基于整点原子的imx6ull alpha开发板一.Linux SDK源码以及image1.环境准备其他的工具我们就不做介绍了&#xff0c;比如ubuntu ftp,ssh等等&#xff0c;我们主要来介绍下编译链1.1 交叉编译链背景&#xff1a;因为在原子的教程中有强调最新的Linaro gcc编译完uboot后无法…

备受认可!中睿天下荣登“2022创业邦100未来独角兽”年度榜单

近日&#xff0c;由创业邦、复旦大学管理学院主办的2022创业邦100未来独角兽峰会暨创业邦年会在上海举办。在峰会现场&#xff0c;2022创业邦100未来独角兽榜单正式揭晓&#xff0c;中睿天下凭借出众的综合实力荣登榜单。作为一家以“实战对抗”为特点的能力价值型网络安全厂商…

Java版设计模式/设计模式的作用是什么/类之间有哪些关系?又怎么表示

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 1. 设计模式概述 1.1 设计模式创始“4人组” ErichGamma–艾瑞克伽马Richard Helm—理查德赫尔码Ralph Johnson----拉尔夫约翰逊John Vlissides—约翰威力斯蒂斯…

【Linux】Linux编译器-gcc/g++的使用和程序执行的基础底层原理

Linux编译器1.gcc/g 的使用2. 程序的基本翻译过程3.预处理3.1验证预处理的功能&#xff08;gcc -E&#xff09;4.编译&#xff08;变成汇编语言&#xff09;4.1验证编译过程&#xff08;gcc -S&#xff09;5.汇编&#xff08;生成机器可识别代码&#xff09;5.1验证汇编过程&am…

K_A11_002 基于STM32等单片机驱动DS18B20串口与OLED0.96双显示

K_A11_002 基于STM32等单片机驱动DS18B20 串口与OLED0.96双显示一、资源说明二、基本参数1.参数2.引脚说明三、驱动说明时序对应程序:四、部分代码说明1、接线说明1.1、STC89C52RCDHT11模块1.2、STM32F103C8T6DHT11模块五、基础知识学习与相关资料下载六、视频效果展示与程序资…

内核解读之内存管理(4)内存管理三级架构之page

我们前面介绍了linux内存管理的三级架构&#xff0c;node->zone->page。本节就来介绍page。 页是内核管理内存的基本单位&#xff0c;体系结构不同&#xff0c;支持的页大小也不尽相同&#xff0c;还有些体系结构甚至支持几种不同的页大小。大多数32位体系结构支持4KB的页…

【Python】爬取弹幕并保存到Excel中

hello&#xff0c;我是李华同学&#xff0c;最近开始学习爬虫&#xff0c;下面是我实现的一个得到弹幕的代码&#x1f60f;找一个URL&#x1f449;想要得到一个网站的内容&#xff0c;首先要找到你想要内容的具体位置&#xff0c;首先你先找到一个有弹幕的地方&#xff0c;找到…

linux系统重装yum工具与python环境

文章目录前言一、强制删除原有环境1.删除python2.删除yum二、安装新环境前置准备三、下载依赖命令1.内核版本7.6.1810下载python依赖包下载yum包2.内核版本7.4.1708下载python依赖包下载yum包3.内核版本7.8.2003下载python依赖包下载yum依赖包4.下载结果四、安装前言 安装之前…

swagger入门

目录 1.前后端分离的特点 2.在没有swagger之前 3.swagger的作用 4.swagger的优点 5.集成swagger 5.1 新建springboot项目 5.2 集成swagger 5.3 开发一个controller用于测试 5.4 启动服务&#xff0c;验证集成效果 6.swagger常用注解 7.swagger使用综合案例 8.会议OA之sw…

第三章 Flink DataStream API

Flink 系列教程传送门 第一章 Flink 简介 第二章 Flink 环境部署 第三章 Flink DataStream API 第四章 Flink 窗口和水位线 第五章 Flink Table API&SQL 第六章 新闻热搜实时分析系统 一、DataStream API是什么&#xff1f; Flink 中的 DataStream 程序是对数据流&a…

Android 深入系统完全讲解(2)

1 系统启动过程、嵌入式系统启动过程 这是我之前画的启动过程的图&#xff0c;这个主要就是给大家讲明白&#xff0c;启动过程整个的流程。 第一个阶段&#xff0c;bootloader 系统在上电的时候&#xff0c;系统会从固定的地方加载一段代码进入内部 ram 进行运行。这段代码 通…