SpringBoot 事务回滚注意事项

参考资料

  1. 导致 Spring 事务失效常见的几种情况
  2. SpringBoot2异常处理回滚事务详解(自动回滚/手动回滚/部分回滚)
  3. Spring,为内部方法新起一个事务,此处应有坑。
  4. PlatformTransactionManager
  5. Spring 事务管理及失效总结
  6. 我认真总结并分析了 Spring 事务失效的十种常见场景
  7. SpringBoot AOP配置全局事务

目录

  • 一. 需求
  • 二. 前期准备
    • 2.1 Mapper
    • 2.2 自定义异常
    • 2.3 前台
    • 2.4 Controller层
  • 三. 错误的使用方式
  • 四. 正确的使用方式
    • 4.1 方式1-抽取新类(声明式事务)
    • 4.2 方式2-编程式事务,事务管理器
    • 4.3 方式3-编程式事务,设置回滚点
  • 五. 事务失效的几种情况


一. 需求

最近在项目中遇到了一个需求,需要读取csv的数据到数据库的临时表,
然后调用存储过程将临时表中的数据插入正式的数据库中。
⏹在将csv数据插入临时表之前需要将该表中的数据清空,如果在插入临时表时出现了问题,同样需要将表中的数据清空。
⏹需要对前台传入的数据进行业务校验,如果校验失败,需要抛出自定义异常,同时临时表中的数据不能回滚,需要被清空。
⏹在调用存储过程时,如果发生异常,则事务回滚。
需要注意的是,只回滚存储过程所涉及的表,临时表中被删除的数据并不参与回滚,直接删除。


二. 前期准备

2.1 Mapper

import java.util.Map;public interface TestMapper02 {void deleteAllTempWork();void insertData(Map<String, Object> dataMap);
}
<?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.TestMapper02"><delete id="deleteAllTempWork">DELETE FROMtemp_work</delete><insert id="insertData" parameterType="map">INSERT INTO day16.`user`(id, username, birthday, email) VALUES (#{id}, #{username}, #{birthday}, #{email})</insert></mapper>

2.2 自定义异常

public class ValidationException extends RuntimeException {private static final long serialVersionUID = 1L;
}

2.3 前台

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事务测试</title>
</head>
<body><button id="btn1">点击发送请求</button>
</body>
<script th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script th:inline="javascript">$("#btn1").click(function() {const data = {from: 13,to: 14}$.ajax({url: "/test02/transactional",type: 'POST',data: JSON.stringify(data),contentType: 'application/json;charset=utf-8',success: function (data, status, xhr) {console.log(data);}});});
</script>
</html>

2.4 Controller层

import java.util.Map;
import javax.annotation.Resource;import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
@RequestMapping("/test02")
public class Test02Controller {@Resourceprivate Test02Service service;@GetMapping("/init")public ModelAndView init() {ModelAndView modelAndView = new ModelAndView();modelAndView.setViewName("test02");return modelAndView;}@PostMapping("/transactional")public ResponseEntity<Void> transactional(@RequestBody Map<String, Integer> data) throws Exception {// 测试事务service.transactional1(data);// service.transactional2(data);// service.transactional3(data);// 无响应给前台return ResponseEntity.noContent().build();}
}

三. 错误的使用方式

  • transactional1主方法调用同一个类中的insertData子方法,并且给子方法添加了@Transactional注解。
  • 出错的原因:
    • 在应用系统调用声明 @Transactional的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,再由这个代理对象来统一管理。
    • Spring 事务是使用 AOP 环绕通知和异常通知,就是对方法进行拦截,在方法执行前开启事务,在捕获到异常时进行事务回滚,在方法执行完成后提交事务。
    • 在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。
    • 👉👉👉spring是通过aop的方式,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。
import java.util.Date;
import java.util.HashMap;
import java.util.Map;import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.mapper.TestMapper02;@Service
public class Test02Service {// 准备要向数据库插入的数据private final static Map<String, Object> dataMap = new HashMap<>() {private static final long serialVersionUID = 1L;{put("id", 99);put("username", "test99");put("birthday", new Date());put("email", "test99@test.com");}};@Resourceprivate TestMapper02 mapper02;// 存在事务的方法public void transactional1(Map<String, Integer> data) throws Exception {// 删除临时表中的所有数据mapper02.deleteAllTempWork();// 获取from和to的值Integer fromValue = data.get("from");Integer toValue = data.get("to");if (fromValue > toValue) {// 抛出异常throw new Exception();}this.insertData(dataMap);}/*insertData方法被transactional1方法调用*/@Transactional(rollbackFor = Exception.class)public void insertData(Map<String, Object> dataMap) {mapper02.insertData(dataMap);// 模拟出现异常int reslt = 1 / 0;}
}

四. 正确的使用方式

4.1 方式1-抽取新类(声明式事务)

⏹创建一个新类,将需要被事务管理的部分放到此类中

import java.util.Map;
import javax.annotation.Resource;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class Test02SubService {@Resourceprivate TestMapper02 mapper02;@Transactional(rollbackFor = Exception.class)public void insertData(Map<String, Object> dataMap) throws Exception {mapper02.insertData(dataMap);// 模拟运行时异常int reslt = 1 / 0;}
}

⏹我们的主Service调用子Service,子Service由Spring来管理,会生成事务对象。

import java.util.Date;
import java.util.HashMap;
import java.util.Map;import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.mapper.TestMapper02;@Service
public class Test02Service {// 准备要向数据库插入的数据private final static Map<String, Object> dataMap = new HashMap<>() {private static final long serialVersionUID = 1L;{put("id", 99);put("username", "test99");put("birthday", new Date());put("email", "test99@test.com");}};@Resourceprivate Test02SubService subService;@Resourceprivate TestMapper02 mapper02;// 存在事务的方法public void transactional1(Map<String, Integer> data) throws Exception {// 删除临时表中的所有数据mapper02.deleteAllTempWork();// 获取from和to的值Integer fromValue = data.get("from");Integer toValue = data.get("to");if (fromValue > toValue) {// 抛出异常throw new Exception();}// 如果调用时发生异常,只会回滚insertData方法中的内容subService.insertData(dataMap);}
}

4.2 方式2-编程式事务,事务管理器

package com.example.demo.service;import java.util.Date;
import java.util.HashMap;
import java.util.Map;import javax.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;@Service
public class Test02Service {private final static Map<String, Object> dataMap = new HashMap<>() {private static final long serialVersionUID = 1L;{put("id", 99);put("username", "test99");put("birthday", new Date());put("email", "test99@test.com");}};@Resourceprivate TestMapper02 mapper02;// 事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;// 事务定义对象@Autowiredprivate TransactionDefinition transactionDefinition;public void transactional2(Map<String, Integer> data) throws Exception {// 删除临时表,此部分不需要被事务管理mapper02.deleteAllTempWork();// 手动开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);try {mapper02.insertData(dataMap);// 模拟异常int a = 1 / 0;} catch (Exception e) {// 手动回滚事务dataSourceTransactionManager.rollback(transactionStatus);// 抛出异常,防止程序继续执行throw new Exception();}// 若没有问题则,则提交事务dataSourceTransactionManager.commit(transactionStatus);}	
}

4.3 方式3-编程式事务,设置回滚点

  • 通过TransactionAspectSupport设置回滚点
  • rollbackFor = Exception.class:指定该异常需要回滚
  • noRollbackFor = ValidationException.class:指定该异常不需要回滚
import java.util.Date;
import java.util.HashMap;
import java.util.Map;import javax.annotation.Resource;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;@Service
public class Test02Service {private final static Map<String, Object> dataMap = new HashMap<>() {private static final long serialVersionUID = 1L;{put("id", 99);put("username", "test99");put("birthday", new Date());put("email", "test99@test.com");}};@Resourceprivate TestMapper02 mapper02;// 指定抛出Exception异常时回滚;指定抛出ValidationException异常时不回滚@Transactional(rollbackFor = Exception.class, noRollbackFor = ValidationException.class)public void transactional3(Map<String, Integer> data) throws Exception {// 如果出现了任何异常就进行捕获,然后就抛出自定义的ValidationException异常// 我们在声明式事务中指定了ValidationException异常不进行回滚,// 因此就算此处的try catch块中的异常被抛出,此处的事务也不会进行回滚try {mapper02.deleteAllTempWork();} catch (Exception e) {throw new ValidationException();}// 设置事务的回滚点Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();try {mapper02.insertData(dataMap);// 模拟运行时异常int a = 1 / 0;} catch (Exception e) {// 当发生异常的时候,将事务回滚到预设的回滚点处TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);}}
}

五. 事务失效的几种情况

  1. @EnableTransactionManagement注解未启用
  2. 方法不是public类型
    • @Transaction 可以用在类上、接口上、public方法上,如果将@Trasaction用在了非public方法上,事务将无效。
  3. 三. 错误的使用方式所示的自调用情况
    • spring是通过aop的方式,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。
  4. 抛出的异常被捕获
    • 当业务方法抛出异常,spring感知到异常的时候,才会做事务回滚的操作,若方法内部将异常给吞了,那么事务无法感知到异常了,事务就不会回滚了。
  5. 数据库非InnoDB引擎
    • 只有InnoDB引擎才支持事务,而MyISAM引擎是不支持事务的

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

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

相关文章

《入门级-Cocos2dx4.0 塔防游戏开发》---第五课:欢迎界面开发(三、事件响应)

目录 一、开发环境介绍 二、开发内容 2.1 开始按钮的事件处理 2.2 背景音乐和背景音效事件处理

YoloV8优化:感受野注意力卷积运算(RFAConv),效果秒杀CBAM和CA等 | 即插即用系列

💡💡💡本文改进:感受野注意力卷积运算(RFAConv),解决卷积块注意力模块(CBAM)和协调注意力模块(CA)只关注空间特征,不能完全解决卷积核参数共享的问题 RFAConv| 亲测在多个数据集能够实现大幅涨点,有的数据集达到3个点以上 💡💡💡Yolov8魔术师,独家首…

LLM - Transformer LLaMA2 结构分析与 LoRA 详解

目录 一.引言 二.图说 LLM 1.Transformer 结构 ◆ Input、Output Embedding ◆ PositionEmbedding ◆ Multi-Head-Attention ◆ ADD & Norm ◆ Feed Forward ◆ Linear & Softmax 2.不同 LLM 结构 ◆ Encoder-Only ◆ Encoder-Decoder ◆ Decoder-Only …

CSS文本裁剪

对于单行文本裁剪&#xff1a; span {text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block; } 对于多行文本裁剪&#xff1a; 在文本容器上定义 CSS Flexbox 属性 display: -webkit-box; 定义要显示的文本行数 -webkit-line-clamp: 4; 添加 -webkit-…

实现缓存el-table分页大小,用户新建标签打开该页面需保持分页大小(考虑是否为嵌入式页面)

需求&#xff1a;每个表格的分页大小 以本地缓存的方式存到浏览器本地&#xff0c;然后用户下次打开的时候 获取这个本地存储的值 如果没有就用页面默认的值&#xff0c;如果有 则先判断是不是有效的(是) 无效用默认 有效就用这个缓存值,需要区分是否为嵌入式页面 分析&#xf…

写给 Android 应用工程师的 Binder 原理剖析

一. 前言 这篇文章我酝酿了很久&#xff0c;参考了很多学习文档&#xff0c;读了很多源码&#xff0c;却依旧不敢下笔。生怕自己理解上还有偏差&#xff0c;对大家造成误解&#xff0c;贻笑大方。又怕自己理解不够透彻&#xff0c;无法用清晰直白的文字准确的表达出 Binder 的…

机器学习——卷积神经网络基础

卷积神经网络&#xff08;Convolutional Neural Network&#xff1a;CNN&#xff09; 卷积神经网络是人工神经网络的一种&#xff0c;是一种前馈神经网络。最早提出时的灵感来源于人类的神经元。 通俗来讲&#xff0c;其主要的操作就是&#xff1a;接受输入层的输入信息&…

【论文阅读】Deep Instance Segmentation With Automotive Radar Detection Points

基于汽车雷达检测点的深度实例分割 一个区别&#xff1a; automotive radar 汽车雷达 &#xff1a; 分辨率低&#xff0c;点云稀疏&#xff0c;语义上模糊&#xff0c;不适合直接使用用于密集LiDAR点开发的方法 &#xff1b; 返回的物体图像不如LIDAR精确&#xff0c;可以…

leetcode做题笔记65

有效数字&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; 一个 小数 或者 整数&#xff08;可选&#xff09;一个 e 或 E &#xff0c;后面跟着一个 整数 小数&#xff08;按顺序&#xff09;可以分成以下几个部分&#xff1a; &#xff08;可选&#xff0…

Golang 中的交叉编译详解

Golang 中的交叉编译 在 Golang 中&#xff0c;交叉编译指的是在同一台机器上生成针对不同操作系统或硬件架构的二进制文件。这在开发跨平台应用或构建特定平台的发布版本时非常有用。 交叉编译 Golang 程序的基本步骤如下&#xff1a; 指定目标操作系统和工具链并设置对应的…

Kuebernetes资源控制管理

第四阶段 时 间&#xff1a;2023年8月11日 参加人&#xff1a;全班人员 内 容&#xff1a; Kuebernetes资源控制管理 目录 Kubectl命令工具 一、kubectl 命令行的语法 二、kubectl命令列表 三、使用 Kubectl 工具容器资源 &#xff08;一&#xff09;创建Pod &…

第十六次CCF计算机软件能力认证

第一题&#xff1a;小中大 在数据分析中&#xff0c;最小值最大值以及中位数是常用的统计信息。 老师给了你 n 个整数组成的测量数据&#xff0c;保证有序&#xff08;可能为升序或降序)&#xff0c;可能存在重复的数据。 请统计出这组测量数据中的最大值、中位数以及最小值&am…

Mysql SUBSTRING_INDEX - 按分隔符截取字符串

作用&#xff1a; 按分隔符截取字符串 语法&#xff1a; SUBSTRING_INDEX(str, delimiter, count) 属性&#xff1a; 参数说明str必需的。一个字符串。delimiter必需的。分隔符定义&#xff0c;是大小写敏感&#xff0c;且是多字节安全的count必须的。大于0或者小于0的数值…

最强的表格组件—AG Grid使用以及License Key Crack

PS: 想要官方 License Key翻到最后面 Ag Grid简介 Ag-Grid 是一个高级数据网格&#xff0c;适用于JavaScript/TypeScript应用程序&#xff0c;可以使用React、Angular和Vue等流行框架进行集成。它是一种功能强大、灵活且具有高度可定制性的表格解决方案&#xff0c;提供了丰富…

Mybatis参数传递

Map传参, #{}里的key要一一对应不能乱写&#xff0c;如果不存在则会填充NULL&#xff0c;不会报错 Map<String, Object> map new HashMap<>(); // 让key的可读性增强 map.put("carNum", "103"); map.put("brand", "奔驰E300L&…

2005-2020年280个地级市绿色全要素生产率测算原始数据

2005-2020年280个地级市绿色全要素生产率测算原始数据 1、时间&#xff1a;2005-2020年 2、来源&#xff1a;中国城市统计年鉴、中国区域统计年鉴、中国能源年鉴、中国环境年鉴等 3、范围&#xff1a;280个地级市 4、指标&#xff1a;年末单位从业人员数、规模以上工业企业…

(一)创建型设计模式:2、单例模式(C++实现实例 线程安全)

目录 1、单例模式&#xff08;Singleton Pattern&#xff09;的含义 2、单例模式的优缺点 &#xff08;1&#xff09;优点&#xff1a; &#xff08;2&#xff09;缺点&#xff1a; 3、C实现单例模式的示例&#xff08;简单&#xff09; 4、C实现单例模式的示例&#xff…

React(5)

1.受控组件案例 1.1之前的影院案例改写 import React, { Component } from react import axios from axios import BetterScroll from better-scroll import ./css/02_tab.cssexport default class Cinema extends Component {constructor() {super();this.state {cinemaLis…

【Docker晋升记】No.1--- Docker工具核心组件构成(镜像、容器、仓库)及性能属性

文章目录 前言&#x1f31f;一、Docker工具&#x1f31f;二、Docker 引擎&#x1f30f;2.1.容器管理&#xff1a;&#x1f30f;2.2.镜像管理&#xff1a;&#x1f30f;2.3.资源管理&#xff1a;&#x1f30f;2.4.网络管理&#xff1a;&#x1f30f;2.5.存储管理&#xff1a;&am…

时序预测 | MATLAB实现BO-LSTM贝叶斯优化长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现BO-LSTM贝叶斯优化长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现BO-LSTM贝叶斯优化长短期记忆神经网络时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-LSTM贝叶斯优化长短期记忆神经网络时间序列预…