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

总结/朱季谦

某天同事突然问我,你知道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 statementgenerateKeys(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 {@Overridepublic 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.mzph.cn/pingmian/43833.shtml

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

相关文章

Git 命令行快速入门

前言 (1)新手个人建议使用TortoiseGit这类图形化界面来上手学习。 (2)如果一定需要用命令行进行操作,可以按照B站:程式与网页开发者必备技能!Git 和 GitHub 零基础快速上手,轻松掌握…

干货:高水平论文写作思路与方法

前言:Hello大家好,我是小哥谈。高水平论文的写作需要扎实的研究基础和严谨的思维方式。同时,良好的写作技巧和时间管理也是成功的关键。本篇文章转载自行业领域专家所写的一篇文章,希望大家阅读后可以能够有所收获。🌈 目录 🚀1.依托事实/证据,通过合理的逻辑,…

汇川CodeSysPLC教程03-2-14 与HMI通信

硬件连接 PLC与HMI连接采用何种连接方式,通常是参考双方支持哪些接口。PLC(可编程逻辑控制器)与HMI(人机界面)之间的通讯方式主要有以下几种: 串行通讯(Serial Communication)&…

Git本地仓库的搭建与使用

目录 一、前言 二、Linux下搭建 git 仓库 三、Windows下搭建 git 仓库 一、前言 做项目时,我们常常需要将自己的代码进行托管,但有时候 Github 的速度属实叫人流泪。有的人会选择 Gitee 等进行托管代码,这当然是可以的。那如果没有其他代码…

前端图表库G2快速上手

文档地址&#xff1a; https://g2-v3.antv.vision/zh/docs/manual/getting-started/ https://g2.antv.antgroup.com/ 安装&#xff1a; pnpm i antv/g2在vue3中使用&#xff1a; <script setup> import {Chart} from antv/g2; import {onMounted} from "vue"…

LeetCode(2)-反转链表、删除链表中等于val的节点、返回链表中的中间节点

一、反转链表 . - 力扣&#xff08;LeetCode&#xff09; 解法1&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ typedef struct ListNode ListNode; struct ListNode* reverseList(struct ListN…

MySQL架构优化及SQL优化

变更项目的整体架构是性能收益最大的方式。主要涉及两方面&#xff0c;一方面是从整个项目角度&#xff0c;引入一些中间件优化整体性能&#xff0c;另一方面是调整MySQL的部署架构&#xff0c;确保能承载更大的流量访问&#xff0c;提高数据层的整体吞吐。 1. 引入缓存中间件…

手把手教你玩转AD9361数字调制解调系列(四) ----纯PL逻辑实现QPSK信号的数字调制解调

因最近客户需求&#xff0c;用纯PL实现AD9361的数字信号调制解调&#xff0c;于是就把各种数字调制都在AD9361上都实现了一遍。 优点就是&#xff1a;既可以在zynq系列上配置9361&#xff0c;也可以在纯FPGA系列配置9361。并且理解起来比较简单&#xff01;&#xff01;&#…

推荐4款免费好用文本转语音工具

Edge文本转语音 Edge文本转语音功能主要通过Edge-TTS实现。Edge-TTS是由微软开发的文本转语音&#xff08;TTS&#xff09;Python库&#xff0c;利用微软Azure Cognitive Services的强大功能&#xff0c;能够将文本信息转换成流畅自然的语音输出。该库支持多种中文语音语色&…

PID控制与模糊PID控制的比较

一、PID控制器的设计 1.PID控制原理图&#xff1a; PID控制其结构框图如下图所示&#xff1a; 图1&#xff1a;PID控制器结构框图 2.PID控制器传递函数的一般表达式 PID控制器传递函数的一般表达形式为&#xff1a; 其中kp为比例增益&#xff1b;ki为积分增益&#xff1b;k…

《大语言模型的临床和外科应用:系统综述》

这篇题为《大语言模型的临床和外科应用&#xff1a;系统综述》的文章对大语言模型&#xff08;LLM&#xff09;目前在临床和外科环境中的应用情况进行了全面评估。 大语言模型&#xff08;LLM&#xff09;是一种先进的人工智能系统&#xff0c;可以理解和生成类似人类的文本。…

【YOLO8系列】(二)YOLOv8环境配置,手把手嘴对嘴保姆教学

目录 一. 准备环境 1.Anaconda下载 2.创建yolov8虚拟环境 3.pytorch安装 4.CUDA下载 5.CUDNN下载 二、yolov8模型下载 1.clone模型 2.pycharm配置 ①解释器配置 ②终端配置 3.安装必要库 4.下载训练模型 三、 环境验证 四、总结 YOLOv8 是 YOLO 系列最新的目标…

兼容性报错--调整字符集解决

文章目录 错误解决办法Unicode 字符集(两个字节来表示一个字符)多字节字符集(一个字节来表示一个字符)如何选择字符集char与wchar_t的区别LPCSTR与LPCWSTR的区别 错误 解决办法 切换字符集类型 Unicode 字符集(两个字节来表示一个字符) 优点&#xff1a; 支持更多的字符集…

Linux|背景 环境搭建

目录 一、简述Linux发展史 1.1计算机的诞生 1.2操作系统的诞生 1.3Linux操作系统开源 1.4Linux发行版本 二、搭建Linux环境 三、使用shell远程登入到Linux 一、简述Linux发展史 可能大家未听说过Linux&#xff0c;或者只知道它是一个搭配在计算机上的操作系统&#xff0…

Studio One直播声音怎么调 Studio One直播没有声音输出怎么办 studio one如何设置声音变好听

Studio One做为新生代音乐工作站&#xff0c;凭借更低的价格和完备的功能&#xff0c;获得了音乐人和直播行业工作者的青睐&#xff0c;尤其是对硬件声卡的适配支持更好&#xff0c;特别适合用来配合线上教学和电商带货。 一、Studio One直播声音怎么调 在Studio One进行直播时…

C基础day7

一、思维导图 二、课后练习 1、提示并输入一个字符串&#xff0c;统计该字符串中字母、数字、空格以及其他字符的个数 #include<myhead.h> #define M 20 int main(int argc, const char *argv[]) {int sum_a0,sum_b0,sum_c0,sum_d0;char str[M];printf("please en…

鼠标点击触发-----以控制开灯、宝箱触发为例

开灯 当点击时触发开灯效果 &#xff08;不用设置触发器&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class OpenLight : MonoBehaviour {public Transform light;bool isOpen;private void OnMouseDown(){if (!isOpe…

Hospital Management Startup 1.0 SQL 注入漏洞(CVE-2022-23366)

前言 CVE-2022-23366是一个影响HMS v1.0的SQL注入漏洞。该漏洞存在于patientlogin.php文件中&#xff0c;允许攻击者通过特定的SQL注入来获取或修改数据库中的敏感信息。 具体来说&#xff0c;攻击者可以通过向patientlogin.php发送恶意构造的SQL语句来绕过身份验证&#xff…

Joblib 是一个专注于高效计算和数据持久化的 Python 库

目录 01Joblib 是什么&#xff1f; 为什么选择 Joblib&#xff1f; 安装与配置 02Joblib 的基本用法 并行计算 数据持久化 03实战案例 项目简介 项目结构 依赖安装 应用代码 运行应用 …