Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mybatis Plus 3.X版本的insert填充自增id的IdType.ID_WORKER策略源码分析

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

原创
作者头像
朱季谦
发布于 2024-07-09 14:57:20
发布于 2024-07-09 14:57:20
74500
代码可运行
举报
运行总次数:0
代码可运行

文/朱季谦

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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设置以下数种策略——

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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方法——

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void test(){
    UserInfo userInfo = new UserInfo();
    userInfo.setUserName("用户名");
    userInfo.setAge(1);
    userInfoMapper.insert(userInfo);
}

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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方法里——

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

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

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

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

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

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的参数映射和传递。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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接口的对象,至于这个对象是什么,可以接着往下去。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法里。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,相当还没有值。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Nature子刊:人类认知控制的闭环增强和神经解码
在抑郁、焦虑、成瘾和其他精神障碍中,普遍存在认知控制缺陷的能力——即抑制默认的有效反应以支持更具适应性的选择。在这里,我们报告了概念验证证据,在接受颅内癫痫监测的患者中,闭环直接刺激内囊或纹状体,特别是背侧部位,增强了患者在冲突任务中的认知控制。我们还表明,闭环刺激在认知控制产生失误的检测比开环刺激更大的行为变化,和单任务性能试验可以直接解码来自神经特性兼容现有的闭环大脑植入的少量电极活动。认知控制的闭环增强可能纠正潜在的认知缺陷,并帮助严重精神障碍的治疗。
悦影科技
2023/07/22
6380
忆阻器玩Atari游戏
Single Neuromorphic Memristor closely Emulates Multiple Synaptic Mechanisms for Energy Efficient Neural Networks
CreateAMind
2024/04/26
1660
忆阻器玩Atari游戏
统计学学术速递[6.24]
【1】 Analysis of the evolution of agroclimatic risks in a context of climate variability in the region of Segou in Mali 标题:马里塞古地区气候多变性背景下农业气候风险演变分析
公众号-arXiv每日学术速递
2021/07/02
9570
Python 人工智能:1~5
在本章中,我们将讨论人工智能(AI)的概念及其在现实世界中的应用。 我们在日常生活中花费了大量时间与智能系统进行交互。 这可以采取以下形式:在互联网上搜索某些内容,进行生物特征识别的人脸识别或将口语单词转换为文本。 人工智能是这一切的核心,它正在成为我们现代生活方式的重要组成部分。 所有这些系统都是复杂的实际应用,而 AI 通过数学和算法解决了这些问题。 在整本书中,我们将学习可用于构建此类应用的基本原理。 我们的总体目标是使您能够应对日常生活中可能遇到的具有挑战性的新 AI 问题。
ApacheCN_飞龙
2023/04/23
9650
万字长文:AI陪伴产品的终极解法?
导语|此篇文章是本人及多名朋友联合 产品人Super黄 共同创作的深度长文,欢迎对这个话题感兴趣的各位“浩浩爸”们疯狂戳戳俺,一起讨论交流~
腾讯大讲堂
2024/01/03
1.1K0
万字长文:AI陪伴产品的终极解法?
2020-2021 设计趋势ISUX报告 · 年轻文化篇
前言 很难界定千禧一代(生于20世纪80年代至2000年初)和 z 世代(生于1995年以后) ,但那些已成为当前消费市场中心的人与上一代有着不同的价值观和文化品味。他们表现出的消费特征是强调个人幸福而非群体,分享而非持有,体验而非产品。他们倾向于通过购买包含社会价值观和信息的东西来表达自己的信念,而不仅仅是单纯地购买。他们也喜欢那种展示自己成功或财富的文化,并且乐于投资昂贵的奢侈品。与老一辈为了一个遥远的未来而牺牲了现在不同,他们关注的是现在。 It is difficult to defin
腾讯ISUX
2020/09/14
1.1K0
重磅:人工智能产业深度研究报告
作者:马仁敏,周焕 来源:华泰证券、格灵深瞳 人工智能相关文章 院士李德毅:大数据认知(演讲全文) [重磅]百度研究院副院长余凯:大数据与人工智能(41PPT) 技术不足导致移动互联网难以催生出更多的新应用和商业模式,为突破瓶颈,新一轮更激动人心、更值得期待的技术革命风暴已经诞生,将成为未来10年乃至更长时间内IT产业发展的焦点,它的名字叫做“人工智能”(AI)。 只有人工智能才能为“万物互联”之后的应用问题提供最完美的解决方案,它将成为IT领域最重要的技术革命,目前市场关心的IT和互联网领域的几乎所有主题
大数据文摘
2018/05/21
1.6K0
人工智能万亿市场待挖掘
发轫于2007年的移动互联网浪潮已经席卷全球,极大地改变了我们的生存状态。然而,就在资本市场热切地期待移动互联网催生出更多新应用服务、更多新商业模式的时候,由技术水平不足导致的发展瓶颈已然出现。与此同时,为突破上述瓶颈,新一轮更激动人心、更值得期待的技术革命风暴已经诞生,将成为未来10年乃至更长时间内IT产业发展的焦点,将再次并更加彻底地颠覆世界。这一轮技术革命风暴,它的名字叫做“人工智能”(Artificial Intelligence,以下简称AI)。
全栈程序员站长
2022/06/25
1.6K0
人工智能万亿市场待挖掘
机器学习学术速递[12.16]
【1】 Model Stealing Attacks Against Inductive Graph Neural Networks 标题:针对归纳图神经网络的模型窃取攻击 链接:https://arxiv.org/abs/2112.08331
公众号-arXiv每日学术速递
2021/12/17
1.2K0
机器学习学术速递[12.7]
【1】 Distance and Hop-wise Structures Encoding Enhanced Graph Attention Networks 标题:增强型图注意网络的距离和跳数结构编码 链接:https://arxiv.org/abs/2112.02868
公众号-arXiv每日学术速递
2021/12/09
1.2K0
机器学习学术速递[9.10]
【1】 fGOT: Graph Distances based on Filters and Optimal Transport 标题:fGOT:基于滤波器和最优传输的图距离 链接:https://arxiv.org/abs/2109.04442
公众号-arXiv每日学术速递
2021/09/16
1.9K0
机器学习学术速递[9.7]
【1】 Knowledge Graph Enhanced Event Extraction in Financial Documents 标题:知识图增强的金融文档事件抽取 链接:https://arxiv.org/abs/2109.02592
公众号-arXiv每日学术速递
2021/09/16
9950
计算机视觉与模式识别学术速递[12.7]
【1】 DoodleFormer: Creative Sketch Drawing with Transformers 标题:DoodleFormer:用Transformer创作素描 链接:https://arxiv.org/abs/2112.03258
公众号-arXiv每日学术速递
2021/12/09
1.3K0
计算机视觉学术速递[6.30]
【1】 Unified Questioner Transformer for Descriptive Question Generation in Goal-Oriented Visual Dialogue 标题:面向目标的视觉对话中描述性问题生成的统一提问器转换器
公众号-arXiv每日学术速递
2021/07/02
2.5K0
JavaSE 编写第一个程序
介绍 JavaSE 基础的基本语法知识,不会包含特别难以理解或更深层次的内容,通俗易懂。本人是实战派,看着大幅篇章晦涩的理论,但是没有多少实践证明的书籍就头疼;同时如果知识东一点、西一点,跳跃性太大,不成体系,也比较麻烦。
全栈程序员站长
2022/09/14
6.7K0
JavaSE 编写第一个程序
最全面、最详细web前端面试题及答案总结
本章是HTML考点的⾮重难点,因此我们采⽤简略回答的⽅式进⾏撰写,所以不会有太多详细的解释。我们约定,每个问题后我们标记『✨ 』的为⾼频⾯试题 doctype的作⽤是什么?✨ DOCTYPE是html5标准⽹⻚声明,且必须声明在HTML⽂档的第⼀⾏。来告知浏览器的解析器⽤什么⽂档标准解析这个 ⽂档,不同的渲染模式会影响到浏览器对于 CSS 代码甚⾄ JavaScript 脚本的解析 ⽂档解析类型有: BackCompat:怪异模式,浏览器使⽤⾃⼰的怪异模式解析渲染⻚⾯。(如果没有声明DOCTYPE,默认就是这个模式) CSS1Compat:标准模式,浏览器使⽤W3C的标准解析渲染⻚⾯。 IE8还有⼀种介乎于上述两者之间的近乎标准的模式,但是基本淘汰了。
全栈程序员站长
2022/08/01
8.3K0
相关推荐
Nature子刊:人类认知控制的闭环增强和神经解码
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验