MyBatis-Plus SQL自动填充字段

1. 前言

在企业级开发中,维护数据的审计字段(create_time, update_time, creator, updater)是必不可少的环节。MyBatis-Plus (MP) 提供了 MetaObjectHandler 接口来实现这一逻辑的自动化。

然而,很多开发者对这一机制存在误解,认为只要实现了 Handler 接口,填充逻辑就会自动生效。事实上,MP 内部存在一套严格的**"短路"触发机制**。

本文将深入剖析这套机制的运行原理,并对比"强制赋值"与"标准填充"的区别,给出生产环境下的最佳实践代码。

2. 核心运行原理:短路机制

MyBatis-Plus 在执行 SQL 之前,并不会无条件地调用 MetaObjectHandler。为了性能优化,它在启动阶段构建元数据时,就已经决定了哪些表需要填充。

2.1 启动阶段:元数据解析

应用启动时,MP 会解析所有的实体类(Entity),生成对应的 TableInfo 对象。在此过程中,框架会遍历实体类的所有字段,检查是否存在 @TableField(fill = ...) 注解。

  • isWithInsertFill : 如果检测到任意一个字段包含 INSERTINSERT_UPDATE 策略,该标记置为 true
  • isWithUpdateFill : 如果检测到任意一个字段包含 UPDATEINSERT_UPDATE 策略,该标记置为 true

2.2 运行阶段:条件执行

当执行 BaseMapper.insert()BaseMapper.update() 时,MybatisParameterHandler 会执行如下逻辑(伪代码):

java 复制代码
// MybatisParameterHandler.java
public void processParameter(Object parameter) {
    // ...
    // 关键判断:
    // 1. 全局配置中是否存在 MetaObjectHandler
    // 2. 当前表元数据 (TableInfo) 是否标记了需要填充
    if (metaObjectHandler != null && tableInfo.isWithInsertFill()) {
        metaObjectHandler.insertFill(metaObject);
    }
    // ...
}

结论 :如果实体类中没有任何一个字段标注了 @TableField(fill = ...)tableInfo.isWithInsertFill()falseinsertFill 方法会被直接跳过,根本不会执行


3. 规范的写法示例

为了确保代码的可维护性和健壮性,我们应当遵循"配置驱动"的原则,即:实体类声明策略,Handler 执行策略

3.1 实体类配置(必须)

必须在基类或实体类中明确标记哪些字段需要填充。

java 复制代码
@Data
public class BaseDO {

    /**
     * 创建时间
     * 策略:仅在插入时填充
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     * 策略:插入和更新时均填充
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 创建人
     * 策略:仅在插入时填充
     */
    @TableField(fill = FieldFill.INSERT)
    private String creator;

    /**
     * 更新人
     * 策略:插入和更新时均填充
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updater;
}

3.2 处理器实现(推荐)

推荐使用 strictInsertFillstrictUpdateFill 方法。
优点 :MP 会在内部再次校验字段是否存在、是否标记了对应的 fill 策略以及属性类型是否匹配,比直接 Set 值更安全。

java 复制代码
@Component
@Slf4j
public class DefaultDBFieldHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 1. 填充创建时间 (仅当字段为空时填充)
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        
        // 2. 填充更新时间
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());

        // 3. 填充当前用户信息
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId != null) {
            String userIdStr = userId.toString();
            this.strictInsertFill(metaObject, "creator", String.class, userIdStr);
            this.strictInsertFill(metaObject, "updater", String.class, userIdStr);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 1. 强制更新 updateTime
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());

        // 2. 更新修改人
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId != null) {
            this.strictUpdateFill(metaObject, "updater", String.class, userId.toString());
        }
    }
}

4. 常见误区:关于"强转赋值"

在实际开发中,有些开发者会使用如下的"强转写法":

java 复制代码
// 不推荐的写法
if (metaObject.getOriginalObject() instanceof BaseDO) {
    BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
    baseDO.setCreateTime(LocalDateTime.now()); // 绕过 MP API,直接操作 Java 对象
}

这种写法为什么也能生效?

只要实体类中至少有一个字段 标记了 @TableField(fill = INSERT),MP 就会打开执行 insertFill 的大门。一旦进入该方法,由于上述代码是直接操作 Java 对象(setter),它不经过 MP 的字段元数据检查,因此无论 createTime 字段本身有没有注解,都会被赋值。

为什么不推荐?

  1. 逻辑不一致 :如果 createTime 忘记加注解,但通过强转代码却被填充了,这会导致代码行为与注解声明不符,增加维护难度。
  2. 耦合度过高 :强依赖 BaseDO 类。如果存在不继承 BaseDO 但也需要填充的表,该逻辑失效。
  3. 违背设计初衷 :绕过了 MP 的 strict (严格) 检查机制,丧失了框架提供的类型安全和策略校验功能。
相关推荐
evans在进步4 小时前
Tomcat NIO 工作原理详解:从 Acceptor、Poller 到容器处理链
java·tomcat·nio
姜太小白8 小时前
【Oracle】记排查sh脚本调用SQL执行卡顿的排查总结
数据库·sql·oracle
镜舟科技10 小时前
为什么 Text-to-SQL 总是停在 Demo?
数据库·sql·demo·text-to-sql·镜舟科技·mip·语义视图
大牧师11 小时前
TypeORM 学习教程
数据库·sql·学习·node.js·orm·nest.js·typeorm
数智启示录13 小时前
PostgreSQL 执行计划实战(第 9 篇):SQL 和索引没变,计划为什么突然慢一百倍
数据库·经验分享·sql·postgresql·面试
润乾软件14 小时前
SQLazy:从事件表中查出下一组的开始时刻
sql·sqlazy
摇滚侠15 小时前
《SpringBoot 3:入门与应用实战》第 13 章 整合 MyBatis MyBatis 简单开发 阅读笔记 39
spring boot·笔记·mybatis
养生技术人15 小时前
Oracle OCP认证考试题目详解082系列第17题
运维·数据库·sql·oracle·ocp
java1234_小锋16 小时前
MyBatis-Plus 3.5.15 已全面支持 Spring Boot 4.0 及 Jackson 3.0
java·spring boot·mybatis
隔窗听雨眠16 小时前
活动中台系统慢SQL治理实践:从监控告警到性能优化的全链路方法论
sql·性能优化·github