当前位置: 首页 > news >正文

事务方法中保证数据只插入一次方案探究

需求场景

在项目的接口请求中,我们有一个接口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分布式锁来解决消息并发消费的问题,保证分布式环境下,消费消息的同步性。同时可以根据业务逻辑丢弃消息。

相关文章:

事务方法中保证数据只插入一次方案探究

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

高通开发系列 - 5G网络之QTI守护进程服务介绍

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

Ansible学习笔记3

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

DP读书:鲲鹏处理器 架构与编程(十)鲲鹏软件生态与云服务

十秒带你了解鲲鹏软件生态与云服务 鲲鹏软件生态与云服务ARM授权机制在传统的PC领域&#xff0c;半导体厂商的业务类型主要分为两种&#xff1a;在移动领域&#xff0c; 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 仓库&#xff0c;或者没有被正确地索引。导致引入依赖一直爆红&#xff0c;找不到。只有本地仓库的依赖没报错&#xff0c;因为下载过了&#xff0c;添加新的依赖就需要到远程仓库找就爆红。 解决 去阿里云Maven官网看了一下&#xff0c;发现阿里云…...

设计模式--代理模式

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

链路聚合原理

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

el-table表尾添加合计行,自动合计,且特殊列自定义计算展示

效果如图 1.element-ui的table表格有合计功能&#xff0c;但是功能却不完善&#xff0c;会有不显示和计算出现错误的问题&#xff0c;项目中有遇到&#xff0c;所以记录下 show-summary&#xff1a;自动合计 getSummaries&#xff08;&#xff09;&#xff1a;对合计行进行特…...

uview ui 1.x ActonSheet项太多,设置滚动(亲测有效)

问题&#xff1a;ActionSheet滚动不了。 使用uview ui &#xff1a;u-action-sheet, 但是item太多&#xff0c;超出屏幕了&#xff0c; 查了一下文档&#xff0c;并没有设置滚动的地方。 官方文档&#xff1a;ActionSheet 操作菜单 | uView - 多平台快速开发的UI框架 - uni-a…...

STM32 Cubemx 同名外设中断及回调

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

储能辅助电力系统调峰的容量需求研究(matlab代码)

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

非计算机科班如何丝滑转码?(本人就是有点不丝滑)

我觉得无非三个办法可以选择(当然可能有其他方法) 自学 报班 有师傅带 但是在学习之前&#xff0c;你一定要明确你学习编程的目的是什么&#xff01; 游戏开发&#xff1f;后台研发&#xff1f;爬虫工程师&#xff1f;前端程序员?数据分析师&#xff1f; 或者 仅仅是想做一…...

tensorrtx部署yolov5 6.0

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

用html5写一个音乐播放器

在HTML5中创建一个简单的音乐播放器时&#xff0c;你可以使用<audio>元素来实现。以下是一个基本的示例&#xff1a; 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 数据类型&#xff1b;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.…...

大数据平台与数据仓库的五大区别

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

React 钩子汇总

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

Python爬取旅游网站数据机票酒店价格对比分析

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

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...