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

目录

事务基本概念

前置准备

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抛出指定的异常类型,不会滚事务,也可以指定多个异常类型

注意:

  • 区别 只读事务 和 无事务
  • 只读事务 可以设置隔离级别,默认为可重复读,可设置 isolation 更改隔离级别
  • 无事务 仅为默认的隔离级别(可重复读)

@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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的遥感图像变化差异可视化系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像变化差异可视化是遥感图像处理和分析的重要研究领域之一。随着遥感技术的快速发展和遥感数据的广泛应用&#xff0c;遥感图像的获取和处理变得越来越容易…

Javaweb | Servlet编程

目录: 1.认识Servlet2.编写Servlet3.Servlet的运行机制4.Servlet的生命周期4.1 Servlet生命周期图init()方法doGet()doPost()service()destroy()方法 5.解决“控制台”打印中文乱码问题6.Servlet 和 JSP内置对象 (常用对象)获得out对象获得request 和 response对象获得session对…

KeePass开源密码管理器

KeePass开源密码管理器 KeePass 是一款免费的开源密码管理器&#xff0c;KeePass 将密码存储为一个数据库&#xff0c;而这个数据库由一个主密码或密码文件锁住&#xff0c;也就是说我们只需要记住一个主密码&#xff0c;或使用一个密码文件&#xff0c;就可以解开这个数据库&a…

每日一题:LeetCode-11.盛水最多的容器

每日一题系列&#xff08;day 13&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

服务器安装JDK17 版本显示JDK8

服务器之前安装的是JDK8&#xff0c;后面升级JDK17后&#xff0c;发现执行 java -vsrsion 显示的是此时我的环境变量已经换成了JAVA17的路径 输入&#xff1a; vim /etc/profile 解决办法&#xff1a; 1.更新自己环境变量 bash export JAVA_HOME/usr/local/jdk-17.0.7 …

【科普】什么是电子印章? PS抠的印章能用吗?

各类扣章教程一搜一大堆&#xff0c;说明大家对于电子印章使用需求很高。不过要谨记&#xff0c;不要随便抠印章用于公文、证明书、合同协议、收据发票等电子文件&#xff0c;否则可能会吃牢饭。 单是一张电子化的图片是不具备合法性的。那有的人就要问了&#xff0c;我见到的…

读书笔记-《数据结构与算法》-摘要4[插入排序]

插入排序 核心&#xff1a;通过构建有序序列&#xff0c;对于未排序序列&#xff0c;在已排序序列中从后向前扫描(对于单向链表则只能从前往后遍历)&#xff0c;找到相应位置并插入。实现上通常使用in-place排序(需用到O(1)的额外空间) 从第一个元素开始&#xff0c;该元素可…

【微服务】springboot整合quartz使用详解

目录 一、前言 二、quartz介绍 2.1 quartz概述 2.2 quartz优缺点 2.3 quartz核心概念 2.3.1 Scheduler 2.3.2 Trigger 2.3.3 Job 2.3.4 JobDetail 2.4 Quartz作业存储类型 2.5 适用场景 三、Cron表达式 3.1 Cron表达式语法 3.2 Cron表达式各元素说明 3.3 Cron表达…

封装PoiExcelUtils

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 为什么要封装PoiExcelU…

Openfire CVE-2023-32315(metasploit版)

step1&#xff1a;用docker搭建环境 Step2&#xff1a;docker查看映射端口 Step3&#xff1a;打开mysql和apache2&#xff0c;访问特定端口&#xff0c;然后靶标应用。 Step4&#xff1a;用metasploit进行攻击&#xff1a; 首先&#xff0c;打开metasploit&#xff0c;然…

Java (JDK 21) 调用 OpenCV (4.8.0)

Java 调用 OpenCV 一.OpenCV 下载和安装二.创建 Java Maven 项目三.其他测试 一.OpenCV 下载和安装 Open CV 官网 可以下载编译好的包&#xff0c;也可以下载源码自行编译 双击安装 opencv-4.8.0-windows.exe 默认为当前目录 安装即解压缩 根据系统位数选择 将 x64 目录下 op…

数据结构 | 查漏补缺之求叶子结点,分离链接法、最小生成树、DFS、BFS

求叶子结点的个数 参考博文&#xff1a; 树中的叶子结点的个数 计算方法_求树的叶子节点个数-CSDN博客 分离链接法 参考博文 数据结构和算法——哈希查找冲突处理方法&#xff08;开放地址法-线性探测、平方探测、双散列探测、再散列&#xff0c;分离链接法&#xff09;_线性…

Linux存储软件栈到底有多复杂,存储软件栈全景概览

从今天开始&#xff0c;我们将介绍一下Linux的存储软件栈&#xff0c;也称为IO栈。今天我们先从整体上了解一下Linux的整个IO栈&#xff0c;后续再深入介绍每个组件。我们很难说清楚Linux的存储软件栈到底有多复杂&#xff0c;但thomas-krenn的一张图可能可以给大家一个比较直观…

堆的相关时间复杂度计算(C语言)

目录 前言 建堆的时间复杂度 向上调整建堆的时间复杂度 向下调整建堆的时间复杂度 维护堆的时间复杂度 top K问题的时间复杂度 前言 在前面的三篇文章中我们成功的实现了堆排序的升降序以及基于堆的top K问题&#xff0c;现在我们来解决它们的时间复杂度问题 建堆的时间…

查看Linux的Ubuntu的版本

我的Ubuntu版本是 Jammy x86_64&#xff0c;即 Ubuntu 22.04.3 LTS&#xff0c;代号为"Jammy Jellyfish"&#xff0c;架构是 x86_64&#xff08;64位&#xff09;。

UEC++ 探索虚幻5笔记 day11

虚幻5.2.1探索 项目目录探索 C工程一定不能是中文路径&#xff0c;中文项目名&#xff0c;最好全部不要用中文&#xff0c;蓝图项目可以是中文浅浅创建一个空项目&#xff0c;讲解一下之前UE4没有讲解的项目目录文件的分布组成 .vs&#xff1a;文件夹一般是项目编译缓存文件夹…

linux安装mysql5.7(一遍过)

之前安装的时候遇到了很多问题&#xff0c;浪费了一些时间。整理出这份教程&#xff0c;照着做基本一遍过。 这是安装包: 链接&#xff1a;https://pan.baidu.com/s/1gBuQBjA4R5qRYZKPKN3uXw?pwd1nuz 1.下载安装包&#xff0c;上传到linux。我这里就放到downloads目录下面…

游戏盾的防御原理以及为什么程序类型更适合接入游戏盾。

游戏盾是一种专门用于游戏服务器的安全防护服务&#xff0c;旨在抵御各种网络攻击。它的原理主要包括以下几个方面&#xff1a; 流量清洗和过滤&#xff1a;游戏盾会对进入游戏服务器的流量进行实时监测、分析和过滤。它通过识别恶意流量和攻击流量&#xff0c;过滤掉其中的攻击…

多种混沌映射初始化种群MATLAB代码(开源代码)

五种混沌映射改进鲸鱼算法&#xff08;自行切换&#xff09;&#xff1a; clc clear close allSearchAgents_no30; % Number of search agents %种群数量Function_nameF1; % Name of the test function that can be from F1 to F23 (Table 1,2,3 in the paper) %使用方程的名…

【java设计模式】——代理设计模式,两种举例说明

代理设计模式 1.介绍 Spring 框架中AOP底层使用动态代理设计模式。通过学习动态代理设计模式可以很好的理解Spring框架AOP底层 代理模式&#xff08;Proxy&#xff09;是GoF23种设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象&#xff0c;而是通过调用代理&am…