事务方法中保证数据只插入一次方案探究
需求场景
在项目的接口请求中,我们有一个接口A需要事务支持,在接口A中调用了方法B,方法B也需要事务支持,两者都带有@Transactional
注解。在B方法中是这个一个逻辑,查询本地数据库是否包含属性值为一个特定值的字段,如果没有的话就插入,如果有的话就跳过。
问题难点及方案分析
首先最简单的方式是在数据层面加入唯一性约束,但是项目中会出现报错,并且这里我们要求不能在数据库层面进行操作,数据的事务的隔离级别必须是可重复读,只能在代码中保证数据插入的数据的唯一性。
@PostMapping("/add")
@Transactional
public String addUser(@RequestBody User user) {//逻辑代码1...//逻辑代码1...//逻辑代码1...//插入逻辑boolean andInsertUser = userService.getAndInsertUser(user);//逻辑代码2...//逻辑代码2...//逻辑代码2...return andInsertUser ? "添加成功" : "插入失败";
}
@Transactionalpublic boolean getAndInsertUser(User user) {LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getAge, user.getAge());List<User> list = list(lambdaQueryWrapper);if (Objects.nonNull(list) && !list.isEmpty()) {return true;}return save(user);}
难点1及方案分析
- 在并发的情况下可能两次请求查询基本同时执行,都查询到了相同的结果发现没有数据,然后都执行了插入的请求,导致数据的重复。
针对于上述情况,我们可以使用信号量机制来解决,使用信号量之后即使在并发的情况下发生,也只有一个线程能够真正执行里面的内容,并且其他的线程获取资源失败之后并不会阻塞。
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {Semaphore semaphore = new Semaphore(1);@Transactionalpublic boolean getAndInsertUser(User user) {if (semaphore.tryAcquire()){try {LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getAge, user.getAge());List<User> list = list(lambdaQueryWrapper);if (Objects.nonNull(list) && !list.isEmpty()) {return true;}return save(user);} finally {semaphore.release();}}return true;}
}
难点2及方案分析
- 由于可重读读颗粒级别下存在存在幻读问题,我们考虑这样一种情况,两个请求a和b都进入了
addUser
方法,其中a执行在逻辑代码1的时候,b已经执行完成了插入逻辑,当a执行到插入逻辑的时候semaphore.tryAcquire()
一定是可以成功执行的,而由于addUser
方法添加了事务注解,这就导致即使b线程已经执行完了插入逻辑但是a在执行插入逻辑的时候,下面的代码在if
判断的时候依然查不到刚才b插入的数据,这里是因为a的事务开启是在b插入数据之前,导致a查询的是a开启事务时候的快照,快照中并不存在b刚才插入的数据,这就导致了再次插入数据。
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getAge, user.getAge());List<User> list = list(lambdaQueryWrapper);if (Objects.nonNull(list) && !list.isEmpty()) {return true;}return save(user);
针对上述的情况,原因就是a事务的开启在b事务提交之前。如果我们两条事务不是并发执行的而是一条事务执行完成之后另一个事务才开启就不会存在这个问题。
- 一个看似合理的解决方案
private volatile AtomicInteger stamp = new AtomicInteger(0);@PostMapping("/add")
@Transactional
public String addUser(@RequestBody User user) {int stamp_temp = stamp.get();//逻辑代码1...//逻辑代码1...//逻辑代码1...//插入逻辑boolean andInsertUser = userService.getAndInsertUser(user, stamp_temp, stamp);//逻辑代码2...//逻辑代码2...//逻辑代码2...return andInsertUser ? "添加成功" : "插入失败";
}
private static final Semaphore semaphore = new Semaphore(1);@Transactionalpublic boolean getAndInsertUser(User user, int stamp, AtomicInteger atomicInteger) {if (semaphore.tryAcquire()){if (Objects.equals(atomicInteger.get(), stamp)) {try {LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(User::getAge, user.getAge());List<User> list = list(lambdaQueryWrapper);if (Objects.nonNull(list) && !list.isEmpty()) {return true;}return save(user);} finally {atomicInteger.incrementAndGet();semaphore.release();}}}return true;}
}
这里采用了类似于解决ABA问题的解决方案,但是会存在这样一种情况,假如两个线程a和b都执行了addUser()
方法,假如起始时候stamp
的值是0,a线程执行到了逻辑代码2,这个时候stamp一定已经被a线程变成了1,此时,线程b这个时候首先获取stamp的值是1,线程a还没有执行完成,也就是事务还没有提交,后面执行插入逻辑的时候当然一样会存在读取不到最新数据的问题,导致b线程还是能够插入成功。最后两个事务都会提交成功,这样还是会插入两次相同的数据。
- 使用
select for update
保证当前读
这个方法在MySQL中没有进行实验,在postgresql中读已提交和可重复读隔离界别下均读取不到其他事务已经插入但是没有提交的数据。仍然无法解决问题。
难点3及解决分析
以上的解决方案都是在单机环境下的解决方案,多机分布式环境下仍然会存在很多的问题。针对于以上问题,提出一下两种方案:
- 使用消息中间件将消息发到mq中进行消费,这样就不会和业务前面的业务逻辑代码冗余在一起,提高接口响应速度。但是由于消息消费可能出现并发消费的问题导致同时插入多条相同数据。
- 使用redis分布式锁来解决消息并发消费的问题,保证分布式环境下,消费消息的同步性。同时可以根据业务逻辑丢弃消息。
相关文章:

事务方法中保证数据只插入一次方案探究
需求场景 在项目的接口请求中,我们有一个接口A需要事务支持,在接口A中调用了方法B,方法B也需要事务支持,两者都带有Transactional注解。在B方法中是这个一个逻辑,查询本地数据库是否包含属性值为一个特定值的字段&…...

高通开发系列 - 5G网络之QTI守护进程服务介绍
By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回:专栏总目录 目录 代码位置和依赖关系功能介绍代码逻辑讲解外设节点关注的目录socket服务端初始化DPM客户端监听守护关键的数据结构体…...

Ansible学习笔记3
ansible模块: ansible是基于模块来工作的,本身没有批量部署的能力,真正具有批量部署的是ansible所运行的模块,ansible只是提供一个框架。 ansible支持的模块非常多,我们并不需要把每个模块记住,而只需要熟…...

DP读书:鲲鹏处理器 架构与编程(十)鲲鹏软件生态与云服务
十秒带你了解鲲鹏软件生态与云服务 鲲鹏软件生态与云服务ARM授权机制在传统的PC领域,半导体厂商的业务类型主要分为两种:在移动领域, ARM服务器生态鲲鹏服务器软件生态1. 鲲鹏计算产业2. 鲲鹏软件生态兼容性3. openEluer操作系统4. 鲲鹏软件栈…...

CSS_IOS适配状态栏和IOS底部安全区域
状态栏 var(--status-bar-height)计算属性 height: calc(var(--status-bar-height) 343px);底部安全区 先constant再env constant(safe-area-inset-bottom) env(safe-area-inset-bottom)计算属性 height: calc(132px constant(safe-area-inset-bottom)); height: calc(1…...

中央仓库更新失败,IDEA报错repository is non-nexus repo, or does not indexed
某个仓库未被识别为 Nexus 仓库,或者没有被正确地索引。导致引入依赖一直爆红,找不到。只有本地仓库的依赖没报错,因为下载过了,添加新的依赖就需要到远程仓库找就爆红。 解决 去阿里云Maven官网看了一下,发现阿里云…...

设计模式--代理模式
笔记来源:尚硅谷Java设计模式(图解框架源码剖析) 代理模式 1、代理模式的基本介绍 1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象2)这样做的好处是…...

链路聚合原理
文章目录 一、定义二、功能三、负载分担四、分类五、常用命令 首先可以看下思维导图,以便更好的理解接下来的内容。 一、定义 在网络中,端口聚合是一种将连接到同一台交换机的多个物理端口捆绑在一起,形成一个逻辑端口的技术。通过端口聚合&…...

el-table表尾添加合计行,自动合计,且特殊列自定义计算展示
效果如图 1.element-ui的table表格有合计功能,但是功能却不完善,会有不显示和计算出现错误的问题,项目中有遇到,所以记录下 show-summary:自动合计 getSummaries():对合计行进行特…...

uview ui 1.x ActonSheet项太多,设置滚动(亲测有效)
问题:ActionSheet滚动不了。 使用uview ui :u-action-sheet, 但是item太多,超出屏幕了, 查了一下文档,并没有设置滚动的地方。 官方文档:ActionSheet 操作菜单 | uView - 多平台快速开发的UI框架 - uni-a…...

STM32 Cubemx 同名外设中断及回调
文章目录 前言示例工程个人理解 前言 最近在学习STM32,采用HAL库开发方式。记录一下同名外设中断及回调。 这里提及的同名外设指USART1/2之类的相同外设,但不是同一个instance。 示例工程 以使用cubemx配置两个同名外设EXTI0/EXT4为例。 在NVIC配置…...

储能辅助电力系统调峰的容量需求研究(matlab代码)
目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序参考文献《储能辅助电力系统调峰的容量需求研究》,是一个很常规很经典的matlab优化代码,主要是对火电、风电和储能等电力设备主体进行优化调度,在调峰能力达不到时采…...

非计算机科班如何丝滑转码?(本人就是有点不丝滑)
我觉得无非三个办法可以选择(当然可能有其他方法) 自学 报班 有师傅带 但是在学习之前,你一定要明确你学习编程的目的是什么! 游戏开发?后台研发?爬虫工程师?前端程序员?数据分析师? 或者 仅仅是想做一…...

tensorrtx部署yolov5 6.0
文章目录 一. yolov5 v6.0训练模型二.训练好的yolov5模型转tensorrt引擎 一. yolov5 v6.0训练模型 官网下载yolov5 v6.0代码 下载官方预训练好的模型 安装yolov5所需要的库文件,requirements.txt在下载好的yolov5源代码中有 pip install -r C:\Users\10001540…...

用html5写一个音乐播放器
在HTML5中创建一个简单的音乐播放器时,你可以使用<audio>元素来实现。以下是一个基本的示例: html <!DOCTYPE html> <html> <head> <title>音乐播放器</title> </head> <body> <h1>音乐…...

postgresql类型转换函数
postgresql类型转换函数 简介CAST 函数to_date 函数to_timestamp 函数to_char 函数to_number 函数隐式类型转换 简介 类型转换函数用于将数据从一种类型转换为另一种类型。 CAST 函数 CAST ( expr AS data_type )函数用于将 expr 转换为 data_type 数据类型;Post…...

Go 自学:Array阵列
以下代码展示了用两种方法建立array。 package mainimport "fmt"func main() {var fruitList [4]stringfruitList[0] "Apple"fruitList[1] "Tomato"fruitList[3] "Peach"fmt.Println("Fruit list is: ", fruitList)fmt.…...

大数据平台与数据仓库的五大区别
随着大数据的快速发展,很多人难以区分大数据平台与数据仓库的区别,两者傻傻分不清楚。今天我们小编就给大家汇总了大数据平台与数据仓库的五大区别,希望有用哦!仅供参考! 大数据平台与数据仓库的五大区别 一、概念不同…...

React 钩子汇总
React 钩子 一、常用的 React 钩子: 1. useState 用于在函数式组件中添加状态管理。它返回一个状态值和一个更新状态的函数,让你可以在组件中追踪和更新状态。 2. useEffect 用于在组件渲染完成后执行副作用操作,比如数据获取、订阅等。…...

Python爬取旅游网站数据机票酒店价格对比分析
本文将介绍如何使用Python爬虫从旅游网站上获取机票和酒店的价格数据,并实现价格对比分析,帮助你做出明智的旅行决策。我们提供了完善的方案和代码,让你能够轻松操作并获得实际价值。 使用Python爬虫获取旅游网站上的机票和酒店价格数据&…...

OA项目之会议通知(查询是否参会反馈详情)
目录 会议查询 是否参会 反馈详情 讲解思路 会议通知SQL语句分析 反馈详情SQL语句分析 后台代码编写 前端代码编写 效果预览 会议查询 MeetingFeedBack.java package com.zking.oa.model;import org.lisen.mvc.util.AutoIncrement; import org.lisen.mvc.util.…...

如何维护自己的电脑的措施
维护自己的电脑可以采取以下措施: 硬件维护:定期清理电脑表面的灰尘和污垢,避免灰尘对电脑内部部件造成影响。电源插座要保持接触良好,保证电脑的电源稳定。如果使用笔记本电脑,要注意保证散热通畅,避免电…...

VS2022 Community 安装步骤
VS2022 Community 安装步骤(C语言学习) 1. 下载地址2. 安装步骤 1. 下载地址 链接: VS2022 Community下载地址 2. 安装步骤 双击图标进行安装。 点击【继续】后等待安装。 选择需要的安装包并修改安装位置,然后点击【安装】。 点击安装…...

vue3中mitt.js使用
在vue2中我们通过事件总线eventbus,来实现两个平行组件之间的通信: bus.js import Vue from vue // 创建vue实例 const Bus new Vue() export default Bus在具体的组件中: A.vue import Bus from ./bus.js // 发布一个事件 Bus.$emit(sendData, {nam…...

Redis 内存淘汰策略详解
Redis 内存淘汰策略详解 一、简介Redis内存管理问题 二、内存淘汰策略1.为什么需要内存淘汰策略2.内存淘汰策略分类(1)noeviction(2)allkeys-lru(3)allkeys-lfu(4)volatile-lru&…...

初识Redis之分布式
一.简单介绍: Redis是用来在内存中, 存储数据的, 他的初心是用来搞消息中间件(或者说消息队列 很熟悉了吧~~),但是呢用的不多,他现在主要是用来做 数据库,缓存 用来存储数据, 为什么不直接存储呢? Redis的优势就在于分布式系统 二.分布式系统 要说其分布式系统,简单想想都能…...

计算机网络-笔记-第三章-数据链路层
目录 三、第三章——数据链路层 1、数据链路层概述(帧) (1)封装成帧、差错检测、可靠传输(简单介绍) (2)CSMA/CD 2、封装成帧 (1)透明传输(…...

【1】openGL glew示例代码分析绘制一个三角形
openGL文档 > docs.gl ,可以直接查询函数的定义和使用 #include <iostream> #include <string> #include <GL/glew.h> #include <GLFW/glfw3.h>int main(void) {GLFWwindow* window;/* Initialize the library */if (!glfwInit())retu…...

android:新建工程文件介绍
一、前言当我们新建一个app时会呈现出固定的工程文件,这篇文章介绍新建工程里的文件。 二、介绍 Structure:就是你选择哪个页面就会显示那个页面的结构,就比如说我选择的是MainActivity他就会显示这个页面所使用的方法。 1-2:是android自动生…...

强化历程6-网络系列(2023.8.30)
文章目录 强化历程6-网络系列(2023.8.30)1 说一下OSI七层协议,为什么要分层?2 什么是TCP/IP协议,与OSI七层协议两者对比?3 什么是TCP协议,TCP协议和UDP协议区别?4 说一下TCP的三次握手和四次挥手5 两次握手…...