You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重写唯一ID生成逻辑?自定义ID生成器实现遇阻

解决自定义ID生成器的两个核心问题:依赖注入失败+优雅生成唯一数字ID

嘿,我来帮你搞定这个问题!刚好之前在项目里做过类似的自定义ID生成逻辑,针对你遇到的两个痛点——FooRepository实例为null、不想用“查最大ID转BigInteger”的笨办法,给你几个实用又优雅的方案:

一、先搞定FooRepository为null的问题(如果一定要用Repository的话)

大概率是你的FooGenerator没有被Spring容器托管,而是手动new出来的,导致依赖注入失效。如果是Hibernate自定义ID生成器的场景,可以通过实现ApplicationContextAware接口来获取Spring上下文,手动获取Repository实例:

public class FooGenerator implements IdentifierGenerator, ApplicationContextAware {
    private static ApplicationContext springContext;
    private FooRepository fooRepository;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        springContext = applicationContext;
    }

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object entity) throws HibernateException {
        // 延迟初始化Repository
        if (fooRepository == null) {
            fooRepository = springContext.getBean(FooRepository.class);
        }
        // 这里就可以正常调用fooRepository的方法了
    }
}

不过真心不建议用查最大ID的方式生成新ID——高并发下很容易出现重复,而且每次生成都要查库,性能拉胯。下面给你几个更靠谱的替代方案:

二、优雅生成15-25位唯一数字ID的方案

方案1:利用数据库原生序列/自增机制+自定义前缀

如果你的数据库支持序列(比如PostgreSQL的SEQUENCE),或者MySQL的自增主键,可以直接用数据库的原子性递增能力,再拼接固定前缀(比如时间戳的前几位)来凑够15-25位:

比如PostgreSQL中先创建序列:

CREATE SEQUENCE foo_id_seq START WITH 1000000000 INCREMENT BY 1;

然后在生成器里通过SessionImplementer执行SQL获取下一个序列值:

@Override
public Serializable generate(SharedSessionContractImplementor session, Object entity) throws HibernateException {
    SessionImplementer sessionImpl = (SessionImplementer) session;
    // 执行SQL获取序列值
    BigInteger seqValue = (BigInteger) sessionImpl.createNativeQuery("SELECT nextval('foo_id_seq')")
            .uniqueResult();
    // 拼接前缀(比如当前时间戳的前8位),生成16位ID:8位时间戳+8位序列值
    String prefix = String.valueOf(System.currentTimeMillis()).substring(0, 8);
    return prefix + String.format("%08d", seqValue);
}

MySQL的话可以用自增主键,插入后获取自增值再拼接前缀,不过这种方式需要先插入再更新ID,不如序列方便。

方案2:用Redis原子递增生成ID(推荐高并发场景)

如果项目里有Redis,这绝对是最优解——Redis的INCR命令是原子性的,不会出现重复,而且性能超高。你可以给每个业务类型定义一个Redis key,每次生成ID时递增这个key的值,再拼接前缀生成15-25位的数字ID:

// 假设你已经通过Spring上下文获取了StringRedisTemplate
public String generateFooId() {
    String redisKey = "foo:id:counter";
    // 原子递增,返回当前递增后的值
    Long counter = stringRedisTemplate.opsForValue().increment(redisKey);
    // 拼接前缀(比如10位时间戳),生成18位ID:10位时间戳+8位计数器
    String prefix = String.valueOf(System.currentTimeMillis()).substring(0, 10);
    // 格式化计数器为8位,不足补0
    return prefix + String.format("%08d", counter);
}

这种方式完全不需要操作数据库,也不存在Repository依赖的问题,高并发下稳得一批。

方案3:基于Snowflake算法的变种(分布式无依赖)

Snowflake算法生成的是64位整数,转换成十进制刚好是18位左右,完美符合你15-25位的要求。而且它不需要依赖任何外部存储,本地就能生成,分布式场景下也能保证唯一。

你可以自己实现一个变种,调整时间戳、机器ID、序列号的位数,比如:

  • 时间戳部分:用当前时间减去一个固定起始时间,取40位(约34年)
  • 机器ID:10位(支持1024台机器)
  • 序列号:14位(每毫秒生成16384个ID)
    把这些部分拼接成一个64位的Long,转换成十进制就是18位左右的数字ID,完全满足你的需求。

总结

优先推荐方案2(Redis)或者方案3(Snowflake变种),这两种方式既解决了FooRepository为null的问题(根本不需要用Repository),又避免了查最大ID的性能和并发问题,优雅又可靠。

内容的提问来源于stack exchange,提问作者Viktor M.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:24:28