最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動(dòng)生成19位長(zhǎng)度主鍵id的坑

 更新時(shí)間:2021年12月10日 09:25:32   作者:朱季謙  
這篇文章主要介紹了Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動(dòng)生成19位長(zhǎng)度主鍵id的坑,本文一步步給大家分享解決方法,給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

某天檢查一位離職同事寫的代碼,發(fā)現(xiàn)其對(duì)應(yīng)表雖然設(shè)置了AUTO_INCREMENT自增,但頁(yè)面新增功能生成的數(shù)據(jù)主鍵id很詭異,長(zhǎng)度達(dá)到了19位,且不是從1開(kāi)始遞增的——

我檢查了一下,發(fā)現(xiàn)該表目前自增主鍵已經(jīng)變成從1468844351843872770開(kāi)始遞增了——

這就很奇怪了,目前該表數(shù)據(jù)量很少,且主鍵是設(shè)置AUTO_INCREMENT,正常而言,應(yīng)該自增id仍在1000范圍內(nèi),但目前已經(jīng)變成一串長(zhǎng)數(shù)字。

底層ORM框架用的是Mybatis-Plus,我尋思了一下,這看起來(lái)像是在插入數(shù)據(jù)庫(kù)就自動(dòng)生成的id,導(dǎo)致并非默認(rèn)使用MySql的自增AUTO_INCREMENT來(lái)生成id。

因此,決定一步步定位,先給Mybatis-Plus打印出sql日志,看下其insert語(yǔ)句是否自動(dòng)生成了一個(gè)id后才插入數(shù)據(jù)庫(kù)。

按照網(wǎng)上的教程,我在yaml文件里對(duì)應(yīng)的mybatis-plus配置處設(shè)置了開(kāi)啟sql打印日志——

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

然而,很詭異的是,執(zhí)行操作時(shí)并沒(méi)有打印出sql日志,故而,某一瞬間,我忽然覺(jué)得,這群家伙可能都是互相抄的,沒(méi)有驗(yàn)證當(dāng)springboot集成了logback時(shí),單純這樣設(shè)置并沒(méi)有效果。

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

logging:

? level:

? ? com:

? ? ? zhu:

? ? ? ? test:

? ? ? ? ? mapper: debug? ?

接下來(lái),驗(yàn)證一番后,發(fā)現(xiàn),Mybatis-Plus在做insert操作時(shí),確實(shí)自動(dòng)生成一條長(zhǎng)19的數(shù)字當(dāng)做該條數(shù)據(jù)的id插入到MySql,導(dǎo)致雖然MySql表設(shè)置了自增,但被Mybatis-Plus生成的id為1468844351843872769所影響,導(dǎo)致下一條數(shù)據(jù)自動(dòng)遞增值變成1468844351843872770,這種過(guò)長(zhǎng)的id值,在做索引維護(hù)時(shí),是很影響效率,占用空間過(guò)大,故而,這個(gè)問(wèn)題必須得解決。

到這里,就確定,這個(gè)長(zhǎng)數(shù)字的id,是在代碼層次就自動(dòng)生成了,最后進(jìn)入對(duì)應(yīng)的實(shí)體類中,發(fā)現(xiàn)該映射數(shù)據(jù)表的id字段,并沒(méi)有顯示設(shè)置對(duì)應(yīng)的主鍵生成策略。

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

Mybatis-Plus主要有以下幾種主鍵生成策略——

@Getter
public enum IdType {
    /**
     * 數(shù)據(jù)庫(kù)ID自增
     */
    AUTO(0),
    /**
     * 該類型為未設(shè)置主鍵類型
     */
    NONE(1),
    /**
     * 用戶輸入ID
     * 該類型可以通過(guò)自己注冊(cè)自動(dòng)填充插件進(jìn)行填充
     */
    INPUT(2),

    /* 以下3種類型、只有當(dāng)插入對(duì)象ID 為空,才自動(dòng)填充。 */
    /**
     * 全局唯一ID (idWorker),根據(jù)雪花算法生成19位數(shù)字,long類型
     */
    ID_WORKER(3),
    /**
     * 全局唯一ID (UUID)
     */
    UUID(4),
    /**
     * 字符串全局唯一ID (idWorker 的字符串表示),根據(jù)雪花算法生成19位字符串,String
     */
    ID_WORKER_STR(5);

    private int key;

    IdType(int key) {
        this.key = key;
    }
}

這里驗(yàn)證了一下,當(dāng)設(shè)置成這樣時(shí),就能正常生成數(shù)據(jù)庫(kù)自增的id了,使用數(shù)據(jù)庫(kù)AUTO_INCREMENT從1開(kāi)始自增的效果了,當(dāng)然,其實(shí)使用IdType.AUTO也是可以的——

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

百度網(wǎng)上的說(shuō)法,當(dāng)Mybatis-Plus實(shí)體類沒(méi)有顯示設(shè)置主鍵策略時(shí),將默認(rèn)使用雪花算法生成,也就是IdType.ID_WORKER或者IdType.ID_WORKER_STR,具體是long類型的19位還是字符串的19位,應(yīng)該是根據(jù)字段定義類型來(lái)判斷。

snowflake算法是Twitter開(kāi)源的分布式ID生成算法,結(jié)果是一個(gè)long類型的ID 。其核心思想:使用41bit作為毫秒數(shù),10bit作為機(jī)器的ID(5bit數(shù)據(jù)中心,5bit的機(jī)器ID),12bit作為毫秒內(nèi)的流水號(hào)(意味著每個(gè)節(jié)點(diǎn)在每個(gè)毫秒可以產(chǎn)生4096個(gè)ID),最后還有一個(gè)符號(hào)位,永遠(yuǎn)是0。

接下來(lái),先驗(yàn)證Mybatis-Plus默認(rèn)主鍵策略是如何的。

Mybatis-Plus項(xiàng)目在啟動(dòng)時(shí),會(huì)對(duì)注解實(shí)體類進(jìn)行初始化,然后緩存到系統(tǒng)Map中。

這里,只需要關(guān)注Mybatis-Plus源碼TableInfoHelper類中的initTableInfo方法即可,這個(gè)方法在項(xiàng)目啟動(dòng)時(shí)會(huì)被調(diào)用,然后初始化所有注解@TableName的實(shí)體類。與主鍵根據(jù)哪種策略來(lái)設(shè)置的邏輯在方法initTableFields(clazz, globalConfig, tableInfo)當(dāng)中——

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;
    }

    /* 沒(méi)有獲取到緩存信息,則初始化 */
    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 {
        // 兼容測(cè)試場(chǎng)景
        globalConfig = GlobalConfigUtils.defaults();
    }

    /* 初始化表名相關(guān) */
    initTableName(clazz, globalConfig, tableInfo);

    /* 初始化字段相關(guān) */
    initTableFields(clazz, globalConfig, tableInfo);

    /* 放入緩存 */
    TABLE_INFO_CACHE.put(clazz.getName(), tableInfo);

    /* 緩存 Lambda 映射關(guān)系 */
    LambdaUtils.createCache(clazz, tableInfo);
    return tableInfo;
}

在初始化字段相關(guān)的initTableFields方法里,會(huì)判斷是否有@TableId 注解,如果沒(méi)有,就執(zhí)行initTableIdWithoutAnnotation方法,連續(xù)前文提到的,如果實(shí)體類id沒(méi)有加@TableId(value = "id", type = IdType.INPUT),那么就會(huì)取默認(rèn)的主鍵策略。這里的判斷是否有@TableId 注解,就是判斷是否需要取默認(rèn)的主鍵策略,至于具體是如何設(shè)置默認(rèn)主鍵的,我們可以直接進(jìn)入到initTableIdWithoutAnnotation方法當(dāng)中。

public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
    /* 數(shù)據(jù)庫(kù)全局配置 */
    GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
    List<Field> list = getAllFields(clazz);
    // 標(biāo)記是否讀取到主鍵
    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     實(shí)體類
 * @return true 繼續(xù)下一個(gè)屬性判斷,返回 continue;
 */
private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
                                                 Field field, Class<?> clazz) {
    //獲取實(shí)體類字段名
    String column = field.getName();
    if (dbConfig.isCapitalMode()) {
        column = column.toUpperCase();
    }
    //當(dāng)字段名為id
    if (DEFAULT_ID_NAME.equalsIgnoreCase(column)) {
        if (StringUtils.isEmpty(tableInfo.getKeyColumn())) {
            tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), field.getName(), column))
                //設(shè)置表策略
                .setIdType(dbConfig.getIdType())
                .setKeyColumn(column)
                .setKeyProperty(field.getName())
                .setClazz(field.getDeclaringClass());
            return true;
        } else {
            throwExceptionId(clazz);
        }
    }
    return false;
}

Debug到這里,可以看到,如果沒(méi)有 @TableId 注解顯示設(shè)置主鍵策略情況下,默認(rèn)設(shè)置的是 ID_WORKER(3),即會(huì)根據(jù)雪花算法生成19位數(shù)字,long類型。

可以進(jìn)一步發(fā)現(xiàn),這里的 dbConfig是GlobalConfig.DbConfig實(shí)例,進(jìn)入到DbConfig類,可以看到原來(lái)實(shí)體類映射的數(shù)據(jù)庫(kù)設(shè)置在這里,主鍵類型默認(rèn)是IdType.ID_WORKER。

@Data
public static class DbConfig {

    /**
     * 數(shù)據(jù)庫(kù)類型
     */
    private DbType dbType = DbType.OTHER;
    /**
     * 主鍵類型(默認(rèn) ID_WORKER)
     */
    private IdType idType = IdType.ID_WORKER;
    /**
     * 表名前綴
     */
    private String tablePrefix;
    /**
     * 表名、是否使用下劃線命名(默認(rèn) true:默認(rèn)數(shù)據(jù)庫(kù)表下劃線命名)
     */
    private boolean tableUnderline = true;
    /**
     * String 類型字段 LIKE
     */
    private boolean columnLike = false;
    /**
     * 大寫命名
     */
    private boolean capitalMode = false;
    /**
     * 表關(guān)鍵詞 key 生成器
     */
    private IKeyGenerator keyGenerator;
    /**
     * 邏輯刪除全局值(默認(rèn) 1、表示已刪除)
     */
    private String logicDeleteValue = "1";
    /**
     * 邏輯未刪除全局值(默認(rèn) 0、表示未刪除)
     */
    private String logicNotDeleteValue = "0";
    /**
     * 字段驗(yàn)證策略
     */
    private FieldStrategy fieldStrategy = FieldStrategy.NOT_NULL;
}

至于如何生成雪花算法id,這里就不一一詳細(xì)介紹,具體邏輯是在MybatisDefaultParameterHandler類populateKeys方法里,核心代碼如下——

protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                     MappedStatement ms, Object parameterObject, boolean isInsert) {
    if (null == tableInfo) {
        /* 不處理 */
        return parameterObject;
    }
    /* 自定義元對(duì)象填充控制器 */
    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());
            }
        }
    }
   ......
}

前邊提到,默認(rèn)的主鍵策略是IdType.ID_WORKER,這里有一個(gè)判斷tableInfo.getIdType() == IdType.ID_WORKER,對(duì)代碼Debug可以看到,metaObject的setValue(tableInfo.getKeyProperty(), IdWorker.getId())代碼的作用,是對(duì)注解id進(jìn)行了值填充。

填充的值為IdWorker.getId()返回的1468970800437465089,剛好是19位長(zhǎng)度,這就意味著,這里產(chǎn)生的id值,就是我們最后要找的。

IdWorker.getId()實(shí)現(xiàn)本質(zhì),正好是基于Snowflake實(shí)現(xiàn)64位自增ID算法,而Snowflake,正是引用了雪花算法——

/**
 * <p>
 * 高效GUID產(chǎn)生算法(sequence),基于Snowflake實(shí)現(xiàn)64位自增ID算法。 <br>
 * 優(yōu)化開(kāi)源項(xiàng)目 http://git.oschina.net/yu120/sequence
 * </p>
 *
 * @author hubin
 * @since 2016-08-01
 */
public class IdWorker {

    /**
     * 主機(jī)和進(jìn)程的機(jī)器碼
     */
    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);
    }

}

到此這篇關(guān)于Mybatis-Plus默認(rèn)主鍵策略導(dǎo)致自動(dòng)生成19位長(zhǎng)度主鍵id的坑的文章就介紹到這了,更多相關(guān)Mybatis-Plus id主鍵生成內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何使用Java 8 中的 Stream 遍歷樹(shù)形結(jié)構(gòu)

    如何使用Java 8 中的 Stream 遍歷樹(shù)形結(jié)構(gòu)

    這篇文章主要介紹了使用Java 8中的Stream遍歷樹(shù)形結(jié)構(gòu),我們可以使用Java8中的Stream流一次性把數(shù)據(jù)查出來(lái),然后通過(guò)流式處理,我們一起來(lái)看看,代碼實(shí)現(xiàn)為了實(shí)現(xiàn)簡(jiǎn)單,就模擬查看數(shù)據(jù)庫(kù)所有數(shù)據(jù)到List里面,需要的朋友可以參考下
    2023-08-08
  • java如何根據(jù)用戶請(qǐng)求獲取ip地址并解析省市信息

    java如何根據(jù)用戶請(qǐng)求獲取ip地址并解析省市信息

    根據(jù)當(dāng)前用戶本地IP地址,查詢IP地址庫(kù),獲取IP所在的省市信息,目前有多種方式實(shí)現(xiàn)該功能,這篇文章主要給大家介紹了關(guān)于java如何根據(jù)用戶請(qǐng)求獲取ip地址并解析省市信息的相關(guān)資料,需要的朋友可以參考下
    2023-12-12
  • Java實(shí)現(xiàn)簡(jiǎn)單圖形界面計(jì)算器

    Java實(shí)現(xiàn)簡(jiǎn)單圖形界面計(jì)算器

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單圖形界面計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Spring Boot 多個(gè)定時(shí)器沖突問(wèn)題的解決方法

    Spring Boot 多個(gè)定時(shí)器沖突問(wèn)題的解決方法

    這篇文章主要介紹了Spring Boot 多個(gè)定時(shí)器沖突問(wèn)題的解決方法,實(shí)際開(kāi)發(fā)中定時(shí)器需要解決多個(gè)定時(shí)器同時(shí)并發(fā)的問(wèn)題,也要解決定時(shí)器之間的沖突問(wèn)題,本文通過(guò)問(wèn)題場(chǎng)景重現(xiàn)給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-05-05
  • SpringBoot 整合 Avro 與 Kafka的詳細(xì)過(guò)程

    SpringBoot 整合 Avro 與 Kafka的詳細(xì)過(guò)程

    本文介紹了如何在Spring Boot中使用Avro和Kafka進(jìn)行數(shù)據(jù)的序列化和反序列化,并通過(guò)MyBatisPlus將數(shù)據(jù)存入數(shù)據(jù)庫(kù),感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • java實(shí)現(xiàn)檢測(cè)是否字符串中包含中文

    java實(shí)現(xiàn)檢測(cè)是否字符串中包含中文

    本文給大家分享了2個(gè)使用java檢測(cè)字符串中是否包含中文的代碼,都非常的實(shí)用,最后附上了各種字符的unicode編碼的范圍,方便我們以后使用正則進(jìn)行匹配檢測(cè)。
    2015-10-10
  • 淺談Maven鏡像更換為阿里云中央倉(cāng)庫(kù)(精)

    淺談Maven鏡像更換為阿里云中央倉(cāng)庫(kù)(精)

    本篇文章主要介紹了Maven鏡像更換為阿里云中央倉(cāng)庫(kù)(精),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • Java后臺(tái)處理Json格式數(shù)據(jù)的方法

    Java后臺(tái)處理Json格式數(shù)據(jù)的方法

    這篇文章主要介紹了Java后臺(tái)處理Json格式數(shù)據(jù)的方法的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • Java核心技術(shù)之反射

    Java核心技術(shù)之反射

    本文非常詳細(xì)的講解了java反射的相關(guān)資料,java反射在現(xiàn)今的使用中很頻繁,希望此文可以幫大家解答疑惑,可以幫助大家理解
    2021-11-11
  • Java中StringBuilder常用構(gòu)造方法解析

    Java中StringBuilder常用構(gòu)造方法解析

    這篇文章主要介紹了Java中StringBuilder常用構(gòu)造方法解析,StringBuilder是一個(gè)可標(biāo)的字符串類,我們可以吧它看成是一個(gè)容器這里的可變指的是StringBuilder對(duì)象中的內(nèi)容是可變的,需要的朋友可以參考下
    2024-01-01

最新評(píng)論

治多县| 陆丰市| 南安市| 开阳县| 五峰| 湖口县| 南康市| 车险| 伽师县| 泉州市| 台北市| 柳林县| 嫩江县| 泰来县| 改则县| 南召县| 金乡县| 兴山县| 永兴县| 沈阳市| 绿春县| 濮阳县| 慈溪市| 临高县| 紫阳县| 锡林浩特市| 榆中县| 扎兰屯市| 长春市| 锦屏县| 从化市| 肥西县| 安乡县| 城固县| 河西区| 舒城县| 阳朔县| 深泽县| 太原市| 钦州市| 诸城市|