如何实现公共字段自动填充?MyBatis-Plus与MyBatis拦截器方案剖析

641 阅读3分钟

一、应用场景

以下场景适合使用公共字段自动填充:

  1. 创建时间:记录数据插入时间。
  2. 更新时间:记录数据最后一次修改时间。
  3. 操作人:记录当前操作用户ID或姓名。

二、方案一:MyBatis-Plus的MetaObjectHandler

MyBatis-Plus(简称MP)提供了 MetaObjectHandler 接口,通过实现该接口,可以在插入或更新时自动填充字段。

2.1 实现步骤

步骤1:实体类添加注解
在需要自动填充的字段上添加 @TableField 注解,并指定填充策略:

public class User {
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
}

步骤2:实现MetaObjectHandler接口
创建一个类实现 MetaObjectHandler,定义填充逻辑:

@Component
public class AutoFillHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // 获取当前用户(需结合业务实现)
        String currentUser = getCurrentUser();
        
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "createBy", String.class, currentUser);
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
    }
    
    private String getCurrentUser() {
        // 从ThreadLocal或SecurityContext中获取用户信息
        return "admin"; 
    }
}

步骤3:配置MP自动填充
application.yml 中启用MP配置:

mybatis-plus:
  global-config:
    db-config:
      logic-not-delete-value: 0
      logic-delete-value: 1
  configuration:
    map-underscore-to-camel-case: true

三、方案二:MyBatis拦截器

如果项目未使用MyBatis-Plus,可以通过MyBatis的拦截器(Interceptor)实现类似功能。

3.1 实现步骤

步骤1:自定义拦截器
实现 Interceptor 接口,拦截插入和更新操作:

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AutoFillInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        
        // 判断操作类型:INSERT或UPDATE
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (parameter != null) {
            MetaObject metaObject = SystemMetaObject.forObject(parameter);
            if (sqlCommandType == SqlCommandType.INSERT) {
                // 填充创建时间和创建人
                setFieldIfNull(metaObject, "createTime", new Date());
                setFieldIfNull(metaObject, "createBy", getCurrentUser());
            }
            if (sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.UPDATE) {
                // 填充更新时间
                setFieldIfNull(metaObject, "updateTime", new Date());
            }
        }
        return invocation.proceed();
    }

    private void setFieldIfNull(MetaObject metaObject, String fieldName, Object value) {
        if (metaObject.getValue(fieldName) == null) {
            metaObject.setValue(fieldName, value);
        }
    }

    private String getCurrentUser() {
        // 获取当前用户(如从ThreadLocal中读取)
        return "admin";
    }
}

步骤2:注册拦截器
将拦截器添加到MyBatis配置中:

@Configuration
public class MyBatisConfig {
    @Bean
    public AutoFillInterceptor autoFillInterceptor() {
        return new AutoFillInterceptor();
    }
}

四、方案对比与注意事项

4.1 方案对比
特性MyBatis-Plus MetaObjectHandlerMyBatis拦截器
实现复杂度简单(仅需实现接口)中等(需处理拦截逻辑)
灵活性依赖MP框架,填充策略固定完全自主控制,可处理复杂逻辑
侵入性低(通过注解配置)低(无侵入)
适用场景已使用MyBatis-Plus的项目原生MyBatis或需要高度定制的场景
4.2 注意事项
  1. 字段名称一致性:确保实体类字段名与数据库列名一致(可通过 @Column 注解映射)。
  2. 空值覆盖问题:拦截器中需判断字段是否已赋值,避免覆盖业务代码手动设置的值。
  3. 分布式环境getCurrentUser() 需结合分布式Session或JWT Token获取用户信息。
  4. 性能影响:拦截器会增加少量性能开销,避免在拦截器中处理耗时逻辑。

五、总结

通过 MyBatis-Plus的MetaObjectHandlerMyBatis拦截器,均可实现公共字段的自动填充:

  • MyBatis-Plus方案 适合快速开发,通过注解和接口简化配置。
  • 拦截器方案 更灵活,适用于原生MyBatis或需要复杂填充逻辑的场景。

选择方案时,需结合项目技术栈和需求,优先使用MyBatis-Plus以提升开发效率。