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 (严格) 检查机制,丧失了框架提供的类型安全和策略校验功能。
相关推荐
冰暮流星5 小时前
sql之删除与软删除
数据库·sql
独断万古他化5 小时前
【MyBatis-Plus 进阶】注解配置、条件构造器与自定义 SQL的复杂操作详解
sql·mybatis·mybatis-plus·条件构造器
瀚高PG实验室14 小时前
通过数据库日志获取数据库中的慢SQL
数据库·sql·瀚高数据库
阳光九叶草LXGZXJ15 小时前
达梦数据库-学习-47-DmDrs控制台命令(LSN、启停、装载)
linux·运维·数据库·sql·学习
马猴烧酒.20 小时前
【JAVA数据传输】Java 数据传输与转换详解笔记
java·数据库·笔记·tomcat·mybatis
ruxshui21 小时前
Python多线程环境下连接对象的线程安全管理规范
开发语言·数据库·python·sql
Mr_Xuhhh21 小时前
MySQL数据表操作全解析:从创建到管理
数据库·sql·oracle
阳光九叶草LXGZXJ21 小时前
达梦数据库-学习-50-分区表指定分区清理空洞率(交换分区方式)
linux·运维·数据库·sql·学习
Apple_羊先森21 小时前
ORACLE数据库巡检SQL脚本--4、检查锁阻塞
数据库·sql·oracle