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

如何高效管理多JPA基础实体(UUID、Long ID、软删除)避免冗余类?

JPA/Hibernate实体抽象类继承的优化方案探讨

问题背景

目前存在6个供Hibernate实体继承的抽象类:

  • BaseEntity
  • BaseEntityImplUUID
  • BaseEntityImplLong
  • BaseEntitySoftDeletable
  • BaseEntitySoftDeletableImplUUID
  • BaseEntitySoftDeletableImplLong

部分实体类会继承其中一个抽象类,部分则不继承。各抽象类代码如下:

BaseEntity.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntity {
    @Column(name = "created_at", updatable = false, nullable = false)
    protected Instant createdAt;

    @Column(name = "updated_at", nullable = false)
    protected Instant updatedAt;

    @PrePersist
    protected void onCreate() {
        Instant now = Instant.now();
        if (createdAt == null)
            this.createdAt = now;
        this.updatedAt = now;
    }

    @PreUpdate
    protected void onUpdate() {
        this.updatedAt = Instant.now();
    }
}

BaseEntityImplUUID.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntityImplUUID extends BaseEntity {
    @Id
    @Column(length = 36, nullable = false, unique = true, updatable = false)
    protected UUID id;

    @Override
    protected void onCreate() {
        super.onCreate();
        if (this.id == null)
            this.id = UUIDFactory.generate();
    }
}

BaseEntityImplLong.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public abstract class BaseEntityImplLong extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;
}

BaseEntitySoftDeletable.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
public class BaseEntitySoftDeletable extends BaseEntity {
    @Column(name = "deleted_at")
    private Instant deletedAt;

    @Column(name = "is_deleted")
    private Boolean isDeleted;
}

其中BaseEntitySoftDeletableImplUUID继承BaseEntityImplUUID,BaseEntitySoftDeletableImplLong继承BaseEntityImplLong,逻辑与BaseEntitySoftDeletable一致。

已了解两种方案:

  1. 维护当前6个独立抽象类;
  2. 使用@Embeddable和@Embedded实现组合。

但不想使用类膨胀或组合方案,希望基于继承思路优化,需解决两个问题:

  1. 是否存在其他基于继承或类似思路的有效实现方式?
  2. 若没有其他方式,最佳实践是什么?

注:曾尝试自定义注解,但处理Hibernate过滤器和监听器存在难度,期待更优方案。


解答

1. 基于继承思路的替代实现方式

Java是单继承模型,无法直接实现多继承,但可以通过接口+默认方法+实体监听器的组合模拟"多继承"能力,同时避免类膨胀:

步骤1:拆分功能为独立接口

将不同的实体能力拆分为独立接口,明确每个接口的职责:

// 时间追踪能力接口
public interface Timestampable {
    Instant getCreatedAt();
    void setCreatedAt(Instant createdAt);
    Instant getUpdatedAt();
    void setUpdatedAt(Instant updatedAt);
}

// 软删除能力接口
public interface SoftDeletable {
    Instant getDeletedAt();
    void setDeletedAt(Instant deletedAt);
    Boolean getIsDeleted();
    void setIsDeleted(Boolean isDeleted);
}

// UUID类型ID接口
public interface UUIDIdentifiable {
    UUID getId();
    void setId(UUID id);
}

// Long类型ID接口
public interface LongIdentifiable {
    Long getId();
    void setId(Long id);
}

步骤2:统一生命周期处理的实体监听器

把原本在抽象类中的@PrePersist、@PreUpdate等逻辑抽离到监听器中,通过类型判断处理不同接口的实体:

public class EntityListener {
    @PrePersist
    public void onPrePersist(Object entity) {
        Instant now = Instant.now();
        // 初始化时间字段
        if (entity instanceof Timestampable ts) {
            if (ts.getCreatedAt() == null) {
                ts.setCreatedAt(now);
            }
            ts.setUpdatedAt(now);
        }
        // 生成UUID ID(如果需要)
        if (entity instanceof UUIDIdentifiable uuidEntity) {
            if (uuidEntity.getId() == null) {
                uuidEntity.setId(UUIDFactory.generate());
            }
        }
        // 初始化软删除字段
        if (entity instanceof SoftDeletable sd) {
            if (sd.getIsDeleted() == null) {
                sd.setIsDeleted(false);
            }
        }
    }

    @PreUpdate
    public void onPreUpdate(Object entity) {
        if (entity instanceof Timestampable ts) {
            ts.setUpdatedAt(Instant.now());
        }
    }
}

步骤3:实体类组合接口并绑定监听器

实体类只需实现所需的接口,添加对应字段并绑定监听器,无需继承多层抽象类:

@Entity
@EntityListeners(EntityListener.class)
public class User implements Timestampable, LongIdentifiable, SoftDeletable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "created_at", updatable = false, nullable = false)
    private Instant createdAt;

    @Column(name = "updated_at", nullable = false)
    private Instant updatedAt;

    @Column(name = "deleted_at")
    private Instant deletedAt;

    @Column(name = "is_deleted")
    private Boolean isDeleted;

    // 实现接口的getter/setter方法
    @Override
    public Long getId() { return id; }
    @Override
    public void setId(Long id) { this.id = id; }

    @Override
    public Instant getCreatedAt() { return createdAt; }
    @Override
    public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }

    @Override
    public Instant getUpdatedAt() { return updatedAt; }
    @Override
    public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; }

    @Override
    public Instant getDeletedAt() { return deletedAt; }
    @Override
    public void setDeletedAt(Instant deletedAt) { this.deletedAt = deletedAt; }

    @Override
    public Boolean getIsDeleted() { return isDeleted; }
    @Override
    public void setIsDeleted(Boolean isDeleted) { this.isDeleted = isDeleted; }
}

这种方式利用接口的"多实现"特性模拟多继承能力,同时通过监听器统一处理生命周期逻辑,避免了抽象类的层级膨胀。

2. 最佳实践选择

如果上述接口+监听器的方案仍无法满足需求,以下两种是行业内的主流最佳实践:

方案A:保留现有抽象类结构

虽然存在6个抽象类,但每个类职责单一、结构清晰:

  • BaseEntity:提供基础时间追踪能力
  • BaseEntityImplUUID/BaseEntityImplLong:基础时间追踪+对应类型ID生成
  • BaseEntitySoftDeletable:基础时间追踪+软删除能力
  • BaseEntitySoftDeletableImplUUID/BaseEntitySoftDeletableImplLong:时间追踪+软删除+对应类型ID

这种方式的优势是实体类只需一行继承即可获得组合能力,代码简洁,Hibernate的映射和生命周期处理天然支持,无需额外配置。唯一的缺点是类数量较多,但只要命名规范、职责明确,维护成本可控。

方案B:使用@Embeddable实现组合

将不同的能力封装为可嵌入类,实体类通过@Embedded组合所需功能:

@Embeddable
public class TimestampEmbeddable {
    @Column(name = "created_at", updatable = false, nullable = false)
    private Instant createdAt;

    @Column(name = "updated_at", nullable = false)
    private Instant updatedAt;

    // getter/setter方法
}

@Embeddable
public class SoftDeleteEmbeddable {
    @Column(name = "deleted_at")
    private Instant deletedAt;

    @Column(name = "is_deleted")
    private Boolean isDeleted;

    // getter/setter方法
}

// 实体类中组合嵌入类
@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private TimestampEmbeddable timestamps;

    @Embedded
    private SoftDeleteEmbeddable softDelete;

    // 其他业务字段和方法
}

配合实体监听器处理生命周期逻辑,这种方式符合"组合优于继承"的设计原则,类结构更灵活,避免了继承层级的限制。虽然需要手动组合嵌入类,但扩展性更强,后续新增能力只需新增嵌入类即可。

总结

如果坚持基于继承思路,接口+实体监听器是最优替代方案;若可以接受现有模式或组合模式,则:

  • 追求实体类代码简洁,选现有抽象类结构;
  • 追求扩展性和灵活性,选@Embeddable组合方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 06:31:04