一、核心概念
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;
}
@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. 填充字段未生效
2. 类型转换异常
3. 批量操作填充失效
四、注意事项
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;
}
总结:填充策略最佳实践
核心原则
- 最小化填充:只填充必要字段,避免过度填充
- 安全优先:敏感字段(如操作人)使用严格填充
- 性能意识:高频字段考虑数据库层实现
- 一致性保障:关键字段使用原子操作
策略选择指南
字段类型 |
推荐策略 |
补充说明 |
创建元数据 |
INSERT |
确保只设置一次 |
更新元数据 |
UPDATE |
避免覆盖历史数据 |
审计信息 |
INSERT_UPDATE |
完整追踪操作记录 |
业务状态 |
手动设置 |
避免自动填充干扰业务逻辑 |
技术字段(版本号) |
INSERT_UPDATE |
需要并发控制 |
性能关键点
- IO操作:减少填充中的远程调用(如用户信息查询)
- 时间获取:批量操作时统一获取时间戳
- 反射开销:避免在填充中使用复杂反射操作
- 并发控制:版本号填充使用乐观锁机制
终极建议:
- 简单系统:使用MyBatis-Plus填充机制
- 高性能系统:关键字段移交给数据库
- 分布式系统:结合分布式ID生成和时间服务
- 审计敏感系统:填充与操作日志双写保障
通过合理使用填充机制,可减少30%以上的样板代码,同时提高数据一致性和可维护性。