Mybatis-plus的saveBatch()造成雪花ID重复问题解析

news2024/10/7 16:15:53

前言

本文主要是针对Mybatis-plus框架,在调用 saveBatch() 方法时,出现的 id 重复导致的异常报错进行分析,提供后续场景出现相同场景时应该如何定位问题,如何进行调整方案。

问题分析及解决方案

一、场景分析

1、Yaml配置文件

mybatis-plus:
  global-config:
    db-config:
      field-strategy: not_empty
      id-type: auto
      db-type: mysql
      logic-delete-field: deleted # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    banner: false
  configuration:
    map-underscore-to-camel-case: true
    auto-mapping-behavior: full
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath*:mapper/**/*Mapper.xml

配置文件中已经配置ID生成逻辑

2、相关DO类

package com.domain.entity;


import java.util.Date;

@Data
@ToString
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@TableName("bean")
public class BeanDO  {
private static final long serialVersionUID = 227482132234482183L;
    /**
    *业务Id
    */
    @TableId(value = "id",type = IdType.ASSIGN_ID)
    private Long id;

    /**
    *所属机构编码
    */
    @TableField("org_code")
    private String orgCode;

    /**
    *所属机构id
    */
    @TableField("org_id")
    private Long orgId;

    ...

}
从DO Bean中可以看到,开发者已经在bean中加入@TableId(value = "id",type = IdType.ASSIGN_ID)注解,并且指定为mybatis-plus的雪花ID算法生成逻辑,配合YAML的配置,以及可以确定ID的生成逻辑是没有任何问题的。

3、实际执行代码

private void updateAndInsert() {
    //新增集合
    List<BeanDO> insertList = new ArrayList<>();
	// ... 业务代码
    this.saveBatch(insertList);
}

从实际的执行批量保存逻辑来看,调用的方法是在service层,使用this.saveBatch(Collection collection)方法,好像也没有任何问题。
那为什么还是出现了 InstrumentUseBasicInfoMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry ‘1695828558899920911’ for key ‘instrument_use_basic_info.PRIMARY’; 问题的呢?

二、问题分析思路

1、代码诊断步骤:

从上面的配置、bean、执行代码,看似好像一整个逻辑都没有任何问题。好像没有任何的思路,遇到这种问题的时候,别慌!
出现这种场景,可以参考以下几步来确认问题点:
第一步、优先先检查配置文件YAML是否有问题。
第二步、检查自身实现代码逻辑是否存在问题。
第三步、通过其他方式验证雪花ID是否生效。
第四步、以上几种场景都已经确认没有任何问题,那么就开始合理怀疑Mybatis-plus框架是否存在问题。

⚠️注意:作为一个开发者,对任何的框架都不要去盲目信任,认为一定不存在任何问题。所有的框架都是人编写的,既然是人编写的那么也一定会存在BUG,任何的一个框架都是存在BUG的,框架的完善也是不断进步,所以存在即合理。

2、假设出现问题点

1. 怀疑一:是不是@TableId(value = "id",type = IdType.ASSIGN_ID)注解,没有生效呢?
2. 怀疑二:什么场景下,才会导致ID生成重复呢?并发场景?
3. 怀疑三:为什么for + save不会导致ID重复呢?
4. 怀疑四:Mybatis-plus雪花ID生成逻辑,是否与新增数据相关呢?

3、手动还原场景

1、怀疑一:是不是@TableId(value = “id”,type = IdType.ASSIGN_ID)注解,没有生效呢?

本地测试 this.save() 是否自动生成雪花ID

	// 代码自己完善
测试结果:已生效

本地测试 this.saveBatch() 是否自动生成雪花ID

	// 代码自己完善
测试结果:已生效

2、怀疑二:是不是 this.saveBatch() 方法中的对象重复了?

Man man = new Man();
man = Man.builder().age(1).email("@1").name("name1:").build();
list3.add(man);
list3.add(man);
list3.add(man);
manService.saveBatch(list3);
测试结果:ID不重复

3、怀疑三:什么场景下,才会导致ID生成重复呢?并发场景?

public Thread getThread(){
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 100000000;
                for (int j = 0; j < i; j++) {
                    long value = identifierGenerator.nextId(new Object()).longValue();
                    list.add(value);
                    if (set.contains(value)) {
                        System.out.println(value);
                        continue;
                    }
                    set.add(value);
                }
            }
        });
        return thread;
    }
 
 
    public void idTest1() throws InterruptedException {
        getThread().start();
        getThread().start();
        getThread().start();
        getThread().start();
        getThread().start();
        getThread().start();
        getThread().start();
        getThread().start();
        
    }
测试结果:发现ID重复

4、怀疑四:为什么for + save不会导致ID重复呢?

Man man = Man.builder().age(1).email("@1").name("name1:").build();
for (int j = 0; j < i; j++) {
    manService.save(man);
}
测试结果:ID不重复

4、结论

通过以上几种场景验证,可以确定出现ID重复的场景,是在并发下导致雪花ID生成重复。

三、源码分析

1、看看this.save()源码

[图片]

可以看到 this.saveBatch() 调用的方法是 default boolean saveBatch(Collection entityList), 而该方法去调用的 boolean saveBatch(Collection entityList, int batchSize);

[图片]

在public boolean saveBatch(Collection entityList, int batchSize)可以看到,一共执行了两行代码,
第一行,String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE); ,可以根据单词意思,这个是去获取SQL声明,稍微理解一下就是SQL模版;
第二行,这行代码才是我们的关注的重点
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
拆分一下这行代码,sqlSession.insert(sqlStatement, entity) 猜测一下,这段代码,就是根据实体类去替换sql模版里面的值进行新增,所以我们要找的ID部分应该不在这里进行维护;
那就重点看一下,executeBatch() 这个方法
[图片]

好像没啥好看的,接着看里面的代码
[图片]

到这里之后,发现idxLimit,终于看到第一个跟ID相关的单词,分析idxLimit的生成逻辑;可以看到 idxLimit 的逻辑就是一个简单的自增逻辑,并且idxLimit有一个大小限制batchSize;batchSize是一个参数,往回代码找一下,可以确认 batchSize = 1000;
com.baomidou.mybatisplus.extension.service.IService#DEFAULT_BATCH_SIZE

[图片]

到此为止,我们可以先下一个初步的结论:并发场景下,ID重复问题,应该是有由于不同请求进来的时候,ID自增的时候,没有在程序内存中进行同步自增造成的;不同请求下,自增ID的生命周期不同,在自增的逻辑下各干各的,从而导致ID自增重复的问题;

2、Mybatis-plus中ID默认生成器 – DefaultIdentifierGenerator

[图片]

首先,我们来分析一下 DefaultIdentifierGenerator,这里面有一个 sequence.nextId() 方法,进入看一下
[图片]

可以看到,netxId() 中有一个关键字 synchronized,说明这个方法是支持并发的,接下来看一下重点代码
[图片]

这两行代码,可以说是告诉了我们ID的生成逻辑,第一行代码,告诉我们如果时间戳相同,雪花ID则走的是自增逻辑,反着,将序列化进行随机
第二行代码,可以告诉我们每次调用nextId都会去更新 lastTimetamp 常量;

四、问题总结

调用 this.saveBatch() 批量新增出现ID重复问题,原因是 this.saveBatch() 方法不支持并发场景,在并发场景下 this.saveBatch() 与 this.save() 生成雪花ID会导致冲突,因为ID的自增逻辑没有在内存中进行同步,导致并发场景下取到的ID自增初始值可能一样,在自增场景下导致重复;

五、改善方案

取消 @TableId(value = “id”,type = IdType.ASSIGN_ID) 的使用 改成手动生成雪花ID,使用Mybatis-plus中com.baomidou.mybatisplus.core.toolkit.IdWorker#getId(java.lang.Object);

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

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

相关文章

平衡二叉树及其应用详解

平衡二叉树 定义与性质 平衡二叉树&#xff08;Balanced Binary Tree&#xff09;是计算机科学中的一种数据结构&#xff0c;它是二叉排序树的一种特殊情况。 平衡二叉树满足以下性质&#xff1a; 左子树和右子树的高度差不超过 1。也就是说&#xff0c;对于任意节点&#…

TopicExchange主题交换机

目录 一、简介 二、代码展示 父pom文件 pom文件 配置文件 config 生产者 消费者 测试 结果 一、简介 主题交换机&#xff0c;这个交换机其实跟直连交换机流程差不多&#xff0c;但是它的特点就是在它的路由键和绑定键之间是有规则的。 简单地介绍下规则&#xff1…

ABeam×Startup | 德硕管理咨询(深圳)创新研究团队拜访微漾创客空间

近日&#xff0c;德硕管理咨询&#xff08;深圳&#xff09;&#xff08;以下简称&#xff1a;“ABeam-SZ”&#xff09;创新研究团队前往微漾创客空间&#xff08;以下简称&#xff1a;微漾&#xff09;拜访参观&#xff0c;并展开合作交流。会议上&#xff0c;双方相互介绍了…

python爬虫12:实战4

python爬虫12&#xff1a;实战4 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网站产生不好…

高精度的石英可编程压控温补振荡器

高精度的石英可编程压控温补振荡器&#xff1a;YSV531PT系列&#xff0c;七大产品特点&#xff0c;让我们一起来了解下~ 1、Q-MEMS VC-TCXO介绍 什么是石英可编程压控温补振荡器&#xff08;Q-MEMS VC-TCXO&#xff09;&#xff1f; “可编程”顾名思义就是其参数可根据用户…

Javascript——循环调接口

需求&#xff1a;同一个接口每个输入框的code传参数不一样&#xff0c;填一个接口成功后循环♻️调接口 <div class"inching-box-radio-btn"><!-- 启动 --><el-button:disabled"noSecValue true"class"inching-btn inching-open"…

cs231n assignmen3 Extra Credit: Image Captioning with LSTMs

文章目录 嫌墨迹直接看代码Extra Credit: Image Captioning with LSTMslstm_step_forward题面解析代码输出 lstm_step_backward题面解析代码输出 lstm_forward题面解析代码输出 lstm_backward题面解析代码输出 CaptioningRNN.loss解析代码输出 最后输出结语 嫌墨迹直接看代码 …

【ES6】—【新特性】—Symbol详情

一、一种新的原始数据类型 定义&#xff1a;独一无二的字符串 二、 声明方式 1. 无描述声明 let s1 Symbol() let s2 Symbol() console.log(s1, s2) // Symbol() Symbol() console.log(s1 s2) // falsePS: Symbol 声明的值是独一无二的 2. 有描述的声明 let s1 Symb…

Android自定义view实现横向滚动弹幕

参考文章 此方案使用动画方式实现&#xff0c;只适合轻量级别的弹幕滚动效果实现&#xff0c;数据量过大时会出现内存激增的情况。 效果&#xff1a; 自定义view代码 public class TumbleLayout extends ViewGroup {private final String TAG "TumbleLayout";priva…

Camunda 7.x 系列【30】中间事件

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 2.7.9 本系列Camunda 版本 7.19.0 源码地址:https://gitee.com/pearl-organization/camunda-study-demo 文章目录 1. 概述2. 消息中间事件3. 定时器中间事件4. 信号中间事件5. 错误中间事件6. 条件中间事件7…

代码随想录算法训练营第五十天|LeetCode 739,496

目录 LeetCode 739.每日温度 LeetCode 496.下一个更大元素&#xff01; LeetCode 739.每日温度 文章讲解&#xff1a;代码随想录 力扣题目&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 代码如下&#xff08;Java&#xff09;&#xf…

Python“牵手”义乌购商品列表数据,关键词搜索义乌购API接口数据,义乌购API接口申请指南

义乌购平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;义乌购API接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问义乌购平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现义…

国标GB28181视频平台EasyGBS视频监控平台无法播放,抓包返回ICMP排查过程

国标GB28181视频平台EasyGBS是基于国标GB/T28181协议的行业内安防视频流媒体能力平台&#xff0c;可实现的视频功能包括&#xff1a;实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。国标GB28181视频监控平台部署简单、可拓展性强&#xff0c;支持将…

产品经理工作常见的4大误区

产品管理对项目来说非常重要&#xff0c;但在日常工作中&#xff0c;我们往往容易进入思维误区&#xff0c;如果我们没有及时发现错误并进行纠正&#xff0c;这会对产品需求工作以及项目进度产生较大影响。 因此我们需要重视产品工作中常见的思维误区并及时避免&#xff0c;常见…

2023Web自动化测试的技术框架和工具有哪些?

Web 自动化测试是一种自动化测试方式&#xff0c;旨在模拟人工操作对 Web 应用程序进行测试。这种测试方式可以提高测试效率和测试精度&#xff0c;减少人工测试的工作量和测试成本。在 Web 自动化测试中&#xff0c;技术框架和工具起着至关重要的作用。本文将介绍几种常见的 W…

如何让你的交易高效且安全?离不开这项技术

作者&#xff5c;Jason Jiang 在区块链技术演变过程中&#xff0c;有两个关键问题始终绕不过去&#xff1a;隐私与扩容。当我们探寻这两个问题的“标准解法”时&#xff0c;却发现它们都离不开一种技术&#xff0c;那就是&#xff1a;零知识证明。什么是零知识证明&#xff1f…

Dubbo源码环境搭建

背景 Dubbo 作为一款微服务框架&#xff0c;最重要的是向用户提供跨进程的 RPC 远程调用能力。如上图所示&#xff0c;Dubbo 的服务消费者&#xff08;Consumer&#xff09;通过一系列的工作将请求发送给服务提供者&#xff08;Provider&#xff09;。 为了实现这样一个目标&a…

Apipost: 程序员必备的API管理神器

作为一款专为程序员打造的API管理工具&#xff0c;Apipost也成为开发人员圈子里的一款热门工具。Apipost拥有强大的功能和便捷操作性&#xff0c;这也让许多开发者爱不释手。那么&#xff0c;Apipost到底有哪些吸引人的特点呢&#xff1f;本文将为您详细介绍。 统一API管理 A…

【具身智能】论文系列解读-RL-ViGen

1. RL-ViGen&#xff1a;视觉泛化的强化学习基准 RL-ViGen: A Reinforcement Learning Benchmark for Visual Generalization 0 摘要与总结 视觉强化学习&#xff08;Visual RL&#xff09;与高维观察相结合&#xff0c;一直面临着分布外泛化的长期挑战。尽管重点关注旨在解…

[QT]设置程序仅打开一个,再打开就唤醒已打开程序的窗口

需求&#xff1a;speedcrunch 这个软件是开源的计算器软件。配合launch类软件使用时&#xff0c;忘记关闭就经常很多窗口&#xff0c;强迫症&#xff0c;从网上搜索对版本进行了修改。 #include "gui/mainwindow.h"#include <QCoreApplication> #include <…