MyBatis-Plus字段自动填充:@TableField(fill = FieldFill.INSERT)详解
痛点场景:为什么需要字段自动填充?
在日常开发中,我们经常需要处理一些公共字段的赋值问题,比如:
- 创建时间:每条记录插入时都需要设置当前时间
- 创建人:记录创建者的用户ID
- 更新时间:记录修改时更新为当前时间
- 版本号:乐观锁控制的版本字段
传统做法是在每个业务逻辑中手动设置这些值,但这会导致:
- 代码重复:相同的赋值逻辑散落在各个Service中
- 容易遗漏:开发人员可能忘记设置某些字段
- 维护困难:修改字段逻辑需要改动多处代码
MyBatis-Plus的字段自动填充功能正是为了解决这些问题而生!
FieldFill.INSERT 核心机制解析
字段填充策略枚举
MyBatis-Plus提供了四种字段填充策略:
public enum FieldFill {
/** 默认不处理 */
DEFAULT,
/** 插入时填充字段 */
INSERT,
/** 更新时填充字段 */
UPDATE,
/** 插入和更新时填充字段 */
INSERT_UPDATE
}
@TableField(fill = FieldFill.INSERT) 工作原理
当你在实体类字段上使用 @TableField(fill = FieldFill.INSERT) 注解时:
实战:三步实现字段自动填充
第一步:实体类配置
在需要自动填充的字段上添加注解:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
// 插入时自动填充创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 插入时自动填充创建人
@TableField(fill = FieldFill.INSERT)
private Long createBy;
// 插入和更新时都填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
第二步:实现MetaObjectHandler接口
创建自定义的字段填充处理器:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 设置创建时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 设置创建人(从线程上下文获取当前用户)
this.strictInsertFill(metaObject, "createBy", Long.class, getCurrentUserId());
// 设置更新时间(INSERT_UPDATE策略也会触发)
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时设置更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
private Long getCurrentUserId() {
// 实际项目中从SecurityContext或ThreadLocal获取
return 1L; // 示例值
}
}
第三步:配置启用(Spring Boot)
在配置类中启用自动填充:
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MyMetaObjectHandler myMetaObjectHandler() {
return new MyMetaObjectHandler();
}
}
高级用法与最佳实践
1. 严格模式填充
MyBatis-Plus 3.3.0+ 提供了严格模式填充,避免误填充:
@Override
public void insertFill(MetaObject metaObject) {
// 严格模式:只有字段为null时才填充
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class);
// 传统模式
this.setFieldValByName("createBy", getCurrentUserId(), metaObject);
}
2. 多数据源场景
在多数据源环境下,需要为每个数据源配置独立的MetaObjectHandler:
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
// 设置自定义的MetaObjectHandler
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setMetaObjectHandler(new PrimaryMetaObjectHandler());
factory.setConfiguration(configuration);
return factory.getObject();
}
}
3. 条件填充控制
可以通过重写openInsertFill和openUpdateFill方法控制填充开关:
@Component
public class ConditionalMetaObjectHandler implements MetaObjectHandler {
@Override
public boolean openInsertFill(MappedStatement mappedStatement) {
// 根据MappedStatement ID判断是否开启插入填充
String id = mappedStatement.getId();
return !id.contains("batch"); // 批量操作不开启填充
}
@Override
public void insertFill(MetaObject metaObject) {
// 填充逻辑...
}
// updateFill方法...
}
常见问题与解决方案
Q1: 填充不生效怎么办?
排查步骤:
- 检查是否配置了MetaObjectHandler Bean
- 确认注解写法正确:
@TableField(fill = FieldFill.INSERT) - 查看字段名是否与实体属性名一致
Q2: 批量操作时填充异常?
解决方案:
@Override
public boolean openInsertFill(MappedStatement mappedStatement) {
// 批量插入时关闭自动填充
return !mappedStatement.getId().contains("Batch");
}
Q3: 如何填充复杂对象?
@Override
public void insertFill(MetaObject metaObject) {
// 填充JSON对象
UserInfo userInfo = new UserInfo(getCurrentUser());
this.strictInsertFill(metaObject, "userInfo", UserInfo.class, userInfo);
}
性能优化建议
1. 避免频繁的反射操作
@Component
public class OptimizedMetaObjectHandler implements MetaObjectHandler {
private final ThreadLocal<Boolean> fillEnabled = ThreadLocal.withInitial(() -> true);
public void disableFill() {
fillEnabled.set(false);
}
public void enableFill() {
fillEnabled.set(true);
}
@Override
public boolean openInsertFill(MappedStatement mappedStatement) {
return fillEnabled.get();
}
// 其他方法...
}
2. 使用缓存减少重复计算
@Component
public class CachedMetaObjectHandler implements MetaObjectHandler {
private final Cache<String, Object> valueCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
@Override
public void insertFill(MetaObject metaObject) {
String cacheKey = "currentUserId";
Long userId = (Long) valueCache.getIfPresent(cacheKey);
if (userId == null) {
userId = getCurrentUserId(); // 耗时操作
valueCache.put(cacheKey, userId);
}
this.strictInsertFill(metaObject, "createBy", Long.class, userId);
}
}
总结对比表
| 填充策略 | 触发时机 | 适用场景 | 注意事项 |
|---|---|---|---|
FieldFill.INSERT | 仅插入操作 | 创建时间、创建人 | 更新操作不会触发 |
FieldFill.UPDATE | 仅更新操作 | 更新时间、更新人 | 插入操作不会触发 |
FieldFill.INSERT_UPDATE | 插入和更新操作 | 最后修改时间 | 注意业务逻辑冲突 |
FieldFill.DEFAULT | 不自动填充 | 普通字段 | 默认值,无需特殊处理 |
实战经验分享
场景一:审计字段自动填充
/**
* 基础审计实体
*/
@Data
public class BaseAuditEntity {
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT)
private String createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateUser;
@TableField(fill = FieldFill.INSERT)
private Integer version = 0;
}
场景二:多租户数据隔离
@Component
public class TenantMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 自动设置租户ID
Long tenantId = TenantContext.getCurrentTenantId();
this.strictInsertFill(metaObject, "tenantId", Long.class, tenantId);
}
}
MyBatis-Plus的字段自动填充功能极大地简化了公共字段的处理,通过合理的配置和使用,可以显著提高开发效率并减少代码错误。掌握 @TableField(fill = FieldFill.INSERT) 的正确用法,让你的代码更加简洁和健壮!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



