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.pswp.cn/news/162226.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/162226.shtml
英文地址,請注明出處:http://en.pswp.cn/news/162226.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

7.1 Windows驅動開發:內核監控進程與線程回調

在前面的文章中LyShark一直在重復的實現對系統底層模塊的枚舉&#xff0c;今天我們將展開一個新的話題&#xff0c;內核監控&#xff0c;我們以監控進程線程創建為例&#xff0c;在Win10系統中監控進程與線程可以使用微軟提供給我們的兩個新函數來實現&#xff0c;此類函數的原…

H3C路由器基本配置命令

1、system-view 進入系統視圖 2、sysname R1 配置路由器名字為R1 3、display clock 查看當前系統時間 4、clock datetime 00:00:00 2/26/2023 用戶模式下修改系統時間 配置控制臺密碼 Console&#xff1a; 1、user-interface aux0 在系統模式下進入圖接口 2、authentication-mo…

2023亞太賽c題完整思路數據 數學建模亞太

Question 1: Analyze the main factors that affect the development of new energy electric vehicles in China, establish a mathematical model, and describe the impact of these factors on the development of new energy electric vehicles in China. 問題1:分析影響…

office 365企業版安裝教程

1.下載所需工具&#xff08;防火墻和防毒軟件記得關閉&#xff09; 下載鏈接&#xff1a;所需文件 2.安裝激活office 1.安裝 office tool plus 2.已安裝過office 先進行office的移除&#xff0c;再進行未安裝office的步驟進行 3.未安裝過office 1.設置部署 按照以下來進行安…

Linux命令(130)之hwclock

linux命令之hwclock 1.hwclock介紹 linux命令hwclock是用來顯示硬件時鐘 2.hwclock用法 hwclock [參數] hwclock參數 參數說明-s讓系統時間同步硬件時間-w讓硬件時間同步系統時間 3.實例 3.1.同步服務器時鐘時間 命令&#xff1a; ntpdate pool.ntp.org [rootrhel77 ~…

【vin 國標正則校驗】

規則一 var checkVINfunction(VIN){if(typeof(VIN)!string) return false;if(VIN.length!17) return false;VINVIN.toUpperCase();RE/^[A-HJ-NPR-Z\d]{8}[X\d][A-HJ-NPR-Z\d]{3}\d{5}$/if(!RE.test(VIN)) return false;let cOT{0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:1,B…

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;讓開發者可以創…

關于Redis底層的兩個問題

1. 為什么Redis不共享包含字符串的對象&#xff1f; 當服務器考慮將一個共享對象設置為鍵的值對象時&#xff0c;程序首先需要檢查給定的共享對象和鍵想要創建的目標對象是否完全相同&#xff0c;只有在共享對象和目標對象完全相同的情況下&#xff0c;程序才會將共享對象用作…

SOEM主站開發篇(3):為APP程序添加命令

0 工具準備 1.SOEM-1.4.0源碼(官網:http://openethercatsociety.github.io/) 2.Linux開發板(本文為正點原子I.MX6U ALPHA開發板) 3.交叉編譯工具(arm-linux-gnueabihf-gcc) 4.cmake(版本不得低于3.9,本文為3.9.2) 5.Ubuntu 16.04(用于編譯生成Linux開發板的可執行文…

【追求卓越06】算法--遞歸

引導 遞歸算法算是我們比較常用的一種算法。但是想用好并不簡單。本章我不再介紹簡單的概念&#xff0c;主要講解遞歸算法的優缺點和如何用遞歸寫代碼。 個人愛好 其實相對于使用while循環&#xff0c;我更喜歡使用遞歸算法。為什么呢&#xff1f; 使用遞歸算法代碼往往會變…

Java語言中的控制流程

控制流程是編程中的重要概念之一&#xff0c;它允許程序根據條件執行不同的代碼塊或重復執行特定的代碼塊。在Java中&#xff0c;控制流程由條件語句和循環語句組成。本文將詳細介紹Java中的條件語句&#xff08;if語句和switch語句&#xff09;和循環語句&#xff08;for循環、…

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;我有自己的見解。 一、貓罐頭多久喂…

shell之wc命令

shell之wc命令 Linux中的wc命令是一個用于統計給定文件中的字節數、字數和行數的工具。它也可以從標準輸入讀取數據并統計。 wc命令的語法為&#xff1a; wc [選項] 文件... 如果沒有給出文件名&#xff0c;則從標準輸入讀取。wc同時也給出所有指定文件的總統計數。wc命令的選…

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…