如何高效管理多JPA基础实体(UUID、Long ID、软删除)避免冗余类?
问题背景
目前存在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一致。
已了解两种方案:
- 维护当前6个独立抽象类;
- 使用
@Embeddable和@Embedded实现组合。
但不想使用类膨胀或组合方案,希望基于继承思路优化,需解决两个问题:
- 是否存在其他基于继承或类似思路的有效实现方式?
- 若没有其他方式,最佳实践是什么?
注:曾尝试自定义注解,但处理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

