一、核心概念

什么是 MetaObjectHandler

MetaObjectHandler 是 MyBatis-Plus 提供的一个元对象处理器接口,用于在执行 INSERTUPDATE 操作时,自动为实体类中标记了 @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;
}

⚠️ 注意:createTimeupdateTime 等字段不要在业务代码中手动 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 "更新成功";
    }
}

测试流程

  1. 调用 POST /user 添加用户 → 数据库 create_timeupdate_time 自动填充当前时间,create_byupdate_byadmin
  2. 调用 PUT /user 更新用户 → update_timeupdate_by 自动更新

三、常见错误与解决方案

错误现象 原因 解决方案
字段未填充 MetaObjectHandler 未被 Spring 管理 添加 @Component
字段未填充 实体类未加 @TableField(fill = ...) 确保字段正确标注
字段填充为 null strictInsertFill 第四个参数为 null 检查值生成逻辑(如 getCurrentUsername() 返回 null)
启动报错 No qualifying bean... 多个 MetaObjectHandler 冲突 项目中只能有一个生效的实现类
更新时不填充 updateFill 方法未实现或逻辑错误 检查 updateFill 方法
填充值不是最新 缓存或时钟问题 确保 LocalDateTime.now() 获取的是当前时间

四、注意事项

  1. 必须添加 @Component:否则 Spring 无法注入,填充不会生效。
  2. 字段不能手动 set:业务代码中设置的值会覆盖自动填充值。
  3. 类型必须匹配strictInsertFill 的泛型类型必须与字段类型一致(如 LocalDateTime 对应 LocalDateTime.class)。
  4. 避免 NPEgetCurrentUsername() 等方法要防止返回 null
  5. 性能影响:填充逻辑在每次 INSERT/UPDATE 时执行,避免复杂计算。
  6. 逻辑删除不影响填充:调用 removeById 也会触发 updateFill(因为是 UPDATE 操作)。
  7. 批量操作saveBatchupdateBatchById 也会触发填充。

五、使用技巧

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_timeupdate_time 都自动填充
✅ 记录操作人 create_byupdate_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 实现字段自动填充的核心机制,通过简单实现接口,即可为 createTimeupdateTimecreateBy 等字段自动赋值,极大提升开发效率和数据一致性。

🚀 核心要点

  1. 实体类加 @TableField(fill = ...)
  2. 创建类实现 MetaObjectHandler
  3. 添加 @Component
  4. 重写 insertFillupdateFill
  5. 使用 strictInsertFill 安全填充
  6. 不要手动 set 填充字段