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

Spring Boot唯一ID生成器实现求助及抽象类设计疑问

问题描述

我正在参考Wim Deblauwe所著的《Spring Boot API后端开发实用指南》开发CRUD应用,在实现实体类实例创建时的唯一ID生成器环节卡壳了,多次尝试都没成功。请帮忙完成generator.getNextUniqueId()对应的UniqueIdGenerator实现,同时解释为什么要通过这些抽象实体类来实现ID生成。

现有代码上下文

AbstractEntityId.java

package com.example.orm.jpa;
import com.example.util.ArtifactForFramework;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;

@MappedSuperclass
public abstract class AbstractEntityId<T extends Serializable> implements Serializable, EntityId<T> {
    private T id;

    @ArtifactForFramework
    protected AbstractEntityId() {
    }

    protected AbstractEntityId(T id) {
        this.id = Objects.requireNonNull(id);
    }

    @Override
    public T getId() {
        return id;
    }

    @Override
    public String asString() {
        return id.toString();
    }

    @Override
    public boolean equals(Object o) {
        boolean result = false;
        if (this == o) {
            result = true;
        } else if (o instanceof AbstractEntityId) {
            AbstractEntityId other = (AbstractEntityId) o;
            result = Objects.equals(id, other.id);
        }
        return result;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return toStringHelper(this)
                .add("id", id)
                .toString();
    }
}

AbstractEntity.java

package com.example.orm.jpa;
import com.example.util.ArtifactForFramework;
import javax.persistence.EmbeddedId;
import javax.persistence.MappedSuperclass;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Abstract super class for entities. We are assuming that early primary key
 * generation will be used.
 *
 * @param <T> the type of {@link EntityId} that will be used for this entity
 */
@MappedSuperclass
public abstract class AbstractEntity<T extends EntityId> implements Entity<T> {
    @EmbeddedId
    private T id;

    @ArtifactForFramework
    protected AbstractEntity() {
    }

    public AbstractEntity(T id) {
        this.id = checkNotNull(id);
    }

    @Override
    public T getId() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        boolean result = false;
        if (this == obj) {
            result = true;
        } else if (obj instanceof AbstractEntity) {
            AbstractEntity other = (AbstractEntity) obj;
            result = Objects.equals(id, other.id);
        }
        return result;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return toStringHelper(this)
                .add("id", id)
                .toString();
    }
}

EntityId.java

package com.example.orm.jpa;
import java.io.Serializable;

/**
 * Interface for primary keys of entities.
 *
 * @param <T> the underlying type of the entity id
 */
public interface EntityId<T> extends Serializable {
    T getId();
    String asString();
}

UserRepositoryCustom.java

package com.example.copsboot.user;

public interface UserRepositoryCustom {
    UserId nextId();
}

UserRepository.java

package com.example.copsboot.user;

import org.springframework.data.repository.CrudRepository;
import java.util.UUID;

public interface UserRepository extends CrudRepository<User, UUID>, UserRepositoryCustom {
}

UserRepositoryImpl.java

package com.example.copsboot.user;
import com.example.orm.jpa.UniqueIdGenerator;
import java.util.UUID;

public class UserRepositoryImpl implements UserRepositoryCustom {
    private final UniqueIdGenerator<UUID> generator;

    public UserRepositoryImpl(UniqueIdGenerator<UUID> generator) {
        this.generator = generator;
    }

    @Override
    public UserId nextId() {
        return new UserId(generator.getNextUniqueId());
    }
}

解决方案

1. 实现UniqueIdGenerator接口及UUID生成器

首先定义UniqueIdGenerator接口:

package com.example.orm.jpa;

public interface UniqueIdGenerator<T> {
    // 生成下一个唯一ID
    T getNextUniqueId();
}

然后实现UUID版本的生成器,Spring会自动注入这个实例到UserRepositoryImpl中:

package com.example.orm.jpa;

import java.util.UUID;
import org.springframework.stereotype.Component;

@Component
public class UuidUniqueIdGenerator implements UniqueIdGenerator<UUID> {
    @Override
    public UUID getNextUniqueId() {
        // 生成随机UUID作为唯一ID
        return UUID.randomUUID();
    }
}

2. 补充UserId类(缺失的实现)

因为UserRepositoryImpl中用到了UserId,它需要继承AbstractEntityId:

package com.example.copsboot.user;

import com.example.orm.jpa.AbstractEntityId;
import java.util.UUID;

public class UserId extends AbstractEntityId<UUID> {
    public UserId(UUID id) {
        super(id);
    }
}

为什么要使用这些抽象实体类做ID生成

  • 类型安全防护:每个实体的ID都是专属类型(比如UserId、OrderId),编译期就能阻止把用户ID传入订单相关方法的错误,避免运行时的ID混用问题。
  • 统一ID行为:AbstractEntityId已经封装了equals、hashCode、toString等通用逻辑,所有实体ID都能复用,保证ID比较、哈希计算的一致性,不用重复编写代码。
  • 适配JPA规范:AbstractEntity使用@EmbeddedId和@MappedSuperclass,让自定义ID类型符合JPA嵌入式主键的规范,同时实现配置复用,减少实体类的重复注解。
  • 支持提前生成ID:这种设计允许在实体持久化到数据库前就生成ID,适合需要提前使用ID的业务场景,也避免了数据库自增ID在分布式环境下的局限性。
  • 扩展性强:如果后续需要更换ID生成策略(比如换成雪花算法),只需要实现新的UniqueIdGenerator,不需要修改实体类和ID类,符合开闭原则。

内容的提问来源于stack exchange,提问作者user17341714

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:45:00