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 (严格) 检查机制,丧失了框架提供的类型安全和策略校验功能。
相关推荐
山岚的运维笔记24 分钟前
SQL Server笔记 -- 第73章:排序/对行进行排序
数据库·笔记·后端·sql·microsoft·sqlserver
vx-Biye_Design2 小时前
servlet家政公司管理系统-计算机毕业设计源码01438
java·vue.js·spring·servlet·tomcat·maven·mybatis
QQ24391972 小时前
语言在线考试与学习交流网页平台信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·spring boot·sql·学习·java-ee
青衫码上行4 小时前
高频 SQL 50题(基础版)| 查询 + 连接
数据库·sql·学习·mysql
树码小子6 小时前
图书管理系统(5)强制登陆(后端实现)
spring boot·mybatis·图书管理系统
丹牛Daniel6 小时前
Java解决HV000183: Unable to initialize ‘javax.el.ExpressionFactory‘
java·开发语言·spring boot·tomcat·intellij-idea·个人开发
山岚的运维笔记8 小时前
SQL Server笔记 -- 第77章:文件组
数据库·笔记·sql·microsoft·oracle·sqlserver
啊哈哈1213811 小时前
SQL学习笔记7:综合查询与高级技巧全解析 + LeetCode实战
笔记·sql·学习
愚公搬代码12 小时前
【愚公系列】《数据可视化分析与实践》019-数据集(自定义SQL数据集)
数据库·sql·信息可视化
lzhdim13 小时前
SQL 入门 2:LIKE、正则、 ORDER BY 与LIMIT
数据库·sql·mysql