基础实体类
框架提供了 BaseDO 和 BaseAssignDO 两个基础实体类,用于统一管理数据库实体的标准字段和主键策略。
核心类概览
| 类名 | 说明 | 主键类型 | 适用场景 |
|---|---|---|---|
BaseDO<ID> | 抽象基类 | 泛型(需子类实现) | 自定义主键类型 |
BaseAssignDO | 雪花ID实现 | Long(自动生成) | 大部分业务场景(推荐) |
BaseDO 抽象基类
类定义
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 个标准字段,用于记录数据的创建和修改信息:
| 字段 | 类型 | 数据库列名 | 自动填充时机 | 说明 |
|---|---|---|---|---|
createTime | LocalDateTime | create_time | INSERT | 创建时间 |
createUser | String | create_user | INSERT | 创建人 |
updateTime | LocalDateTime | update_time | INSERT、UPDATE | 修改时间 |
updateUser | String | update_user | INSERT、UPDATE | 修改人 |
字段常量
BaseDO 还定义了 7 个字段常量,方便在代码中引用:
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"使用示例:
// 在查询条件中使用字段常量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc(BaseDO.FIELD_UPDATE_TIME); // 按修改时间降序
wrapper.eq(BaseDO.FIELD_CREATE_USER, userId); // 按创建人筛选自动填充配置
BaseDO 的字段使用了 MyBatis-Plus 的自动填充注解:
// 创建时间 - 插入时自动填充,且不能为 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实现
类定义
@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 类型主键:
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;雪花ID特点:
- ✅ 全局唯一:分布式环境下保证唯一性
- ✅ 趋势递增:有利于数据库索引性能
- ✅ 无需数据库交互:客户端生成,减轻数据库压力
- ✅ 高性能:每秒可生成数百万个不重复ID
2. JSON 序列化处理
使用 Jackson 的 ToStringSerializer 将 Long 类型转换为字符串:
@JsonSerialize(using = ToStringSerializer.class)
private Long id;为什么需要转换为字符串?
JavaScript 的 Number 类型最大安全整数为 2^53 - 1(约 16 位),而雪花ID是 19 位,直接传给前端会导致精度丢失。
对比示例:
| 场景 | 后端值 | 前端接收值 | 结果 |
|---|---|---|---|
不使用 ToStringSerializer | 1234567890123456789 | 1234567890123456800 | ❌ 精度丢失 |
使用 ToStringSerializer | 1234567890123456789 | "1234567890123456789" | ✅ 精度保留 |
使用示例
示例1:标准使用(推荐)
继承 BaseAssignDO 获得雪花ID和标准字段:
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 自动拥有以下字段:
private Long id; // 主键(雪花ID)
private LocalDateTime createTime; // 创建时间
private String createUser; // 创建人
private LocalDateTime updateTime; // 修改时间
private String updateUser; // 修改人示例2:自定义主键类型
如果需要使用其他类型的主键(如 String、UUID),可以直接继承 BaseDO:
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:数据库自增主键
使用数据库自增主键(不推荐,分布式环境下可能冲突):
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 字段:
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 语句:
// 逻辑删除(实际执行 UPDATE)
productService.removeById(1L);
// 生成的 SQL
// UPDATE sys_product SET logic_delete = 1 WHERE id = 1 AND logic_delete = 0示例5:乐观锁
如果需要乐观锁功能,可以在实体类中添加 version 字段:
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;
}配置乐观锁后,更新时会自动比较版本号:
// 更新订单
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)数据库表设计
标准表结构
建议按以下规范设计表结构,充分利用框架的自动填充功能:
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='用户表';字段设计规范
| 字段 | 数据类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
id | BIGINT(20) | 是 | - | 主键(雪花ID) |
create_time | DATETIME | 是 | - | 创建时间(自动填充) |
create_user | VARCHAR(50) | 否 | NULL | 创建人(自动填充) |
update_time | DATETIME | 是 | - | 修改时间(自动填充) |
update_user | VARCHAR(50) | 否 | NULL | 修改人(自动填充) |
logic_delete | TINYINT(1) | 否 | 0 | 逻辑删除(0:未删除,1:已删除) |
version | INT(11) | 否 | 1 | 版本号(乐观锁) |
最佳实践
✅ 推荐做法
统一继承 BaseAssignDO
javapublic class User extends BaseAssignDO { } // ✅ 推荐使用字段常量
javawrapper.orderByDesc(BaseDO.FIELD_UPDATE_TIME); // ✅ 类型安全添加表注释和索引
sqlKEY `idx_create_time` (`create_time`) -- ✅ 便于按时间查询逻辑删除优于物理删除
java@TableLogic(value = "0", delval = "1") // ✅ 数据可追溯 private Integer logicDelete;并发更新使用乐观锁
java@Version // ✅ 防止并发更新冲突 private Integer version;
❌ 不推荐做法
❌ 不继承基类,手动定义标准字段
javapublic class User { // ❌ 不统一 private Long id; private LocalDateTime createTime; // ... }❌ 使用字符串常量
javawrapper.orderByDesc("updateTime"); // ❌ 容易拼写错误❌ 不添加索引
sql-- ❌ 按时间查询会很慢❌ 直接物理删除数据
javauserMapper.deleteById(1L); // ❌ 数据无法恢复❌ 自增主键用于分布式系统
java@TableId(type = IdType.AUTO) // ❌ 分布式环境下可能冲突 private Long id;
常见问题
Q: 为什么推荐使用 BaseAssignDO?
A: 雪花ID的优势:
- 全局唯一,适合分布式系统
- 趋势递增,有利于数据库索引性能
- 无需数据库交互,生成效率高
- 包含时间戳信息,方便排序
Q: 如何自定义字段的自动填充逻辑?
A: 可以自定义 MetaObjectHandler 实现类:
@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 注解覆盖:
@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: 分步迁移:
新表使用 BaseDO
javapublic class NewEntity extends BaseAssignDO { }旧表逐步改造
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 { }数据迁移
sql-- 填充历史数据的时间字段 UPDATE old_table SET create_time = NOW(), update_time = NOW() WHERE create_time IS NULL;
下一步
- 增强 Service 层 - 了解如何使用
BaseService进行 CRUD 操作 - 自动填充 - 深入了解自动填充机制
- 校验与检查 - 学习数据校验注解的使用
