iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Mybatis-Plus默认主键策略导致自动生成19位长度主键id的坑
  • 710
分享到

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

2024-04-02 19:04:59 710人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

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

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

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

这就很奇怪了,目前该表数据量很少,且主键是设置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
    AUTO(0),
    
    NONE(1),
    
    INPUT(2),

    
    
    ID_WORKER(3),
    
    UUID(4),
    
    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);

    
    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) {
        
        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";

private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo,
                                                 Field field, Class<?> clazz) {
    //获取实体类字段名
    String column = field.getName();
    if (dbConfig.isCapitalMode()) {
        column = column.toUpperCase();
    }
    //当字段名为id
    if (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类型。

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


@Data
public static class DbConfig {

    
    private DbType dbType = DbType.OTHER;
    
    private IdType idType = IdType.ID_WORKER;
    
    private String tablePrefix;
    
    private boolean tableUnderline = true;
    
    private boolean columnLike = false;
    
    private boolean capitalMode = false;
    
    private IKeyGenerator keyGenerator;
    
    private String logicDeleteValue = "1";
    
    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());
        
        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进行了值填充。

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

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



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

    
    public static synchronized String get32UUID() {
        return UUID.randomUUID().toString().replace(StringPool.DASH, StringPool.EMPTY);
    }

}

到此这篇关于Mybatis-Plus默认主键策略导致自动生成19位长度主键id的坑的文章就介绍到这了,更多相关Mybatis-Plus id主键生成内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Mybatis-Plus默认主键策略导致自动生成19位长度主键id的坑

本文链接: https://www.lsjlt.com/news/159562.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作