【SpringBoot】Spring中事务的实现:声明式事务@Transactional、编程式事务

1. 准备工作

1.1 在MySQL数据库中创建相应的表

用户注册的例子进行演示事务操作,索引需要一个用户信息表

(1)创建数据库

-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;

(2)创建用户表

-- ⽤⼾表
DROP TABLE IF EXISTS user_info; CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR (128) NOT NULL,`count` int NOT NULL,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(), PRIMARY KEY (`id`)) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '用户表';

(3)操作日志表

-- 操作⽇志表
DROP TABLE IF EXISTS log_info;
CREATE	TABLE log_info (	`id` INT PRIMARY KEY auto_increment,	`from` VARCHAR ( 128 ) NOT NULL,	`to` VARCHAR ( 256 ) NOT NULL,	`num` int not null,`create_time` DATETIME DEFAULT now(),	`update_time` DATETIME DEFAULT now() ON UPDATE	now()) DEFAULT charset 'utf8mb4';

1.2 在Java项目中创建相应的实体类

(1)UserInfo类:

@Data
public class UserInfo {private Integer id;private String userName;private Integer count;private Date createTime;private Date updateTime;
}

(2)用户日志表

@Data
public class LogInfo {private Integer id;private String from;private String to;private Integer num;private Date createTime;private Date updateTime;
}

2. Spring编程式事务

2.1 简单介绍

Spring ⼿动操作事务和上⾯ MySQL 操作事务类似, 有 3 个重要操作步骤:
(1)开启事务(获取事务)
(2)提交事务
(3)回滚事务

SpringBoot 内置了两个对象:

  1. DataSourceTransactionManager 事务管理器.:⽤来获取事务(开启事务), 提交或回滚事务
  2. TransactionDefinition 是事务的属性, 在获取事务的时候需要将
    TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus

2.2 使用事务

2.2.1 创建Mapper接口

(1)创建UserInfoMapper接口

@Mapper
@Mapper
public interface UserInfoMapper {@Insert("insert into user_info(user_name, `count`) values(#{userName},#{count})")Integer insert(UserInfo userInfo);@Update("update user_info set count = count +#{countAdd} where user_name=#{userName}")Integer updateAdd(@Param("userName") String userName, @Param("countAdd") int countAdd);@Update("update user_info set count = count - #{countDelete} where user_name=#{userName}")Integer updateDelete(@Param("userName") String userName, @Param("countDelete") int countDelete);
}

使用测试类向该表转中插入数据:

@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid insert() {UserInfo userInfo1 = new UserInfo();userInfo1.setUserName("zangsan");userInfo1.setCount(100);UserInfo userInfo2 = new UserInfo();userInfo2.setUserName("lisi");userInfo2.setCount(100);userInfoMapper.insert(userInfo1);userInfoMapper.insert(userInfo2);}
}

(2)创建LogInfoMapper接口

@Mapper
public interface LogInfoMapper {@Insert("insert into log_info(`from` ,`to` , `num`) values(#{from},#{to},#{num})")Integer insert(LogInfo logInfo);
}

2.3.2 创建Controller接口

需求:A 向 B 转 10

@RequestMapping("/user")
@RestController
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@RequestMapping("/update")public boolean  updateUserInfo(@RequestBody LogInfo logInfo) {boolean flag =  userInfoService.updateUserInfo(logInfo);return flag;}
}

2.3.3 创建Service接口

事务通常放在Service接口中,事务的逻辑也在Service接口中

@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;//JDBC事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性private TransactionDefinition transactionDefinition;public boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);//提交事务dataSourceTransactionManager.commit(transactionStatus);return true;}
}

2.3.4 测试

使用PostMan测试:
在这里插入图片描述

运行结果:
在这里插入图片描述

在MySQL中查询是否成功:
在这里插入图片描述
成功–数据已更新和记录

2.3.5 演示事务出错

在Service的事务中设置一个出错点:

@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;//JDBC事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性private TransactionDefinition transactionDefinition;public boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);// 故意设置的出错点int n = 10/0;//提交事务dataSourceTransactionManager.commit(transactionStatus);return true;}
}

使用Postman测试:
在这里插入图片描述
运行结果:
在这里插入图片描述
可以从运行结果中看到,只是释放了会话,没有提交事务,在java语句int n = 10/0;之前的语句会执行成功吗?

看一下MySQL中表的变化:
在这里插入图片描述

没有任何的变化。
所以,在事务中如果有异常会自动回滚事务。

2.3.6 演示事务回滚

只需要把Service接口中的提交事务改为回滚事务即可:

@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;//JDBC事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性private TransactionDefinition transactionDefinition;public boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);//        //提交事务
//        dataSourceTransactionManager.commit(transactionStatus);//事务回滚dataSourceTransactionManager.rollback(transactionStatus);return true;}
}

当再次请求的时候会发现程序运行成功但是数据库中的数据没有修改。

3. Spring声明式事务 @Transactional

3.1 简介

Spring声明式事务很简单,只需要添加注解@Transactional

(1)相关的依赖:

        <dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></dependency>

(2)在需要事务的⽅法上添加 注解就可以实现了. ⽆需⼿动开启事务和提交事务, 进⼊⽅法时⾃动开启事务, ⽅法执⾏完会⾃动提交事务, 如果中途发生了没有处理的异常会⾃动回滚事务.

3.2 使用@Transactional

3.2.1 创建Mapper接口

(1)创建UserInfoMapper接口

@Mapper
@Mapper
public interface UserInfoMapper {@Insert("insert into user_info(user_name, `count`) values(#{userName},#{count})")Integer insert(UserInfo userInfo);@Update("update user_info set count = count +#{countAdd} where user_name=#{userName}")Integer updateAdd(@Param("userName") String userName, @Param("countAdd") int countAdd);@Update("update user_info set count = count - #{countDelete} where user_name=#{userName}")Integer updateDelete(@Param("userName") String userName, @Param("countDelete") int countDelete);
}

使用测试类向该表转中插入数据:

@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid insert() {UserInfo userInfo1 = new UserInfo();userInfo1.setUserName("zangsan");userInfo1.setCount(100);UserInfo userInfo2 = new UserInfo();userInfo2.setUserName("lisi");userInfo2.setCount(100);userInfoMapper.insert(userInfo1);userInfoMapper.insert(userInfo2);}
}

(2)创建LogInfoMapper接口

@Mapper
public interface LogInfoMapper {@Insert("insert into log_info(`from` ,`to` , `num`) values(#{from},#{to},#{num})")Integer insert(LogInfo logInfo);
}

3.3.2 创建Controller接口

需求:A 向 B 转 10

@RequestMapping("/user")
@RestController
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@RequestMapping("/update")public boolean  updateUserInfo(@RequestBody LogInfo logInfo) {boolean flag =  userInfoService.updateUserInfo(logInfo);return flag;}
}

3.3.3 创建Service接口

事务通常放在Service接口中,在方法上使用注解@Transactional,事务的逻辑写在方法中

@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;public boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);return true;}
}

3.3.4 测试

使用PostMan测试:
在这里插入图片描述
注:
如果发生报错:Public Key Retrieval is not allowed
在配置文件中修改如下:

spring.datasource.url=jdbc:mysql://x.x.x.x:3306/trans_test?characterEncoding=utf8&useSSL=false&&allowPublicKeyRetrieval=true&useSSL=false

关键参数说明​​:
allowPublicKeyRetrieval=true:允许驱动从服务器获取公钥。
useSSL=false:禁用 SSL(开发环境可用,生产环境建议启用)。

运行结果:
在这里插入图片描述
MySQL中的结果:
在这里插入图片描述
数据已更新和记录

3.3.4 演示事务出错

在Service中设置一个出错点:

@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;@Transactionalpublic boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);// 故意设置出错点int n = 10/0;return true;}
}

postman测试:
在这里插入图片描述
运行结果:
在这里插入图片描述
可以看到只是释放了会话,没有提交事务

MySQL中的数据没有修改成功(数据没有改动)。在java语句int n = 10/0;之前的语句不会执行成功。

3.3 @Transactional 注解

3.3.1 介绍

@Transactional 可以⽤来修饰⽅法或类:
(1)修饰⽅法时: 只有修饰public 方法时才生效(修饰其他⽅法时不会报错, 也不⽣效)[推荐]
(2) 修饰类时: 对 @Transactional 修饰的类中所有的 public ⽅法都⽣效.

方法/类被 @Transactional 注解修饰时, 在⽬标⽅法执⾏开始之前, 会⾃动开启事务, ⽅法执⾏结束之后, ⾃动提交事务.

如果在⽅法执⾏过程中, 出现异常, 且异常未被捕获, 就进⾏事务回滚操作.

如果异常被程序捕获, ⽅法就被认为是成功执⾏, 依然会提交事务.

3.3.2 异常捕获–事务提交

修改Service接口:

@Slf4j
@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;@Transactionalpublic boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);try {// 故意设置出错点int n = 10 / 0;}catch (Exception e) {log.info("捕获 事务中的异常:"+ e);}return true;}
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述

MySQL中的数据:
在这里插入图片描述
数据已更新和记录

3.3.3 异常捕获后再抛出–事务回滚

修改Service中的代码:

@Slf4j
@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;@Transactionalpublic boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);try {// 故意设置出错点int n = 10 / 0;}catch (Exception e) {log.info("捕获事务中的异常后 再抛出:"+ e);//抛出异常throw e;}return true;}
}

使用postman测试:

在这里插入图片描述
可以看到发生报错

运行结果:
在这里插入图片描述
可以看到只是释放了会话,没有提交事务

MySQL:
在这里插入图片描述
MySQL中的数据没有发生改变

3.3.4 异常捕获后手动抛出–事务回滚

使⽤ TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并使⽤ setRollbackOnly 设置 setRollbackOnly

Service接口:

@Slf4j
@Service
public class UserInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Autowiredprivate UserInfoMapper userInfoMapper;@Transactionalpublic boolean updateUserInfo(LogInfo logInfo) {// 处理 from 向 to 转账 10// 事务逻辑// 1.from 的账户 -10userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());// 2.to 的账户 +10userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());// 3. 记录日志logInfoMapper.insert(logInfo);try {// 故意设置出错点int n = 10 / 0;}catch (Exception e) {log.info("捕获事务中的异常后 手动回滚事务:"+ e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return true;}
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述
前端收到的访问结果是成功的,但是运行过程中还是没有提交事务

MySQL:
在这里插入图片描述
MySQL中的数据没有发生改变

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

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

相关文章

javascript 深拷贝和浅拷贝的区别及具体实现方案

一、核心区别 特性浅拷贝深拷贝复制层级仅复制对象的第一层属性递归复制对象的所有层级属性&#xff08;包括嵌套对象和数组&#xff09;引用关系嵌套对象/数组与原对象共享内存&#xff08;引用拷贝&#xff09;嵌套对象/数组与原对象完全独立&#xff08;值拷贝&#xff09;…

pytorch对应gpu版本是否可用判断逻辑

# gpu_is_ok.py import torchdef check_torch_gpu():# 打印PyTorch版本print(f"PyTorch version: {torch.__version__}")# 检查CUDA是否可用cuda_available torch.cuda.is_available()print(f"CUDA available: {cuda_available}")if cuda_available:# 打印…

国内无法访问GitHub官网的问题解决

作为一名程序员&#xff0c;在国内访问GitHub官网经常会遇到打开过慢或者访问失败的问题&#xff0c;但通过一些技巧可以改善访问体验。GitHub访问问题的根源在于GitHub官网访问不稳定的主要原因在于DNS解析过程。当我们直接访问github.com时&#xff0c;需要通过DNS服务器将域…

使用 MediaPipe 和 OpenCV 快速生成人脸掩膜(Face Mask)

在实际项目中&#xff0c;尤其是涉及人脸识别、换脸、图像修复等任务时&#xff0c;我们经常需要生成人脸区域的掩膜&#xff08;mask&#xff09;。这篇文章分享一个简单易用的小工具&#xff0c;利用 MediaPipe 和 OpenCV&#xff0c;快速提取人脸轮廓并生成二值掩膜图像。 …

【动态导通电阻】GaN功率器件中动态导通电阻退化的机制、表征及建模方法

2019年,浙江大学的Shu Yang等人在《IEEE Journal of Emerging and Selected Topics in Power Electronics》上发表了一篇关于GaN(氮化镓)功率器件动态导通电阻(Dynamic On-Resistance, RON)的研究论文。该文深入探讨了GaN功率器件中动态导通电阻退化的机制、表征方法、建模…

从括号匹配看栈:数据结构入门的实战与原理

在计算机科学的世界里&#xff0c;数据结构是程序员的 “瑞士军刀”&#xff0c;不同的数据结构适用于不同的场景&#xff0c;能高效解决各类问题。其中&#xff0c;栈作为一种简单却强大的数据结构&#xff0c;在很多实际应用中发挥着关键作用。今天&#xff0c;我们就通过一个…

Dubbo(89)如何设计一个支持多语言的Dubbo服务?

设计一个支持多语言的Dubbo服务需要考虑以下几个方面&#xff1a; 服务接口设计&#xff1a;确保服务接口的定义可以被不同语言实现。序列化协议&#xff1a;选择一个支持多语言的序列化协议&#xff0c;例如Protobuf、Thrift、gRPC等。服务注册与发现&#xff1a;确保服务注册…

力扣面试150题--分隔链表

day 39 题目描述 思路 遍历链表&#xff0c;每一个点与值比较&#xff0c;比值小就继续&#xff0c;比值大就放到链表尾部即可 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int…

VSCode 查看文件的本地修改历史

1. 使用时间线视图&#xff08;Timeline&#xff09; 新版 VSCode 内置了一个叫 Timeline&#xff08;时间线&#xff09; 的功能&#xff0c;可以查看&#xff1a; 本地文件修改记录&#xff08;包括保存历史&#xff09;Git 提交历史&#xff08;如果仓库是 Git 管理的&…

C++学习-入门到精通-【3】控制语句、赋值、自增和自减运算符

C学习-入门到精通-【3】控制语句、赋值、自增和自减运算符 控制语句、赋值、自增和自减运算符 C学习-入门到精通-【3】控制语句、赋值、自增和自减运算符一、什么是算法二、伪代码三、控制结构顺序结构选择结构if语句if...else语句switch语句 循环结构while语句 四、算法详述&a…

父子组件双向绑定

v-model 语法糖实现 vue中我们在input中可以直接使用v-model来完成双向绑定,这个时候 v-model 通常会帮我们完成两件事: v-bind:value的数据绑定@input的事件监听如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢? 当我…

用Selenium开启自动化网页交互与数据抓取之旅

用Selenium开启自动化网页交互与数据抓取之旅 在当今数字化时代&#xff0c;数据的价值不言而喻&#xff0c;而网页作为海量数据的重要载体&#xff0c;如何高效获取其中的关键信息成为众多开发者和数据爱好者关注的焦点。Selenium这一强大工具&#xff0c;为我们打开了自动化…

VB.net序列化和反序列化的使用方法和实用场景

引言 相信很多初学编程的人都会提出过这个疑问&#xff1a;“既然我的变量可以存在内存之中&#xff0c;那么是否也可以存在硬盘之中呢” 其实我想回答的是&#xff0c;完全可以而且方法不止一种&#xff0c;而今天讲的是序列化最经典的——二进制序列化 由于序列化的部分已…

Android OTA

一、OTA运行原理 Android 平台提供 Google diff arithmetic 差分机制&#xff0c;升级包支持完整升级以及差分升级&#xff0c;OTA 运行原理图如下所示。 1. OTA Server 负责对更新包进行上传&#xff0c;下载以及版本的管理。 2. 开发者在修改 Android 系统后&#xff0c;通…

Untiy基础学习(三)Untiy中编写脚本的基本规则

一、怎么创建脚本 在Project窗口下&#xff0c;右键Create C#Script 即可创建脚本 创建脚本的注意事项 &#xff1a; 1&#xff09;类名和文件名必须一致,不然不能挂载&#xff08;因为反射机制创建对象&#xff0c;会通过文件名去找Type&#xff09; 2&#xff09;没有特殊需…

VBA宏即根据第一列的内容和第二列的数字,按照数字数量生成对应内容并依次放在第三列、第四列等

打开你的 Excel 工作表。按下 Alt F11 组合键&#xff0c;打开 VBA 编辑器。在 VBA 编辑器中&#xff0c;点击 插入 -> 模块。在模块窗口中&#xff0c;输入以下 VBA 代码&#xff1a; Sub GenerateItems()Dim lastRow As LongDim i As Long, j As LongDim item As String…

深度学习系统学习系列【1】之基本知识

文章目录 说明基础知识人工智能、机器学习、深度学习的关系机器学习传统机器学习的缺陷选择深度学习的原因深度学习的关键问题深度学习的应用深度学习的加速硬件GPU环境搭建主流深度学习框架对比 说明 文章属于个人学习笔记内容&#xff0c;仅供学习和交流。内容参考深度学习原…

论文笔记-基于多层感知器(MLP)的多变量桥式起重机自适应安全制动与距离预测

《IET Cyber-Systems and Robotics》出版山东大学 Tenglong Zhang 和 Guoliang Liu 团队的研究成果&#xff0c;文章题为“Adaptive Safe Braking and Distance Prediction for Overhead Cranes With Multivariation Using MLP”。 摘要 桥式起重机的紧急制动及其制动距离预测是…

DeepSeek实战--各版本对比

1.对比 版本参数量优势劣势使用场景竞品DeepSeek-V36710亿&#xff08;MoE架构&#xff0c;激活370亿&#xff09;开源、高效推理&#xff08;60 TPS&#xff09;、低成本&#xff08;API费用低&#xff09;、中文处理能力突出&#xff08;90%准确率多模态能力有限通用任务&am…

从0开始建立Github个人博客(hugoPaperMod)

从0开始建立Github个人博客(hugo&PaperMod) github提供给每个用户一个网址&#xff0c;用户可以建立自己的静态网站。 一、Hugo hugo是一个快速搭建网站的工具&#xff0c;由go语言编写。 1.安装hugo 到hugo的github标签页Tags gohugoio/hugo选择一个版本&#xff0c…