Mybatis-Plus3.0默认主键策略导致自动生成19位长度主键id的坑

码字不易,如果对您有用,求各位看官点赞关注~

原创/朱季谦

目前的Mybatis-Plus版本是3.0,至于最新版本是否已经没有这个问题,后续再考虑研究。

某天检查一位离职同事写的代码,发现其对应表虽然设置了AUTO_INCREMENT自增,但页面新增功能生成的数据主键id很诡异,长度达到了19位,且不是从1开始递增的——

image

我检查了一下,发现该表目前自增主键已经变成从1468844351843872770开始递增了——

image

这就很奇怪了,目前该表数据量很少,且主键是设置AUTO_INCREMENT,正常而言,应该自增id仍在1000范围内,但目前已经变成一串长数字。

底层ORM框架用的是Mybatis-Plus,我寻思了一下,这看起来像是在插入数据库就自动生成的id,导致并非默认使用MySql的自增AUTO_INCREMENT来生成id。

因此,决定一步步定位,先给Mybatis-Plus打印出sql日志,看下其insert语句是否自动生成了一个id后才插入数据库。

按照网上的教程,我在yaml文件里对应的mybatis-plus配置处设置了开启sql打印日志——

mybatis-plus:mapper-locations: classpath*:mapper/*.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

然而,很诡异的是,执行操作时并没有打印出sql日志,故而,某一瞬间,我忽然觉得,这群家伙可能都是互相抄的,没有验证当springboot集成了logback时,单纯这样设置并没有效果。

最后额外在yaml加了以下配置,才能正常打印MP的sql日志信息——

logging:level:com:zhu:test:mapper: debug   

接下来,验证一番后,发现,Mybatis-Plus在做insert操作时,确实自动生成一条长19的数字当做该条数据的id插入到MySql,导致虽然MySql表设置了自增,但被Mybatis-Plus生成的id为1468844351843872769所影响,导致下一条数据自动递增值变成1468844351843872770,这种过长的id值,在做索引维护时,是很影响效率,占用空间过大,故而,这个问题必须得解决。

image

到这里,就确定,这个长数字的id,是在代码层次就自动生成了,最后进入对应的实体类中,发现该映射数据表的id字段,并没有显示设置对应的主键生成策略。

@Data
@TableName("test")
public class Test extends Model<Test> implements Serializable {private Long id;......
}

Mybatis-Plus主要有以下几种主键生成策略——

@Getter
public enum IdType {/*** 数据库ID自增*/AUTO(0),/*** 该类型为未设置主键类型*/NONE(1),/*** 用户输入ID* 该类型可以通过自己注册自动填充插件进行填充*/INPUT(2),/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 *//*** 全局唯一ID (idWorker),根据雪花算法生成19位数字,long类型*/ID_WORKER(3),/*** 全局唯一ID (UUID)*/UUID(4),/*** 字符串全局唯一ID (idWorker 的字符串表示),根据雪花算法生成19位字符串,String*/ID_WORKER_STR(5);private int key;IdType(int key) {this.key = key;}
}

这里验证了一下,当设置成这样时,就能正常生成数据库自增的id了,使用数据库AUTO_INCREMENT从1开始自增的效果了,当然,其实使用IdType.AUTO也是可以的——

@Data
@TableName("test")
public class Test extends Model<Test> implements Serializable {@TableId(value = "id", type = IdType.INPUT)private Long id;......
}

百度网上的说法,当Mybatis-Plus实体类没有显示设置主键策略时,将默认使用雪花算法生成,也就是IdType.ID_WORKER或者IdType.ID_WORKER_STR,具体是long类型的19位还是字符串的19位,应该是根据字段定义类型来判断。

snowflake算法是Twitter开源的分布式ID生成算法,结果是一个long类型的ID 。其核心思想:使用41bit作为毫秒数,10bit作为机器的ID(5bit数据中心,5bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每个毫秒可以产生4096个ID),最后还有一个符号位,永远是0。

接下来,先验证Mybatis-Plus默认主键策略是如何的。

Mybatis-Plus项目在启动时,会对注解实体类进行初始化,然后缓存到系统Map中。

这里,只需要关注Mybatis-Plus源码TableInfoHelper类中的initTableInfo方法即可,这个方法在项目启动时会被调用,然后初始化所有注解@TableName的实体类。与主键根据哪种策略来设置的逻辑在方法initTableFields(clazz, globalConfig, tableInfo)当中——

public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {TableInfo tableInfo = TABLE_INFO_CACHE.get(clazz.getName());if (tableInfo != null) {if (tableInfo.getConfigMark() == null && builderAssistant != null) {tableInfo.setConfigMark(builderAssistant.getConfiguration());}return tableInfo;}/* 没有获取到缓存信息,则初始化 */tableInfo = new TableInfo();GlobalConfig globalConfig;if (null != builderAssistant) {tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());tableInfo.setConfigMark(builderAssistant.getConfiguration());tableInfo.setUnderCamel(builderAssistant.getConfiguration().isMapUnderscoreToCamelCase());globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());} else {// 兼容测试场景globalConfig = GlobalConfigUtils.defaults();}/* 初始化表名相关 */initTableName(clazz, globalConfig, tableInfo);/* 初始化字段相关 */initTableFields(clazz, globalConfig, tableInfo);/* 放入缓存 */TABLE_INFO_CACHE.put(clazz.getName(), tableInfo);/* 缓存 Lambda 映射关系 */LambdaUtils.createCache(clazz, tableInfo);return tableInfo;
}

在初始化字段相关的initTableFields方法里,会判断是否有@TableId 注解,如果没有,就执行initTableIdWithoutAnnotation方法,连续前文提到的,如果实体类id没有加@TableId(value = "id", type = IdType.INPUT),那么就会取默认的主键策略。这里的判断是否有@TableId 注解,就是判断是否需要取默认的主键策略,至于具体是如何设置默认主键的,我们可以直接进入到initTableIdWithoutAnnotation方法当中。

public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {/* 数据库全局配置 */GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();List<Field> list = getAllFields(clazz);// 标记是否读取到主键boolean isReadPK = false;// 是否存在 @TableId 注解boolean existTableId = isExistTableId(list);List<TableFieldInfo> fieldList = new ArrayList<>();for (Field field : list) {/** 主键ID 初始化*/if (!isReadPK) {if (existTableId) {isReadPK = initTableIdWithAnnotation(dbConfig, tableInfo, field, clazz);} else {isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, clazz);}if (isReadPK) {continue;}}......}......
}

initTableIdWithoutAnnotation方法——

private static final String DEFAULT_ID_NAME = "id";
/*** <p>* 主键属性初始化* </p>** @param tableInfo 表信息* @param field     字段* @param clazz     实体类* @return true 继续下一个属性判断,返回 continue;*/
private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,Field field, Class<?> clazz) {//获取实体类字段名String column = field.getName();if (dbConfig.isCapitalMode()) {column = column.toUpperCase();}//当字段名为idif (DEFAULT_ID_NAME.equalsIgnoreCase(column)) {if (StringUtils.isEmpty(tableInfo.getKeyColumn())) {tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), field.getName(), column))//设置表策略.setIdType(dbConfig.getIdType()).setKeyColumn(column).setKeyProperty(field.getName()).setClazz(field.getDeclaringClass());return true;} else {throwExceptionId(clazz);}}return false;
}

Debug到这里,可以看到,如果没有 @TableId 注解显示设置主键策略情况下,默认设置的是 ID_WORKER(3),即会根据雪花算法生成19位数字,long类型。

image

可以进一步发现,这里的 dbConfig是GlobalConfig.DbConfig实例,进入到DbConfig类,可以看到原来实体类映射的数据库设置在这里,主键类型默认是IdType.ID_WORKER。

@Data
public static class DbConfig {/*** 数据库类型*/private DbType dbType = DbType.OTHER;/*** 主键类型(默认 ID_WORKER)*/private IdType idType = IdType.ID_WORKER;/*** 表名前缀*/private String tablePrefix;/*** 表名、是否使用下划线命名(默认 true:默认数据库表下划线命名)*/private boolean tableUnderline = true;/*** String 类型字段 LIKE*/private boolean columnLike = false;/*** 大写命名*/private boolean capitalMode = false;/*** 表关键词 key 生成器*/private IKeyGenerator keyGenerator;/*** 逻辑删除全局值(默认 1、表示已删除)*/private String logicDeleteValue = "1";/*** 逻辑未删除全局值(默认 0、表示未删除)*/private String logicNotDeleteValue = "0";/*** 字段验证策略*/private FieldStrategy fieldStrategy = FieldStrategy.NOT_NULL;
}

至于如何生成雪花算法id,这里就不一一详细介绍,具体逻辑是在MybatisDefaultParameterHandler类populateKeys方法里,核心代码如下——

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());}}}......
}

前边提到,默认的主键策略是IdType.ID_WORKER,这里有一个判断tableInfo.getIdType() == IdType.ID_WORKER,对代码Debug可以看到,metaObject的setValue(tableInfo.getKeyProperty(), IdWorker.getId())代码的作用,是对注解id进行了值填充。

image


填充的值为IdWorker.getId()返回的1468970800437465089,刚好是19位长度,这就意味着,这里产生的id值,就是我们最后要找的。

IdWorker.getId()实现本质,正好是基于Snowflake实现64位自增ID算法,而Snowflake,正是引用了雪花算法——

/*** <p>* 高效GUID产生算法(sequence),基于Snowflake实现64位自增ID算法。 <br>* 优化开源项目 http://git.oschina.net/yu120/sequence* </p>** @author hubin* @since 2016-08-01*/
public class IdWorker {/*** 主机和进程的机器码*/private static final Sequence WORKER = new Sequence();public static long getId() {return WORKER.nextId();}public static String getIdStr() {return String.valueOf(WORKER.nextId());}/*** <p>* 获取去掉"-" UUID* </p>*/public static synchronized String get32UUID() {return UUID.randomUUID().toString().replace(StringPool.DASH, StringPool.EMPTY);}}

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

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

相关文章

7.1 Windows驱动开发:内核监控进程与线程回调

在前面的文章中LyShark一直在重复的实现对系统底层模块的枚举&#xff0c;今天我们将展开一个新的话题&#xff0c;内核监控&#xff0c;我们以监控进程线程创建为例&#xff0c;在Win10系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现&#xff0c;此类函数的原…

office 365企业版安装教程

1.下载所需工具&#xff08;防火墙和防毒软件记得关闭&#xff09; 下载链接&#xff1a;所需文件 2.安装激活office 1.安装 office tool plus 2.已安装过office 先进行office的移除&#xff0c;再进行未安装office的步骤进行 3.未安装过office 1.设置部署 按照以下来进行安…

360:流氓or保家卫国的勇士?

你曾用过360吗&#xff0c;这个在国内名声不好的杀毒软件&#xff0c;却是令国外黑客闻风丧胆的存在。 首先&#xff0c;在电脑病毒刚兴起的年代&#xff0c;杀毒软件是要收费的&#xff0c;当时盛行的瑞星和金山就是采用的付费模式&#xff0c;而就在2006年&#xff0c;奇虎…

C/C++通过位操作实现2个uint32_t合并为uint64_t

#include <iostream> using namespace std;int main() {uint32_t a 10;uint32_t b 600;//先将uint32_t的a转为uint64_t&#xff0c;此时a前面32位都是0&#xff0c;然后左移32位&#xff0c;此时右32位为0&#xff0c;最后加上uint32_t类型的b&#xff0c;填充右32位的…

解决Activiti5.22流程图部署在Windows上正常,但在linux上部署后出现中文变方块的问题

总结/朱季谦 楼主最近在做公司的工作流平台&#xff0c;发现一个很无语的事情&#xff0c;Activiti5.22的流程图在Windows环境上部署&#xff0c;是可以正常查看的&#xff0c;但发布到公司的Linux服务器上后&#xff0c;在上面进行流程图在线部署时&#xff0c;发现中文都变成…

2023亚太杯数学建模C题思路代码 - 我国新能源电动汽车的发展趋势

1 赛题 问题C 我国新能源电动汽车的发展趋势 新能源汽车是指以先进技术原理、新技术、新结构的非常规汽车燃料为动力来源( 非常规汽车燃料指汽油、柴油以外的燃料&#xff09;&#xff0c;将先进技术进行汽车动力控制和驱动相结 合的汽车。新能源汽车主要包括四种类型&#x…

一套开源、强大且美观的WPF UI控件库 - HandyControl

前言 今天给大家推荐一套开源、强大且美观的WPF UI控件库&#xff1a;HandyControl。 WPF介绍 WPF 是一个强大的桌面应用程序框架&#xff0c;用于构建具有丰富用户界面的 Windows 应用。它提供了灵活的布局、数据绑定、样式和模板、动画效果等功能&#xff0c;让开发者可以创…

MySQL用户与权限管理

快捷查看指令 ctrlf 进行搜索会直接定位到需要的知识点和命令讲解&#xff08;如有不正确的地方欢迎各位小伙伴在评论区提意见&#xff0c;博主会及时修改&#xff09; MySQL用户与权限管理 登录 #本地登录 mysql -uroot -p123456#远程登录 #客户端语法&#xff1a;mysql -…

聚观早报 |快手Q3营收;拼多多杀入大模型;Redmi K70E开启预约

【聚观365】11月23日消息 快手Q3营收 拼多多杀入大模型 Redmi K70E开启预约 华为nova 12系列或下周发布 亚马逊启动“AI就绪”新计划 快手Q3营收 财报显示&#xff0c;快手第三季度营收279亿元&#xff0c;同比增长20.8%&#xff1b;期内盈利21.8亿元&#xff0c;去年同期…

猫罐头多久喂一次?好用的猫罐头牌子推荐

猫爱吃猫罐头&#xff0c;包含各种美味&#xff0c;提供营养和口感。但喂猫吃罐头需技巧和耐心&#xff0c;以确保猫健康快乐成长。 作为一个从业宠物营养师7年的人&#xff0c;可以说对于猫咪的食物很有研究和猫罐头品牌选购上&#xff0c;我有自己的见解。 一、猫罐头多久喂…

40、Flink 的Apache Kafka connector(kafka source 和sink 说明及使用示例) 完整版

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

循环神经网络(RNN)实现股票预测

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据 四、数据预处理1.归一化2.设置测试集训练集 五、构建模型六、激活模型七、训练模型八、结果可视化1.绘制loss图2.预测3.评估 一、前言 我的环境&#xff1a; 语言环…

【Rust】快速教程——一直在单行显示打印、输入、文件读写

前言 恨不过是七情六欲的一种&#xff0c;再强大的恨也没法独占整颗心&#xff0c;总有其它情感隐藏在心底深处&#xff0c;说不定在什么时候就会掀起滔天巨浪。——《死人经》 图中是Starship扔掉下面的燃料罐&#xff0c;再扔掉头顶的翅膀后&#xff0c;再翻转过来着陆火星的…

[C++ 从入门到精通] 13.派生类、调用顺序、继承方式、函数遮蔽

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

【Unity细节】Default clip could not be found in attached animations list.(动画机报错)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

生产制造业如何谋求数字化转型?需要哪些信息化系统做支撑?

生产制造业的数字化转型是将数字系统和各种技术整合到传统制造流程中的过程&#xff0c;这将导致行业格局的重大变革。工业4.0的到来为制造业开创了一个新时代&#xff0c;制造商可以简化生产线&#xff0c;提高整体效率。同时&#xff0c;这一技术革命使他们能够收集到大量的数…

Altium Designer学习笔记9

忽视了一个最大的问题&#xff0c;就是元器件的封装&#xff0c;不应该是根据AD系统的封装走&#xff0c;而应该是根据立创商城上的规格书&#xff0c;确认每个封装的大小&#xff0c;画出封装图&#xff0c;然后才是布局和走线。 1、确认电容的封装采用0805&#xff0c;贴片电…

【css】Google第三方登录按钮样式修改

文章目录 场景前置准备修改样式官方属性修改样式CSS修改样式按钮的高度height和border-radiusLogo和文字布局 场景 需要用到谷歌的第三方登录&#xff0c;登录按钮有自己的样式。根据官方文档&#xff1a;概览 | Authentication | Google for Developers&#xff0c;提供两种第…

局域网协议:地址解析协议(ARP,Address Resolution Protocol)

地址解析协议&#xff08;ARP&#xff0c;Address Resolution Protocol&#xff09;是一种用于在IP网络中将IP地址映射到物理MAC地址的协议。在IP网络中&#xff0c;IP是用于寻址&#xff0c;真正将数据包从一个设备发送到另外一个设备&#xff0c;用于通信的是物理MAC地址。 …

40、Flink 的Apache Kafka connector(kafka sink的介绍及使用示例)-2

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…