Java通用父类设计核心是明确复用边界:只上提语义相同、逻辑一致的字段与方法,如id、createdAt/updatedAt及JPA自动填充;禁放业务逻辑与语义各异的属性(如name);优先用泛型父类、接口+组合替代继承。

Java中设计通用父类,核心不是“写一个万能父类”,而是明确复用边界——只把真正共有的行为和状态上提,避免为复用而继承导致耦合加重。
哪些字段和方法才该放进通用父类
判断标准很简单:子类是否必须拥有相同语义的字段、必须以相同逻辑执行某操作。比如所有实体类都需要 id、createdAt、updatedAt,且都走 JPA 自动填充,这时才适合抽取到 BaseEntity;但若只是“都有名字”,而 name 在用户、订单、商品中含义和校验规则完全不同,就绝不该放进去。
-
id字段类型统一(如Long或UUID),且主键生成策略一致(如@GeneratedValue(strategy = GenerationType.IDENTITY)) -
createdAt/updatedAt使用@CreatedDate/@LastModifiedDate,且依赖@EnableJpaAuditing - 公共工具方法如
toMap()、isValid()必须对所有子类有意义,不能靠强制转型或空实现糊弄
为什么不要在通用父类里写业务逻辑
常见错误是把“保存前校验”“发送通知”这类强业务动作塞进 BaseEntity.save()。问题在于:不同子类的校验规则不同(用户要校验邮箱格式,订单要校验库存),通知渠道也不同(短信 vs Webhook)。一旦这么写,要么子类重写方法绕过逻辑,要么加一堆 if (this instanceof Order) 分支,违背开闭原则。
- 父类中只保留与数据生命周期直接相关的钩子,如
prePersist()(由 JPA 调用),不主动触发业务流 - 业务编排交给 Service 层,用策略模式或事件机制解耦,而不是靠继承传递职责
- 如果真需要统一前置动作,优先考虑
@PrePersist注解 + 公共AuditorAware,而非覆盖方法
泛型父类比普通父类更安全
当父类要操作自身类型(比如返回当前实例用于链式调用),不用 BaseEntity 这种裸类型,改用 BaseEntity 可避免向下转型。Spring Data JPA 的 QueryByExampleExecutor 就是典型应用。
立即学习“Java免费学习笔记(深入)”;
public abstract class BaseEntity> { public T setId(Long id) { // this 是 T 类型,无需转型 return (T) this; } public abstract T copy(); }
- 子类声明时必须指定自身类型:
public class User extends BaseEntity - 避免在父类里写
(User) this这类脆弱转型 - IDE 和编译器能提前发现类型误用,比如
new User().setId(...).copy()返回的就是User,不是BaseEntity
比继承更轻量的替代方案
很多场景下,组合 + 接口比继承更可控。比如“软删除”能力,与其让所有实体继承 SoftDeletableEntity,不如定义 SoftDeletable 接口 + SoftDeleteFilter 拦截器,再配合 JPA 的 @Where 注解。
- 接口定义行为契约:
interface SoftDeletable { Boolean getDeleted(); void setDeleted(Boolean deleted); } - 通用查询过滤通过 Hibernate Filter 实现,不侵入实体继承树
- 需要扩展时(如增加恢复逻辑),新增
Restorable接口即可,不影响现有结构
继承容易锁死设计,而接口+组合允许运行时装配。真正难的是判断“这个功能是不是所有子类都无条件需要”——多数时候答案是否定的。









