关于将公共字段放入父类的设计与实践

将实体共有的字段(如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;
    // ...
}

为什么这是最佳实践?

  1. 代码复用:避免在每个实体中重复定义相同字段
  2. 统一管理:可以集中处理公共字段的逻辑(如ID生成策略)
  3. 维护方便:修改公共字段只需修改父类
  4. 查询便利:可以通过父类类型查询所有实体公共属性

高级用法

  1. 审计字段自动填充(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;
}
  1. 逻辑删除支持
@MappedSuperclass
public abstract class BaseEntity {
    // ...
    
    @Column(name = "is_deleted", columnDefinition = "BIT DEFAULT 0")
    private Boolean deleted = false;
}
  1. 乐观锁控制
@MappedSuperclass
public abstract class BaseEntity {
    // ...
    
    @Version
    private Integer version;
}

注意事项

  1. 访问权限:
    • 父类字段建议使用protected而非private,方便子类访问

    @Column(...)
    protected String code;  // 允许子类直接访问
    
  2. 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());
    }
    
  3. 序列化问题:
    • 实现Serializable接口时注意serialVersionUID

    private static final long serialVersionUID = 1L;
    
  4. 数据库设计:
    • 确保所有子类表的公共字段定义一致

    • 对于code字段,如果要求全局唯一需要额外处理

替代方案比较

方案 优点 缺点 适用场景
@MappedSuperclass 简单直观,表结构扁平化 不支持多态查询 大多数业务实体
@Inheritance 支持多态关系 表结构复杂,性能可能受影响 需要多态查询的场景
组合模式 更灵活 查询稍复杂 需要动态字段的场景

实际项目中的典型结构

src/main/java
└── com/example
    ├── model
    │   ├── BaseEntity.java       # 基础实体
    │   ├── AuditableEntity.java  # 带审计的实体
    │   ├── user
    │   │   ├── User.java         # 用户实体
    │   ├── product
    │   │   ├── Product.java      # 产品实体

通过这种设计,您的项目可以获得:
• 更高的代码一致性

• 更少的重复代码

• 更便捷的全局修改能力

• 更清晰的实体关系结构

posted @ 2025-04-23 20:18  许仙儿  阅读(34)  评论(0)    收藏  举报