一、核心概念

1. 自动填充机制

  • 作用:在数据操作时自动填充特定字段值(如创建时间、修改时间、操作人等)
  • 核心组件
    • @TableField(fill):字段填充策略注解
    • MetaObjectHandler:元对象处理器接口
    • FieldFill:填充策略枚举(INSERT/UPDATE/INSERT_UPDATE)

2. 填充策略枚举

策略类型 说明 适用场景
INSERT 仅在插入时填充 创建时间、创建人
UPDATE 仅在更新时填充 修改时间、修改人
INSERT_UPDATE 插入和更新时都填充 版本号、逻辑删除标记
DEFAULT 默认策略(不填充) 普通字段

3. 填充时机对比

操作类型 可触发策略 执行顺序
insert() INSERT, INSERT_UPDATE 在执行SQL之前
update() UPDATE, INSERT_UPDATE 在执行SQL之前
saveOrUpdate() 所有策略 根据操作类型触发

二、详细操作步骤

步骤1:实体类配置填充字段

public class User {
    // 插入时填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    // 更新时填充
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
    
    // 插入和更新时都填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String operator;
    
    // 插入时填充(带默认值)
    @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.INTEGER)
    private Integer status = 1;
}

步骤2:实现MetaObjectHandler接口

@Component
public class AutoFillHandler implements MetaObjectHandler {
    
    // 获取当前用户(可从安全框架获取)
    private String getCurrentUser() {
        return SecurityUtils.getCurrentUsername(); // 伪代码
    }

    // 插入时填充逻辑
    @Override
    public void insertFill(MetaObject metaObject) {
        // 严格填充模式(字段不存在或类型不匹配时报错)
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        
        // 宽松填充模式(字段不存在时跳过)
        setFieldValByName("operator", getCurrentUser(), metaObject);
        
        // 条件填充
        if (metaObject.getOriginalObject() instanceof User) {
            setFieldValByName("status", 1, metaObject);
        }
    }

    // 更新时填充逻辑
    @Override
    public void updateFill(MetaObject metaObject) {
        // 严格填充模式
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        
        // 防止覆盖原有值
        Object operator = getFieldValByName("operator", metaObject);
        if (operator == null) {
            setFieldValByName("operator", getCurrentUser(), metaObject);
        }
    }
}

步骤3:配置填充处理器(可选)

@Configuration
public class MybatisPlusConfig {
    
    // 注册自定义填充处理器
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new AutoFillHandler();
    }
    
    // 配置填充拦截器(可选)
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new AutoFillInterceptor());
        return interceptor;
    }
}

步骤4:测试填充效果

@SpringBootTest
class FillOperationTest {
    
    @Autowired
    private UserMapper userMapper;
    
    @Test
    void testInsertFill() {
        User user = new User();
        user.setName("John");
        userMapper.insert(user);
        
        // 验证填充字段
        assertNotNull(user.getCreateTime());
        assertEquals("admin", user.getOperator());
        assertEquals(1, user.getStatus());
        assertNull(user.getUpdateTime()); // UPDATE未触发
    }
    
    @Test
    void testUpdateFill() {
        User user = userMapper.selectById(1L);
        user.setName("John Doe");
        userMapper.updateById(user);
        
        // 验证填充字段
        assertNotNull(user.getUpdateTime());
        assertEquals("admin", user.getOperator());
    }
}

三、常见错误及解决方案

1. 填充字段未生效

  • 现象:字段值为空
  • 原因
    • 实体类字段未加@TableField(fill)注解
    • MetaObjectHandler未注册为Spring Bean
    • 字段名称拼写错误
  • 解决
    // 检查注解配置
    @TableField(fill = FieldFill.INSERT) // 确保注解存在
    private LocalDateTime createTime;
    
    // 确保处理器是Spring组件
    @Component
    public class AutoFillHandler implements MetaObjectHandler { ... }
    

2. 类型转换异常

  • 错误java.lang.ClassCastException: java.util.Date cannot be cast to java.time.LocalDateTime
  • 解决
    // 使用严格填充指定类型
    strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    

3. 批量操作填充失效

  • 现象:批量插入/更新时部分字段未填充
  • 原因:MyBatis-Plus批量操作默认不触发填充
  • 解决
    // 手动触发批量填充
    List<User> users = ...;
    users.forEach(user -> {
        MetaObject metaObject = SystemMetaObject.forObject(user);
        metaObjectHandler.insertFill(metaObject);
    });
    userService.saveBatch(users);
    

四、注意事项

1. 字段覆盖规则

场景 填充行为
字段已有值 保持原值,不填充
字段为null 执行填充
严格模式(strictXxxFill) 类型不匹配或字段不存在时报错

2. 多数据源特殊处理

@Slave // 从库操作
@Override
public void updateFill(MetaObject metaObject) {
    // 从库禁止填充操作
    if (DataSourceContext.isSlave()) {
        return;
    }
    // 主库正常填充
    strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}

3. 填充性能影响

操作类型 单次填充耗时 万次操作总耗时
无填充 0 ms 0 ms
基础填充 0.1-0.3 ms 1-3 s
复杂逻辑填充 0.5-2 ms 5-20 s

五、高级使用技巧

1. 动态条件填充

@Override
public void insertFill(MetaObject metaObject) {
    // 根据实体类型动态填充
    if (metaObject.getOriginalObject() instanceof Order) {
        setFieldValByName("orderNo", generateOrderNo(), metaObject);
    }
    
    // 根据字段值条件填充
    Object status = getFieldValByName("status", metaObject);
    if (status == null) {
        setFieldValByName("status", 0, metaObject);
    }
}

2. 填充链式编程

// 自定义填充构建器
public class FillBuilder {
    private final MetaObject metaObject;
    
    public FillBuilder(MetaObject metaObject) {
        this.metaObject = metaObject;
    }
    
    public FillBuilder fill(String field, Object value) {
        setFieldValByName(field, value, metaObject);
        return this;
    }
    
    public void apply() {
        // 可添加后处理逻辑
    }
}

// 在处理器中使用
@Override
public void updateFill(MetaObject metaObject) {
    new FillBuilder(metaObject)
        .fill("updateTime", LocalDateTime.now())
        .fill("version", getNextVersion())
        .apply();
}

3. 审计信息自动填充

// 审计实体基类
public abstract class AuditEntity {
    @TableField(fill = FieldFill.INSERT)
    private String createdBy;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;
    
    @TableField(fill = FieldFill.UPDATE)
    private String updatedBy;
    
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updatedAt;
}

// 处理器中统一处理
@Override
public void insertFill(MetaObject metaObject) {
    if (metaObject.getOriginalObject() instanceof AuditEntity) {
        strictInsertFill(metaObject, "createdBy", String.class, getCurrentUser());
        strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now());
    }
}

六、最佳实践与性能优化

1. 填充策略优化矩阵

字段类型 推荐策略 优化建议
创建时间 INSERT 使用数据库函数替代
修改时间 UPDATE 使用数据库触发器替代
操作人 INSERT_UPDATE 缓存用户信息减少IO
版本号 INSERT_UPDATE 原子操作避免并发问题
租户ID INSERT 线程上下文传递

2. 高性能填充方案

// 填充缓存优化
public class CachedFillHandler implements MetaObjectHandler {
    private final UserCache userCache; // 用户信息缓存
    
    @Override
    public void insertFill(MetaObject metaObject) {
        // 从缓存获取用户信息
        String currentUser = userCache.getCurrentUser();
        setFieldValByName("operator", currentUser, metaObject);
    }
}

// 填充批处理优化
@Override
public void insertFill(MetaObject metaObject) {
    // 批量填充时减少时间获取次数
    if (BatchContext.isBatching()) {
        setFieldValByName("createTime", BatchContext.getBatchTime(), metaObject);
    } else {
        setFieldValByName("createTime", LocalDateTime.now(), metaObject);
    }
}

3. 填充操作监控

// 使用Micrometer监控填充耗时
public class MonitoredMetaObjectHandler implements MetaObjectHandler {
    private final MeterRegistry meterRegistry;
    
    @Override
    public void insertFill(MetaObject metaObject) {
        Timer.Sample sample = Timer.start(meterRegistry);
        // 填充逻辑...
        sample.stop(meterRegistry.timer("fill.operation", "type", "insert"));
    }
}

// 监控指标示例:
// fill_operation_seconds_sum{type="insert"} 0.45
// fill_operation_seconds_count{type="insert"} 1200

4. 多租户填充方案

@Override
public void insertFill(MetaObject metaObject) {
    // 自动填充租户ID
    if (metaObject.hasGetter("tenantId")) {
        setFieldValByName("tenantId", TenantContext.getCurrentTenant(), metaObject);
    }
    
    // 租户特定填充
    if ("tenantA".equals(TenantContext.getCurrentTenant())) {
        setFieldValByName("department", "DeptA", metaObject);
    }
}

七、实战应用场景

场景1:电商订单状态追踪

public class Order {
    @TableField(fill = FieldFill.INSERT)
    private String orderNo; // 订单号
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.UPDATE)
    private OrderStatus status; // 订单状态
    
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
}

// 填充处理器
@Override
public void updateFill(MetaObject metaObject) {
    // 状态变更时自动记录修改时间
    OrderStatus newStatus = (OrderStatus) getFieldValByName("status", metaObject);
    if (newStatus != null) {
        setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}

场景2:文件版本管理

public class Document {
    @TableField(fill = FieldFill.INSERT)
    private Integer version = 1; // 初始版本
    
    @TableField(fill = FieldFill.UPDATE)
    private Integer updatedVersion; // 更新版本
}

// 填充处理器
@Override
public void updateFill(MetaObject metaObject) {
    // 版本号递增
    Integer currentVersion = (Integer) getFieldValByName("version", metaObject);
    setFieldValByName("updatedVersion", currentVersion + 1, metaObject);
}

场景3:敏感操作审计

public class OperationLog {
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String ipAddress; // 操作IP
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String deviceInfo; // 设备信息
}

// 填充处理器
@Override
public void insertFill(MetaObject metaObject) {
    HttpServletRequest request = RequestContextHolder.getRequest();
    setFieldValByName("ipAddress", request.getRemoteAddr(), metaObject);
    setFieldValByName("deviceInfo", request.getHeader("User-Agent"), metaObject);
}

八、性能优化方案对比

优化方案 适用场景 性能提升 实现复杂度 维护成本
缓存用户信息 高频填充操作人 40-60% ★★☆ ★☆☆
批量时间统一 批量插入/更新 30-50% ★☆☆ ★☆☆
数据库函数替代 时间字段填充 60-80% ★★☆ ★★☆
异步填充 非关键审计字段 50-70% ★★★ ★★☆
填充开关配置 性能敏感操作 20-40% ★☆☆ ★☆☆

优化示例:数据库函数替代填充

-- MySQL示例
ALTER TABLE user 
MODIFY COLUMN create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
MODIFY COLUMN update_time TIMESTAMP NOT NULL 
    DEFAULT CURRENT_TIMESTAMP 
    ON UPDATE CURRENT_TIMESTAMP;
// 实体类配置
public class User {
    // 数据库自动填充,应用层不处理
    @TableField(fill = FieldFill.NEVER) 
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.NEVER)
    private LocalDateTime updateTime;
}

总结:填充策略最佳实践

核心原则

  1. 最小化填充:只填充必要字段,避免过度填充
  2. 安全优先:敏感字段(如操作人)使用严格填充
  3. 性能意识:高频字段考虑数据库层实现
  4. 一致性保障:关键字段使用原子操作

策略选择指南

字段类型 推荐策略 补充说明
创建元数据 INSERT 确保只设置一次
更新元数据 UPDATE 避免覆盖历史数据
审计信息 INSERT_UPDATE 完整追踪操作记录
业务状态 手动设置 避免自动填充干扰业务逻辑
技术字段(版本号) INSERT_UPDATE 需要并发控制

性能关键点

  1. IO操作:减少填充中的远程调用(如用户信息查询)
  2. 时间获取:批量操作时统一获取时间戳
  3. 反射开销:避免在填充中使用复杂反射操作
  4. 并发控制:版本号填充使用乐观锁机制

终极建议

  • 简单系统:使用MyBatis-Plus填充机制
  • 高性能系统:关键字段移交给数据库
  • 分布式系统:结合分布式ID生成和时间服务
  • 审计敏感系统:填充与操作日志双写保障

通过合理使用填充机制,可减少30%以上的样板代码,同时提高数据一致性和可维护性。