关于将公共字段放入父类的设计与实践
将实体共有的字段(如ID、CODE、NAME等)放入父类是非常推荐的做法,这符合面向对象设计的"DRY"(Don't Repeat Yourself)原则。以下是具体实现方案和注意事项:
推荐方案:使用 @MappedSuperclass
基础实现
import javax.persistence.*;
import java.io.Serializable;
@MappedSuperclass // 关键注解,表示这是映射的超类而非实体
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "code", length = 50, unique = true, nullable = false)
private String code;
@Column(name = "name", length = 100, nullable = false)
private String name;
// 公共审计字段
@Column(name = "created_time", updatable = false)
private LocalDateTime createdTime;
@Column(name = "updated_time")
private LocalDateTime updatedTime;
// getters/setters
// 建议添加equals()和hashCode()(基于id或code)
}
子类实现示例
@Entity
@Table(name = "sys_user")
public class User extends BaseEntity {
// 特有字段
private String email;
private String phone;
// ...
}
@Entity
@Table(name = "sys_role")
public class Role extends BaseEntity {
// 特有字段
private String description;
// ...
}
为什么这是最佳实践?
- 代码复用:避免在每个实体中重复定义相同字段
- 统一管理:可以集中处理公共字段的逻辑(如ID生成策略)
- 维护方便:修改公共字段只需修改父类
- 查询便利:可以通过父类类型查询所有实体公共属性
高级用法
- 审计字段自动填充(Spring Data JPA)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
// ...
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdTime;
@LastModifiedDate
private LocalDateTime updatedTime;
@CreatedBy
private String createdBy;
@LastModifiedBy
private String modifiedBy;
}
- 逻辑删除支持
@MappedSuperclass
public abstract class BaseEntity {
// ...
@Column(name = "is_deleted", columnDefinition = "BIT DEFAULT 0")
private Boolean deleted = false;
}
- 乐观锁控制
@MappedSuperclass
public abstract class BaseEntity {
// ...
@Version
private Integer version;
}
注意事项
-
访问权限:
• 父类字段建议使用protected而非private,方便子类访问@Column(...) protected String code; // 允许子类直接访问 -
equals()/hashCode():
• 在父类中实现时要注意只基于不可变字段(如ID或code)@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BaseEntity)) return false; BaseEntity that = (BaseEntity) o; return Objects.equals(getCode(), that.getCode()); } -
序列化问题:
• 实现Serializable接口时注意serialVersionUIDprivate static final long serialVersionUID = 1L; -
数据库设计:
• 确保所有子类表的公共字段定义一致• 对于
code字段,如果要求全局唯一需要额外处理
替代方案比较
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
@MappedSuperclass |
简单直观,表结构扁平化 | 不支持多态查询 | 大多数业务实体 |
@Inheritance |
支持多态关系 | 表结构复杂,性能可能受影响 | 需要多态查询的场景 |
| 组合模式 | 更灵活 | 查询稍复杂 | 需要动态字段的场景 |
实际项目中的典型结构
src/main/java
└── com/example
├── model
│ ├── BaseEntity.java # 基础实体
│ ├── AuditableEntity.java # 带审计的实体
│ ├── user
│ │ ├── User.java # 用户实体
│ ├── product
│ │ ├── Product.java # 产品实体
通过这种设计,您的项目可以获得:
• 更高的代码一致性
• 更少的重复代码
• 更便捷的全局修改能力
• 更清晰的实体关系结构
浙公网安备 33010602011771号