Mybatis Plus 3.X版本的insert填充自增id的IdType.ID_WORKER策略源码分析

news2024/11/13 9:29:48

总结/朱季谦

某天同事突然问我,你知道Mybatis Plus的insert方法,插入数据后自增id是如何自增的吗?

我愣了一下,脑海里只想到,当在POJO类的id设置一个自增策略后,例如@TableId(value = "id",type = IdType.ID_WORKER)的注解策略时,就能实现在每次数据插入数据库时,实现id的自增,例如以下形式——

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "用户对象")
@TableName("user_info")
public class UserInfo {
    @ApiModelProperty(value = "用户ID", name = "id")
    @TableId(value = "id",type = IdType.ID_WORKER)
    private Integer id;
    @ApiModelProperty(value = "用户姓名", name = "userName")
    private String userName;
    @ApiModelProperty(value = "用户年龄", name = "age")
    private int age;
}

但是,说实话,我一直都没能理解,这个注解策略实现id自增的底层原理究竟是怎样的?

带着这样的疑惑,我开始研究了一番Mybatis Plus的insert自增id的策略源码,并将其写成了本文。

先来看一下Mybatis Plus生成id的自增策略,可以通过枚举IdType设置以下数种策略——

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型
     */
    NONE(1),
    /**
     * 用户输入ID
     * 该类型可以通过自己注册自动填充插件进行填充
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 全局唯一ID (idWorker)
     */
    ID_WORKER(3),
    /**
     * 全局唯一ID (UUID)
     */
    UUID(4),
    /**
     * 字符串全局唯一ID (idWorker 的字符串表示)
     */
    ID_WORKER_STR(5);
    
    ......
}

每个字段都有各自含义,说明如下:

  1. AUTO(0): 用于数据库ID自增的策略,主要用于数据库表的主键,在插入数据时,数据库会自动为新插入的记录分配一个唯一递增ID。
  2. NONE(1): 表示未设置主键类型,存在某些情况下不需要主键,或者主键由其他方式生成。
  3. INPUT(2): 表示用户输入ID,允许用户自行指定ID值,例如前端传过来的对象id=1,就会根据该自行定义的id=1当作ID值;
  4. ID_WORKER(3): 表示全局唯一ID,使用的是idWorker算法生成的ID,这是一种雪花算法的改进。
  5. UUID(4): 表示全局唯一ID,使用的是UUID(Universally Unique Identifier)算法。
  6. ID_WORKER_STR(5): 表示字符串形式的全局唯一ID,这是idWorker生成的ID的字符串表示形式,便于在需要字符串ID的场景下使用。

接下来,让我们跟着源码看一下,究竟是如何基于这些ID策略做id自增的,本文主要以ID_WORKER(3)策略id来追踪。

先从插入insert方法开始。

基于前文创建的UserInfo类,我们写一个test的方法,用于追踪insert方法——

@Test
public void test(){
    UserInfo userInfo = new UserInfo();
    userInfo.setUserName("用户名");
    userInfo.setAge(1);
    userInfoMapper.insert(userInfo);
}

可以看到,此时的id=0,还没有任何值——

image

执行到insert的时候,底层会执行一个动态代理,最终通过动态代理,执行DefaultSqlSession类的insert方法,可以看到,insert方法里,最终调用的是一个update方法。

image

在mybatis中,无论是新增insert或者更新update,其底层都是统一调用DefaultSqlSession的update方法——

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

执行到executor.update(ms, wrapCollection(parameter))方法时,会跳转到BaseExecutor的update方法里——

image

这里的BaseExecutor是mybatis的核心组件,它是Executor 接口的一个具体实现,提供了实际数据的增删改查操作功能。在 MyBatis 中,基于BaseExecutor扩展了以下三种基本执行器类:

  1. SimpleExecutor:这是最简单的执行器类型,它对每个数据库CURD操作都创建一个新的 Statement 对象。如果应用程序执行大量的数据库操作,这种类型的执行器可能会产生大量的开销,因为它不支持 Statement 重用。
  2. ReuseExecutor:这种执行器类型会尝试重用 Statement 对象。它在处理多个数据库操作时,会尝试使用相同的 Statement 对象,从而减少创建 Statement 对象的次数,提高性能。
  3. BatchExecutor:这种执行器类型用于批量操作,它会在内部缓存所有的更新操作,然后在适当的时候一次性执行它们,适合批量插入或更新操作的场景,可以显著提高性能。

除了这三种基本的执行器类型,MyBatis 还提供了其他一些执行器,这里暂时不展开讨论。

在本文中,执行到doUpdate(ms, parameter)时,会默认跳转到SimpleExecutor执行器的doUpdate方法里。注意我标注出来的这两行代码,自动填充插入ID策略的逻辑,就是在这两行代码当中——

image

先来看第一行代码,从类名就可以看出,这里创建里一个实现StatementHandler接口的对象,这个StatementHandler接口专门用来处理SQL语句的接口。从这里就可以看出,通过创建这个对象,可以专门用来处理SQL相关语句操作,例如,对参数的设置,更具体一点,可以对参数id进行自定义设置等功能。

实现StatementHandler接口有很多类,那么,具体需要创建哪个对象呢?

跟着代码一定进入到RoutingStatementHandler类的RoutingStatementHandler方法当中,可以看到,这里有一个switch,debug到这一步,最终创建的是一个PreparedStatementHandler对象——

image

进入到PreparedStatementHandler方法当中,可以看到会通过super调用创建其父类的构造器方法——

public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

从super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql)方法进去,到父类的BaseStatementHandler里,这里面有一行很关键的代码 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql),这是一个MyBatis内部的接口或实现类的实例,用于处理SQL的参数映射和传递。

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;

  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();

  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }

  this.boundSql = boundSql;

  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

进入到configuration.newParameterHandler(mappedStatement, parameterObject, boundSql)代码里,可以看到这里通过createParameterHandler方法创建一个实现ParameterHandler接口的对象,至于这个对象是什么,可以接着往下去。

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
  return parameterHandler;
}

最终来到MybatisXMLLanguageDriver类的createParameterHandler方法,可以看到,创建的这个实现ParameterHandler接口的对象,是这个MybatisDefaultParameterHandler。

public class MybatisXMLLanguageDriver extends XMLLanguageDriver {

    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject,
                                                   BoundSql boundSql) {
        /* 使用自定义 ParameterHandler */
        return new MybatisDefaultParameterHandler(mappedStatement, parameterObject, boundSql);
    }
}

继续跟进去,可以看到构造方法里,有一个processBatch(mappedStatement, parameterObject)方法,我们要找的填充自增id的IdType.ID_WORKER策略实现,其实就在这个processBatch方法里。

public MybatisDefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    super(mappedStatement, processBatch(mappedStatement, parameterObject), boundSql);
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
}

至于processBatch(mappedStatement, parameterObject)中的两个参数分别是什么,debug就知道了,mappedStatement是一个存储执行语句相关的Statement对象,而parameterObject则是需要插入数据库的对象数据,此时id仍然是默认0,相当还没有值。

image

继续往下debug,因为是insert语句,故而会进入到ms.getSqlCommandType() == SqlCommandType.INSERT方法里,将isFill赋值true,isInsert赋值true,这两个分别表示是否需要填充以及是否插入。由此可见,它将会执行if (isFill) {}里的逻辑——

image

在if(isFill)方法当中,最重要的是populateKeys(metaObjectHandler, tableInfo, ms, parameterObject, isInsert);这个方法,这个方法就是根据不同的id策略,去生成不同的id值,然后填充到id字段里,最终插入到数据库当中。而我们要找的最终方法,正是在这里面——

protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                     MappedStatement ms, Object parameterObject, boolean isInsert) {
    if (null == tableInfo) {
        /* 不处理 */
        return parameterObject;
    }
    /* 自定义元对象填充控制器 */
    MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
    // 填充主键
    if (isInsert && !StringUtils.isEmpty(tableInfo.getKeyProperty())
        && null != tableInfo.getIdType() && tableInfo.getIdType().getKey() >= 3) {
        Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
        /* 自定义 ID */
        if (StringUtils.checkValNull(idValue)) {
            if (tableInfo.getIdType() == IdType.ID_WORKER) {
                metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
            } else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
                metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
            } else if (tableInfo.getIdType() == IdType.UUID) {
                metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
            }
        }
    }
    if (metaObjectHandler != null) {
        if (isInsert && metaObjectHandler.openInsertFill()) {
            // 插入填充
            metaObjectHandler.insertFill(metaObject);
        } else if (!isInsert) {
            // 更新填充
            metaObjectHandler.updateFill(metaObject);
        }
    }
    return metaObject.getOriginalObject();
}

例如,我们设置的id策略是这个 @TableId(value = "id",type = IdType.ID_WORKER),当代码执行到populateKeys方法里时,就会判断是否为 IdType.ID_WORKER策略,如果是,就会执行对应的生存id的方法。这里的IdWorker.getId()就是获取一个唯一ID,然后赋值给tableInfo.getKeyProperty(),这个tableInfo.getKeyProperty()正是user_info的对象id。

image

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

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

相关文章

Linux多进程和多线程(八)多线程

多线程 线程定义线程与进程线程资源 线程相关命令 pidstat 命令 top 命令ps 命令常见的并发方案 1. 多进程模式2. 多线程模式 创建线程 1. pthread_create() 示例:创建一个线程 2. pthread_exit() 退出线程3. pthread_join() 等待线程结束 示例: 线程分离 创建多个线程 示例 1:…

Spring Boot集成grpc快速入门demo

1.什么是GRPC? gRPC 是一个高性能、开源、通用的RPC框架,由Google推出,基于HTTP2协议标准设计开发,默认采用Protocol Buffers数据序列化协议,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务&#xff0c…

VUE之旅—day3

工程化开发和脚手架Vue CLI 开发Vue的两种方式: 核心包创痛开发模式:基于html/css/js文件,直接引入核心包,开发Vue。 工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue。…

『大模型笔记』GraphRAG:利用复杂信息进行发现的新方法!

GraphRAG:利用复杂信息进行发现的新方法! 文章目录 一. GraphRAG:利用复杂信息进行发现的新方法!1. 将RAG应用于私人数据集2. 整个数据集的推理3. 创建LLM生成的知识图谱4. 结果指标5. 下一步二. 参考文献微软官方推文:https://www.microsoft.com/en-us/research/blog/gra…

招投标信息采集系统:让您的企业始终站在行业前沿

一、为何招投标信息如此关键? 在经济全球化的大背景下,招投标活动日益频繁,成为企业获取项目、拓展市场的主流方式之一。招投标信息采集,作为企业战略决策的前置环节,其重要性不言而喻。它不仅关乎企业能否第一时间发…

实时消息推送系统,写得太好了!

websocket 协议是在 http 协议上的一种补充协议,是 html5 的新特性,是一种持久化的协议。其实 websocket 和 http 关系并不是很大,不过都是属于应用层的协议,接下来我们就开始实战。 websocket 定时推送 本教程基于 springboot …

如何解决群晖Docker注册表查询失败/无法拉取镜像等问题

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 问题概述 📒📒 解决方案 📒🔖 方法一🔖 方法二🔖 方法三⚓️ 相关链接 🚓️📖 介绍 📖 在群晖(Synology)NAS设备上使用Docker时,我们可能会遇到查询Docker注册表失败,无法拉取Docker镜像的问题。这种情况…

一文理解 Treelite,Treelite 为决策树集成模型的部署和推理提供了高效、灵活的解决方案

🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、什么是 Treelite? Treelite 是一个专门用于将决策树集成模型高效部署到生产环境中的机器学习模型编译器,特别适合处理大批量数据的推理任务,能够显著提升推理性能…

《RWKV》论文笔记

原文出处 [2305.13048] RWKV: Reinventing RNNs for the Transformer Era (arxiv.org) 原文笔记 What RWKV(RawKuv):Reinventing RNNs for the Transformer Era 本文贡献如下: 提出了 RWKV 网络架构,结合了RNNS 和Transformer 的优点,同…

vscode调试教程

VSCode调试 VSCode Debuggers VSCode使用launch.json进行细粒度的控制,可以启动程序或将其附加到复杂的调试场景中 打开Run and Debug视图Ctrl Shift D 点击create a launch.json file,选择C(GDB/LLDB) 会在工作目录自动创建.vscode/launch.json文…

单对以太网连接器多场景应用

单对以太网连接器应用场景概述 单对以太网(Single Pair Ethernet,简称SPE)作为一种新兴的以太网技术,以其独特的优势在多个领域得到了广泛的应用。SPE通过单对电缆进行数据传输,支持高速数据传输,同时还能…

Python学习笔记33:进阶篇(二十二)pygame的使用之image模块

前言 基础模块的知识通过这么长时间的学习已经有所了解,更加深入的话需要通过完成各种项目,在这个过程中逐渐学习,成长。 我们的下一步目标是完成python crash course中的外星人入侵项目,这是一个2D游戏项目。在这之前&#xff…

文献解读-基准与方法研究-第十六期|《GeneMind 公司的 GenoLab M 测序平台 WGS 和 WES 数据基准测试》

关键词:基准与方法研究;基因测序;变异检测; 文献简介 标题(英文):Accuracy benchmark of the GeneMind GenoLab M sequencing platform for WGS and WES analysis标题(中文&#xf…

无法连接Linux远程服务器的Mysql,解决办法

问题描述 如果是关闭虚拟机之后,二次打开无法连接Mysql,则可尝试一下方法进行解决 解决方法 关闭虚拟机的防火墙 1:查看防火墙状态 systemctl status firewalld 一下显示说明防火墙是启动的状态 2:关闭防火墙 systemctl st…

Java线程死锁及解决方法

多线程环境下,死锁即两个或两个以上的线程去争夺同一个共享资源,而导致互相等待的情况。 要产生死锁,必须满足如下四个条件: 互斥条件,共享资源x和y只能被一个线程占有请求和保持条件,T1持有x&#xff0c…

7.9数据结构

思维导图 作业 doubleloop.h #ifndef __DOUBLELOOP_H__ #define __DOUBLELOOP_H__#include <stdio.h> #include <stdlib.h>typedef int datatype; typedef struct node {union{int len;datatype data;};struct node *pri;//前驱指针struct node *next;//后继指针…

Docker安装BRIA-RMBG-1.4模型,背景去除

目录 前言 模型描述 训练数据 定性评估 docker安装 运行 结论 Tip&#xff1a; 问题1&#xff1a; 问题2&#xff1a; 前言 BRIA 背景去除 v1.4 模型 RMBG v1.4 是我们最先进的背景去除模型&#xff0c;旨在有效地将各种类别和图像类型的前景与背景分开。该模型已在…

springboot会议室管理系统-计算机毕业设计源码50331

目 录 摘要 1 绪论 1.1 开发背景与意义 1.2国内外研究现状 1.3 相关技术、工具简介 1.3.1 MySQL数据库的介绍 1.3.2 B/S架构的介绍 1.3.3 Java语言 1.3.4 SpringBoot框架 1.4论文结构与章节安排 2 会议室管理系统需求分析 2.1 可行性分析 2.1.1 技术可行性分析 2…

昇思25天学习打卡营第21天|LSTM+CRF序列标注

1. 学习内容复盘 概述 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。以命名…

一文实践强化学习训练游戏ai--doom枪战游戏实践

一文实践强化学习训练游戏ai–doom枪战游戏实践 上次文章写道下载doom的环境并尝试了简单的操作&#xff0c;这次让我们来进行对象化和训练、验证&#xff0c;如果你有基础&#xff0c;可以直接阅读本文&#xff0c;不然请你先阅读Doom基础知识&#xff0c;其中包含了下载、动作…