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

news/2024/11/18 10:42:42/文章来源:https://www.cnblogs.com/zhujiqian/p/18292901

总结/朱季谦

某天同事突然问我,你知道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.hqwc.cn/news/741374.html

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

相关文章

共享ip服务器实现外网访问

信息 服务器厂商:炎火云 系统:Windows-2012R2-Datacenter-cn 面板:phpstudy 共享ip服务器实现外网访问思路 通过nat端口映射将内网端口映射到共享ip(即公网ip)端口,实现使用共享ip+端口访问服务器 步骤 首先确定好服务器端口 我这里用的是默认的80端口接着要在防火墙里面…

MySQL-18.主从复制

C-18.主从复制 1.主从复制概述1.1 如何提升数据库并发能力 在实际工作中,我们常常将Redis作为缓存与MySQL配合来使用,当有请求的时候,首先会从缓存中进行查找,如果存在就直接取出。如果不存在再方法数据库,这样就提升了读取的效率,也减少了对后端数据库的访问压力。Redis…

Jetbrains IDE (IntelliJ) 启用原生Wayland支持

启用最新jbr 打开你要设置的IDE,ctrl+shift+a 后输入“runtine”,回车,在显示的窗口中选择jbr21的最新版本,如图所示:设置jvm参数 确认后,点击窗口右下角的小齿轮(如果已经打开了任何项目,先关掉),选择“Edit Custom VM Options“,在出现的文本框最后面加上这么一行:…

30、Django-项目部署-nginx

原理: 安装: 配置: - 这里uwsgi_pass 表示使用uwsgi协议转发代理 - include 表示加载uwsgi协议的参数(固定) - nginx -t   #检查配置文件语法修改uWSGI:socket 表示启用uwsgi协议 本文来自博客园,作者:little小新,转载请注明原文链接:https://www.cnblogs.com/li…

15、 Django-多表操作-多个模块的关联-多对多的增删改查- models.manytomany()

针对多对多的关系django会自动创建第三张表、也可以通过through参数指定第三张表models.py from django.db import models# Create your models here.#多对多 #用户表:电影 = N:M #一个用户可以收藏多部电影 #一部电影可以被不同的用户收藏#电影 class Movie(models.Mode…

14、 Django-多表操作-多个模块的关联-一对多的增删改查- models.ForeignKey()

#多模块关联-- 关联分类:Django中的三个函数-- ForeignKey-称为外键:一对多、将字段定义在多的一端中-- ManyToMnayField:多对多、将字段定义在两端的任意一端中-- OneToOneField:一对一、将字段定义在任意一端中如:一对一:一对多:多对多:常用 如: 模型models.py from …

一文熟悉拖拽式表单设计器的方方面面

通过本文,可以详细了解拖拽式表单设计器的更多优势和特点。很多客户朋友都想知道用什么样的软件平台可以实现提质、降本、增效的目的。可以来了解低代码技术平台、拖拽式表单设计器的更多功能与特点。作为专业的服务商,流辰信息为客户提供整套低代码技术平台服务解决方案,通…

Simple WPF: C# 使用基本的async/await实现异步

本文介绍了基本async/await关键字基于TAP任务异步模型的异步任务处理方法。最新内容优先发布于个人博客:小虎技术分享站,随后逐步搬运到博客园。 创作不易,如果觉得有用请在Github上为博主点亮一颗小星星吧! 博主开始学习编程于11年前,年少时还只会使用cin 和cout ,给单片…

植物大战僵尸1.2.0.1073汉化版

下载链接:https://download.csdn.net/download/hello_hlqk/89528378?spm=1001.2101.3001.9499 植物大战僵尸是一款益智策略类塔防游戏,玩家通过武装多种植物切换不同的功能,快速有效地把僵尸阻挡在入侵的道路上。不同的敌人,不同的玩法构成五种不同的游戏模式,加之黑夜、…

3-电子支付技术与系统

3.1 电子货币及其职能 3.1.1 电子货币的定义 在电子商务迅速发展和金融电子化的背景下,电子货币成为媒介商品交易的重要工具。电子货币(ElectronicMoney,)是指用一定金额的现金或存款从发行者处兑换并获得代表相同金额的数据或者通过银行及第三方推出的快捷支付服务,通过使…

先有鸡还是先有蛋?这是领域驱动设计落地最大的困局

本文书接上回 《关于领域驱动设计,大家都理解错了》 欢迎关注公众号“老肖想当外语大佬”: https://mp.weixin.qq.com/s/HHJ5vt2_iT0-CFcw0HcPnA 先有鸡还是先有蛋的困局 前文我们提出了“领域驱动设计是一种价值观”这个观点,那么落地领域驱动设计就是践行价值观的过程,实…

2017年,我成为了技术博主

2017年9月,我已经大三了。 >>上一篇(爪哇,我初窥门径) 我大二学了很多java技术,看似我一会就把javaweb/ssh/ssm这些技术栈给学了。 这些技术确实不难,即便是我,我都能学会,门槛并不高。 但是,这也不是能随便百度学学,10分钟就懂了,学会了。 从理解技术的用途,…