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 (严格) 检查机制,丧失了框架提供的类型安全和策略校验功能。
相关推荐
韦禾水12 小时前
记录一次项目部署到tomcat的异常
java·tomcat
曦月合一12 小时前
树莓派安装jdk、tomcat、vnc、谷歌浏览器开机自启等环境配置
java·tomcat·树莓派
Irene199115 小时前
大数据开发语境下,SQL 模式名,映射关系 - - 概念理解
大数据·数据库·sql
看腻了那片水16 小时前
开源一个对业务代码零侵入的透明数据治理框架 —— 【sangsang】
java·mybatis
ffqws_19 小时前
MyBatis 动态 SQL 详解:从原理到实战
java·sql·mybatis
浮尘笔记19 小时前
在Snowy后台无需编码实现自动化生成CRUD操作流程
java·开发语言·经验分享·spring boot·后端·程序人生·mybatis
SuperherRo20 小时前
服务攻防-中间件安全&Apache&Tomcat&Jetty&Weblogic&AJP协议&反序列化&CVE漏洞
中间件·tomcat·apache·jetty·weblogic
其实防守也摸鱼20 小时前
《SQL注入进阶实验:基于sqli-Labs的报错注入(Error-Based Injection)实战解析》
网络·数据库·sql·安全·网络安全·sql注入·报错注入
juniperhan20 小时前
Flink 系列第20篇:Flink SQL 语法全解:从 DDL 到 DML,窗口、聚合、列转行一网打尽
大数据·数据仓库·分布式·sql·flink
河阿里1 天前
MyBatis:高效开发全流程+示例
mybatis