Skip to content

基础实体类

框架提供了 BaseDOBaseAssignDO 两个基础实体类,用于统一管理数据库实体的标准字段和主键策略。

核心类概览

类名说明主键类型适用场景
BaseDO<ID>抽象基类泛型(需子类实现)自定义主键类型
BaseAssignDO雪花ID实现Long(自动生成)大部分业务场景(推荐)

BaseDO 抽象基类

类定义

java
public abstract class BaseDO<ID> implements Serializable {
    
    // 抽象方法,由子类实现
    public abstract void setId(ID id);
    public abstract ID getId();
    
    // 标准字段
    public LocalDateTime createTime;  // 创建时间
    public String createUser;         // 创建人
    public LocalDateTime updateTime;  // 修改时间
    public String updateUser;         // 修改人
}

标准字段说明

BaseDO 提供了 4 个标准字段,用于记录数据的创建和修改信息:

字段类型数据库列名自动填充时机说明
createTimeLocalDateTimecreate_timeINSERT创建时间
createUserStringcreate_userINSERT创建人
updateTimeLocalDateTimeupdate_timeINSERT、UPDATE修改时间
updateUserStringupdate_userINSERT、UPDATE修改人

字段常量

BaseDO 还定义了 7 个字段常量,方便在代码中引用:

java
BaseDO.FIELD_ID            // "id"
BaseDO.FIELD_CREATE_TIME   // "createTime"
BaseDO.FIELD_CREATE_USER   // "createUser"
BaseDO.FIELD_UPDATE_TIME   // "updateTime"
BaseDO.FIELD_UPDATE_USER   // "updateUser"
BaseDO.FIELD_LOGIC_DELETE  // "logicDelete"
BaseDO.FIELD_VERSION       // "version"

使用示例

java
// 在查询条件中使用字段常量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc(BaseDO.FIELD_UPDATE_TIME);  // 按修改时间降序
wrapper.eq(BaseDO.FIELD_CREATE_USER, userId);   // 按创建人筛选

自动填充配置

BaseDO 的字段使用了 MyBatis-Plus 的自动填充注解:

java
// 创建时间 - 插入时自动填充,且不能为 null
@TableField(value = "create_time", fill = FieldFill.INSERT, insertStrategy = FieldStrategy.NOT_NULL)
public LocalDateTime createTime;

// 创建人 - 插入时自动填充
@TableField(value = "create_user", fill = FieldFill.INSERT)
public String createUser;

// 修改时间 - 插入和更新时都自动填充,且不能为 null
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE,
        insertStrategy = FieldStrategy.NOT_NULL, updateStrategy = FieldStrategy.NOT_NULL)
public LocalDateTime updateTime;

// 修改人 - 插入和更新时都自动填充
@TableField(value = "update_user", fill = FieldFill.INSERT_UPDATE)
public String updateUser;

自动填充原理

框架通过 KsMybatisPlusMetaObjectHandler 实现自动填充,详见自动填充

BaseAssignDO 雪花ID实现

类定义

java
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BaseAssignDO extends BaseDO<Long> implements Serializable {
    
    /**
     * 唯一ID(雪花算法)
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
}

核心特性

1. 雪花ID算法

使用 MyBatis-Plus 的雪花算法(Snowflake)自动生成 19 位的 Long 类型主键:

java
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;

雪花ID特点

  • 全局唯一:分布式环境下保证唯一性
  • 趋势递增:有利于数据库索引性能
  • 无需数据库交互:客户端生成,减轻数据库压力
  • 高性能:每秒可生成数百万个不重复ID

2. JSON 序列化处理

使用 Jackson 的 ToStringSerializer 将 Long 类型转换为字符串:

java
@JsonSerialize(using = ToStringSerializer.class)
private Long id;

为什么需要转换为字符串?

JavaScript 的 Number 类型最大安全整数为 2^53 - 1(约 16 位),而雪花ID是 19 位,直接传给前端会导致精度丢失

对比示例

场景后端值前端接收值结果
不使用 ToStringSerializer12345678901234567891234567890123456800❌ 精度丢失
使用 ToStringSerializer1234567890123456789"1234567890123456789"✅ 精度保留

使用示例

示例1:标准使用(推荐)

继承 BaseAssignDO 获得雪花ID和标准字段:

java
import host.springboot.framework.mybatisplus.domain.BaseAssignDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
public class User extends BaseAssignDO {
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 邮箱
     */
    private String email;
    
    /**
     * 手机号
     */
    private String phone;
    
    /**
     * 状态(0: 禁用, 1: 启用)
     */
    private Integer status;
}

继承后,User 自动拥有以下字段:

java
private Long id;                   // 主键(雪花ID)
private LocalDateTime createTime;  // 创建时间
private String createUser;         // 创建人
private LocalDateTime updateTime;  // 修改时间
private String updateUser;         // 修改人

示例2:自定义主键类型

如果需要使用其他类型的主键(如 String、UUID),可以直接继承 BaseDO

java
import host.springboot.framework.mybatisplus.domain.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_config")
public class Config extends BaseDO<String> {
    
    /**
     * 配置键(使用字符串作为主键)
     */
    @TableId(value = "id", type = IdType.INPUT)
    private String id;
    
    /**
     * 配置值
     */
    private String value;
    
    /**
     * 描述
     */
    private String description;
}

示例3:数据库自增主键

使用数据库自增主键(不推荐,分布式环境下可能冲突):

java
import host.springboot.framework.mybatisplus.domain.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_log")
public class Log extends BaseDO<Long> {
    
    /**
     * 主键(数据库自增)
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    /**
     * 日志内容
     */
    private String content;
}

示例4:逻辑删除

如果需要逻辑删除功能,可以在实体类中添加 logicDelete 字段:

java
import host.springboot.framework.mybatisplus.domain.BaseAssignDO;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_product")
public class Product extends BaseAssignDO {
    
    /**
     * 商品名称
     */
    private String name;
    
    /**
     * 价格
     */
    private BigDecimal price;
    
    /**
     * 逻辑删除(0: 未删除, 1: 已删除)
     */
    @TableLogic(value = "0", delval = "1")
    private Integer logicDelete;
}

配置逻辑删除后,调用 removeById 等删除方法时会自动转为 UPDATE 语句:

java
// 逻辑删除(实际执行 UPDATE)
productService.removeById(1L);

// 生成的 SQL
// UPDATE sys_product SET logic_delete = 1 WHERE id = 1 AND logic_delete = 0

示例5:乐观锁

如果需要乐观锁功能,可以在实体类中添加 version 字段:

java
import host.springboot.framework.mybatisplus.domain.BaseAssignDO;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_order")
public class Order extends BaseAssignDO {
    
    /**
     * 订单号
     */
    private String orderNo;
    
    /**
     * 订单金额
     */
    private BigDecimal amount;
    
    /**
     * 版本号(乐观锁)
     */
    @Version
    private Integer version;
}

配置乐观锁后,更新时会自动比较版本号:

java
// 更新订单
Order order = orderService.getById(1L);  // version = 1
order.setAmount(new BigDecimal("100.00"));
orderService.updateById(order);

// 生成的 SQL
// UPDATE sys_order SET amount = 100.00, version = 2 
// WHERE id = 1 AND version = 1

// 如果 version 不匹配,更新失败(返回 false)

数据库表设计

标准表结构

建议按以下规范设计表结构,充分利用框架的自动填充功能:

sql
CREATE TABLE `sys_user` (
  -- 主键(19位雪花ID)
  `id` BIGINT(20) NOT NULL COMMENT '主键ID',
  
  -- 业务字段
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
  `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
  `status` TINYINT(1) NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
  
  -- 标准字段(自动填充)
  `create_time` DATETIME NOT NULL COMMENT '创建时间',
  `create_user` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
  `update_time` DATETIME NOT NULL COMMENT '修改时间',
  `update_user` VARCHAR(50) DEFAULT NULL COMMENT '修改人',
  
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_update_time` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

字段设计规范

字段数据类型是否必填默认值说明
idBIGINT(20)-主键(雪花ID)
create_timeDATETIME-创建时间(自动填充)
create_userVARCHAR(50)NULL创建人(自动填充)
update_timeDATETIME-修改时间(自动填充)
update_userVARCHAR(50)NULL修改人(自动填充)
logic_deleteTINYINT(1)0逻辑删除(0:未删除,1:已删除)
versionINT(11)1版本号(乐观锁)

最佳实践

✅ 推荐做法

  1. 统一继承 BaseAssignDO

    java
    public class User extends BaseAssignDO { }  // ✅ 推荐
  2. 使用字段常量

    java
    wrapper.orderByDesc(BaseDO.FIELD_UPDATE_TIME);  // ✅ 类型安全
  3. 添加表注释和索引

    sql
    KEY `idx_create_time` (`create_time`)  -- ✅ 便于按时间查询
  4. 逻辑删除优于物理删除

    java
    @TableLogic(value = "0", delval = "1")  // ✅ 数据可追溯
    private Integer logicDelete;
  5. 并发更新使用乐观锁

    java
    @Version  // ✅ 防止并发更新冲突
    private Integer version;

❌ 不推荐做法

  1. ❌ 不继承基类,手动定义标准字段

    java
    public class User {  // ❌ 不统一
        private Long id;
        private LocalDateTime createTime;
        // ...
    }
  2. ❌ 使用字符串常量

    java
    wrapper.orderByDesc("updateTime");  // ❌ 容易拼写错误
  3. ❌ 不添加索引

    sql
    -- ❌ 按时间查询会很慢
  4. ❌ 直接物理删除数据

    java
    userMapper.deleteById(1L);  // ❌ 数据无法恢复
  5. ❌ 自增主键用于分布式系统

    java
    @TableId(type = IdType.AUTO)  // ❌ 分布式环境下可能冲突
    private Long id;

常见问题

Q: 为什么推荐使用 BaseAssignDO?

A: 雪花ID的优势

  • 全局唯一,适合分布式系统
  • 趋势递增,有利于数据库索引性能
  • 无需数据库交互,生成效率高
  • 包含时间戳信息,方便排序

Q: 如何自定义字段的自动填充逻辑?

A: 可以自定义 MetaObjectHandler 实现类:

java
@Component
public class CustomMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        // 自定义创建时间填充逻辑
        this.strictInsertFill(metaObject, "createTime", 
            () -> LocalDateTime.now().plusHours(8), LocalDateTime.class);
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        // 自定义修改时间填充逻辑
        this.strictUpdateFill(metaObject, "updateTime", 
            LocalDateTime::now, LocalDateTime.class);
    }
}

详见自动填充

Q: 如何禁用某个字段的自动填充?

A: 使用 @TableField 注解覆盖:

java
@TableField(fill = FieldFill.DEFAULT)  // 禁用自动填充
private LocalDateTime createTime;

Q: BaseDO 的字段为什么是 public?

A: 为了让 MyBatis-Plus 能够直接访问字段进行自动填充,而不需要通过反射调用 setter 方法,提高性能。同时使用 Lombok 的 @Data 注解会自动生成 getter/setter 方法,不影响正常使用。

Q: 雪花ID会用完吗?

A: 不会。雪花ID是 64 位的 Long 类型,理论上可以使用 69 年。即使每秒生成 100 万个ID,也需要数千年才能用完。

Q: 如何在已有项目中迁移到 BaseDO?

A: 分步迁移:

  1. 新表使用 BaseDO

    java
    public class NewEntity extends BaseAssignDO { }
  2. 旧表逐步改造

    java
    // 先添加标准字段到数据库
    ALTER TABLE old_table ADD COLUMN create_time DATETIME;
    ALTER TABLE old_table ADD COLUMN update_time DATETIME;
    
    // 再让实体类继承 BaseDO
    public class OldEntity extends BaseAssignDO { }
  3. 数据迁移

    sql
    -- 填充历史数据的时间字段
    UPDATE old_table SET create_time = NOW(), update_time = NOW() 
    WHERE create_time IS NULL;

下一步

扩展阅读

Released under the Apache-2.0 License