一、核心概念
什么是 MetaObjectHandler
?
MetaObjectHandler
是 MyBatis-Plus 提供的一个元对象处理器接口,用于在执行 INSERT 或 UPDATE 操作时,自动为实体类中标记了 @TableField(fill = FieldFill.XXX)
的字段填充指定值。
核心作用
- 自动填充创建时间、更新时间
- 自动填充创建人、更新人
- 避免在业务代码中手动设置这些字段
- 保证数据一致性,防止遗漏
关键注解
注解 | 说明 |
---|---|
@TableField(fill = FieldFill.INSERT) |
插入时自动填充 |
@TableField(fill = FieldFill.UPDATE) |
更新时自动填充 |
@TableField(fill = FieldFill.INSERT_UPDATE) |
插入和更新时都填充 |
二、操作步骤(超详细)
第一步:在实体类中定义需填充的字段
以 UserDO.java
为例:
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class UserDO {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
// 插入时自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 插入和更新时都填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// 假设需要记录操作人
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
}
⚠️ 注意:
createTime
、updateTime
等字段不要在业务代码中手动 set,否则会覆盖自动填充值。
第二步:创建 MetaObjectHandler
实现类
创建 src/main/java/com/example/demo/config/MyMetaObjectHandler.java
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义元对象处理器
*/
@Component // 必须添加 @Component,让 Spring 扫描到
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时填充
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("【MyMetaObjectHandler】执行插入填充");
// 填充 createTime
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 填充 updateTime(插入时也填充)
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 填充 createBy
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUsername());
// 填充 updateBy
this.strictInsertFill(metaObject, "updateBy", String.class, getCurrentUsername());
}
/**
* 更新时填充
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("【MyMetaObjectHandler】执行更新填充");
// 只填充 updateTime
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 填充 updateBy
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUsername());
}
/**
* 获取当前登录用户名(模拟)
* 实际项目中应从 SecurityContext、ThreadLocal 或 JWT 中获取
*/
private String getCurrentUsername() {
// 示例:从 Spring Security 获取
// return SecurityContextHolder.getContext().getAuthentication().getName();
return "admin"; // 模拟用户
}
}
第三步:确保 Spring Boot 扫描到该组件
在 Spring Boot 主类或配置类上确保包路径正确:
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
只要
MyMetaObjectHandler
在主类包路径下或子包中,@Component
就能被扫描到。
第四步:测试自动填充功能
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public String save(@RequestBody UserDO user) {
// 注意:不要手动设置 createTime/updateTime
// user.setCreateTime(LocalDateTime.now()); // ❌ 不要这样做
// user.setUpdateTime(LocalDateTime.now()); // ❌ 不要这样做
userService.save(user);
return "保存成功";
}
@PutMapping
public String update(@RequestBody UserDO user) {
userService.updateById(user);
return "更新成功";
}
}
测试流程:
- 调用
POST /user
添加用户 → 数据库create_time
和update_time
自动填充当前时间,create_by
和update_by
为admin
- 调用
PUT /user
更新用户 →update_time
和update_by
自动更新
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
字段未填充 | MetaObjectHandler 未被 Spring 管理 |
添加 @Component |
字段未填充 | 实体类未加 @TableField(fill = ...) |
确保字段正确标注 |
字段填充为 null | strictInsertFill 第四个参数为 null |
检查值生成逻辑(如 getCurrentUsername() 返回 null) |
启动报错 No qualifying bean... |
多个 MetaObjectHandler 冲突 |
项目中只能有一个生效的实现类 |
更新时不填充 | updateFill 方法未实现或逻辑错误 |
检查 updateFill 方法 |
填充值不是最新 | 缓存或时钟问题 | 确保 LocalDateTime.now() 获取的是当前时间 |
四、注意事项
- ✅ 必须添加
@Component
:否则 Spring 无法注入,填充不会生效。 - ✅ 字段不能手动 set:业务代码中设置的值会覆盖自动填充值。
- ✅ 类型必须匹配:
strictInsertFill
的泛型类型必须与字段类型一致(如LocalDateTime
对应LocalDateTime.class
)。 - ✅ 避免 NPE:
getCurrentUsername()
等方法要防止返回null
。 - ✅ 性能影响:填充逻辑在每次 INSERT/UPDATE 时执行,避免复杂计算。
- ✅ 逻辑删除不影响填充:调用
removeById
也会触发updateFill
(因为是 UPDATE 操作)。 - ✅ 批量操作:
saveBatch
、updateBatchById
也会触发填充。
五、使用技巧
1. 使用 strictInsertFill
vs fillStrategy
- ✅ 推荐
strictInsertFill
:类型安全,编译期检查。 - ❌
fillStrategy
已过时,不推荐使用。
// 推荐 ✅
this.strictInsertFill(metaObject, "fieldName", String.class, "value");
// 不推荐 ❌
this.fillStrategy(metaObject, "fieldName", "value");
2. 动态获取当前用户
private String getCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
return auth.getName();
}
return "system"; // 默认值
}
3. 填充雪花 ID(非主键场景)
// 某些业务字段需要唯一ID
this.strictInsertFill(metaObject, "businessId", String.class, IdWorker.getIdStr());
4. 条件填充
@Override
public void insertFill(MetaObject metaObject) {
Object createTime = getFieldValByName("createTime", metaObject);
if (createTime == null) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
// 只有 createTime 为空时才填充
}
六、最佳实践与性能优化
✅ 最佳实践
实践 | 说明 |
---|---|
✅ 统一处理时间字段 | 所有表的 create_time 、update_time 都自动填充 |
✅ 记录操作人 | create_by 、update_by 提高数据可追溯性 |
✅ 日志记录 | 在 MetaObjectHandler 中添加日志,便于调试 |
✅ 默认值兜底 | 用户名获取失败时返回 "system" 或 "unknown" |
✅ 避免业务逻辑 | 填充器只做填充,不包含复杂业务判断 |
✅ 单元测试 | 编写测试用例验证填充逻辑是否正确 |
⚡ 性能优化建议
- ✅ 轻量级逻辑:填充方法内避免数据库查询、远程调用。
- ✅ 缓存用户信息:若用户信息获取成本高,可缓存
ThreadLocal
。 - ✅ 避免反射开销:MyBatis-Plus 内部已优化,无需担心。
- ✅ 批量操作友好:
strictInsertFill
在批量插入时仍高效。
七、高级用法:自定义填充策略
场景:根据字段类型自动填充
@Override
public void insertFill(MetaObject metaObject) {
LocalDateTime now = LocalDateTime.now();
// 遍历所有字段,自动填充所有 LocalDateTime 类型的 INSERT 字段
for (String fieldName : metaObject.getOriginalPropertyNames()) {
Object value = getFieldValByName(fieldName, metaObject);
if (value == null) {
Class<?> type = metaObject.getGetterType(fieldName);
TableField tableField = getTableFieldAnnotation(metaObject, fieldName);
if (LocalDateTime.class.equals(type) && tableField != null) {
if (tableField.fill() == FieldFill.INSERT || tableField.fill() == FieldFill.INSERT_UPDATE) {
setFieldValByName(fieldName, now, metaObject);
}
}
}
}
}
⚠️ 注意:此方式较复杂,一般推荐显式调用
strictInsertFill
。
总结
MetaObjectHandler
是 MyBatis-Plus 实现字段自动填充的核心机制,通过简单实现接口,即可为 createTime
、updateTime
、createBy
等字段自动赋值,极大提升开发效率和数据一致性。
🚀 核心要点:
- 实体类加
@TableField(fill = ...)
- 创建类实现
MetaObjectHandler
- 添加
@Component
- 重写
insertFill
和updateFill
- 使用
strictInsertFill
安全填充- 不要手动 set 填充字段