MyBatis-Plus自动填充功能实现原理与使用技巧
引言
在日常开发中,我们经常需要处理一些公共字段的自动填充,比如创建时间、更新时间、创建人、更新人等。手动处理这些字段不仅繁琐,还容易出错。MyBatis-Plus的自动填充功能(Auto Fill)正是为了解决这一痛点而生,它能够优雅地实现字段的自动填充,让开发者专注于业务逻辑的实现。
本文将深入剖析MyBatis-Plus自动填充功能的实现原理,并分享实用的使用技巧和最佳实践。
自动填充功能概述
MyBatis-Plus的自动填充功能通过MetaObjectHandler接口实现,支持以下四种填充策略:
| 填充策略 | 描述 | 适用场景 |
|---|---|---|
FieldFill.DEFAULT | 默认不处理 | 普通字段 |
FieldFill.INSERT | 插入时填充 | 创建时间、创建人 |
FieldFill.UPDATE | 更新时填充 | 更新时间、更新人 |
FieldFill.INSERT_UPDATE | 插入和更新时填充 | 逻辑删除标记 |
实现原理深度解析
核心架构
MyBatis-Plus的自动填充功能基于MyBatis的插件机制实现,主要涉及以下核心组件:
执行流程
自动填充的执行流程如下:
关键源码分析
1. 字段填充策略判断
在TableFieldInfo类中,MyBatis-Plus会根据@TableField注解的fill属性来判断字段的填充策略:
// TableFieldInfo.java 关键代码
this.fieldFill = tableField.fill();
this.withInsertFill = this.fieldFill == FieldFill.INSERT ||
this.fieldFill == FieldFill.INSERT_UPDATE;
this.withUpdateFill = this.fieldFill == FieldFill.UPDATE ||
this.fieldFill == FieldFill.INSERT_UPDATE;
2. 填充执行入口
MybatisParameterHandler是填充功能的执行入口,在process方法中处理填充逻辑:
// MybatisParameterHandler.java 关键代码
protected void insertFill(MetaObject metaObject, TableInfo tableInfo) {
GlobalConfigUtils.getMetaObjectHandler(this.configuration).ifPresent(metaObjectHandler -> {
if (metaObjectHandler.openInsertFill() &&
metaObjectHandler.openInsertFill(mappedStatement) &&
tableInfo.isWithInsertFill()) {
metaObjectHandler.insertFill(metaObject);
}
});
}
使用技巧与最佳实践
基础使用示例
1. 定义实体类
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
}
2. 实现MetaObjectHandler
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
strictInsertFill(metaObject, "createBy", this::getCurrentUsername, String.class);
strictInsertFill(metaObject, "updateBy", this::getCurrentUsername, String.class);
}
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
strictUpdateFill(metaObject, "updateBy", this::getCurrentUsername, String.class);
}
private String getCurrentUsername() {
// 获取当前用户信息的逻辑
return "system";
}
}
高级使用技巧
1. 条件填充控制
@Component
public class ConditionalMetaObjectHandler implements MetaObjectHandler {
@Override
public boolean openInsertFill(MappedStatement mappedStatement) {
// 根据MappedStatement ID决定是否开启插入填充
return !mappedStatement.getId().contains("ignoreFill");
}
@Override
public boolean openUpdateFill(MappedStatement mappedStatement) {
// 根据MappedStatement ID决定是否开启更新填充
return !mappedStatement.getId().contains("ignoreFill");
}
@Override
public void insertFill(MetaObject metaObject) {
// 条件填充逻辑
if (shouldFillCreateTime(metaObject)) {
strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
}
}
private boolean shouldFillCreateTime(MetaObject metaObject) {
// 自定义填充条件判断
return true;
}
}
2. 多数据源下的填充处理
@Configuration
public class MetaObjectHandlerConfig {
@Bean
@ConditionalOnBean(name = "primaryDataSource")
public MetaObjectHandler primaryMetaObjectHandler() {
return new PrimaryMetaObjectHandler();
}
@Bean
@ConditionalOnBean(name = "secondaryDataSource")
public MetaObjectHandler secondaryMetaObjectHandler() {
return new SecondaryMetaObjectHandler();
}
}
3. 自定义填充策略
public class CustomFillStrategy {
/**
* 智能填充策略:只有字段值为null时才填充
*/
public static void smartFill(MetaObject metaObject, String fieldName, Supplier<?> supplier) {
if (metaObject.getValue(fieldName) == null) {
Object value = supplier.get();
if (value != null) {
metaObject.setValue(fieldName, value);
}
}
}
/**
* 批量填充多个字段
*/
public static void batchFill(MetaObject metaObject, Map<String, Supplier<?>> fillMap) {
fillMap.forEach((fieldName, supplier) -> {
if (metaObject.getValue(fieldName) == null) {
Object value = supplier.get();
if (value != null) {
metaObject.setValue(fieldName, value);
}
}
});
}
}
性能优化建议
1. 避免不必要的填充检查
public class OptimizedMetaObjectHandler implements MetaObjectHandler {
private final Set<String> fillableEntities = new HashSet<>();
public OptimizedMetaObjectHandler() {
// 预加载需要填充的实体类
fillableEntities.add("com.example.entity.User");
fillableEntities.add("com.example.entity.Order");
}
@Override
public void insertFill(MetaObject metaObject) {
String className = metaObject.getOriginalObject().getClass().getName();
if (!fillableEntities.contains(className)) {
return;
}
// 执行填充逻辑
strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
}
}
2. 使用缓存提升性能
public class CachedMetaObjectHandler implements MetaObjectHandler {
private final Cache<String, Boolean> fillCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
@Override
public void insertFill(MetaObject metaObject) {
String cacheKey = generateCacheKey(metaObject, "insert");
if (fillCache.getIfPresent(cacheKey) != null) {
return;
}
// 执行填充并缓存结果
strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
fillCache.put(cacheKey, true);
}
private String generateCacheKey(MetaObject metaObject, String type) {
return metaObject.getOriginalObject().getClass().getName() + ":" + type;
}
}
常见问题与解决方案
1. 填充不生效问题
问题现象:字段标注了@TableField(fill = FieldFill.INSERT)但未自动填充。
解决方案:
- 检查
MetaObjectHandler是否被Spring容器管理(添加@Component注解) - 确认实体类字段的setter方法存在且可访问
- 检查全局配置中是否正确配置了
metaObjectHandler
2. 多数据源下的填充冲突
问题现象:多数据源环境下,填充处理器被多次实例化导致冲突。
解决方案:
@Configuration
public class MultiDataSourceConfig {
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 为每个数据源配置独立的MetaObjectHandler
MybatisPlusProperties properties = new MybatisPlusProperties();
properties.setMetaObjectHandler(new PrimaryMetaObjectHandler());
factoryBean.setConfiguration(properties.getConfiguration());
return factoryBean.getObject();
}
}
3. 填充性能优化
问题现象:大量数据操作时,填充操作影响性能。
解决方案:
- 使用
strictInsertFill和strictUpdateFill方法,避免不必要的反射调用 - 实现条件填充逻辑,只在必要时执行填充
- 使用缓存减少重复的填充检查
实战案例:审计日志自动填充
场景描述
在微服务架构中,通常需要记录每个操作的审计信息,包括操作人、操作时间等。使用MyBatis-Plus的自动填充功能可以优雅地实现这一需求。
实现方案
1. 定义审计实体基类
@Data
@MappedSuperclass
public abstract class AuditEntity {
@TableField(fill = FieldFill.INSERT)
private String createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableField(fill = FieldFill.INSERT)
private String tenantId;
}
2. 实现审计填充处理器
@Component
public class AuditMetaObjectHandler implements MetaObjectHandler {
private final TenantContext tenantContext;
private final UserContext userContext;
public AuditMetaObjectHandler(TenantContext tenantContext, UserContext userContext) {
this.tenantContext = tenantContext;
this.userContext = userContext;
}
@Override
public void insertFill(MetaObject metaObject) {
LocalDateTime now = LocalDateTime.now();
String currentUser = userContext.getCurrentUser();
String tenantId = tenantContext.getCurrentTenant();
strictInsertFill(metaObject, "createdBy", () -> currentUser, String.class);
strictInsertFill(metaObject, "createdAt", () -> now, LocalDateTime.class);
strictInsertFill(metaObject, "updatedBy", () -> currentUser, String.class);
strictInsertFill(metaObject, "updatedAt", () -> now, LocalDateTime.class);
strictInsertFill(metaObject, "tenantId", () -> tenantId, String.class);
}
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updatedBy", userContext::getCurrentUser, String.class);
strictUpdateFill(metaObject, "updatedAt", LocalDateTime::now, LocalDateTime.class);
}
}
3. 业务实体继承审计基类
@Data
@TableName("business_entity")
@EqualsAndHashCode(callSuper = true)
public class BusinessEntity extends AuditEntity {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String description;
private Integer status;
}
总结
MyBatis-Plus的自动填充功能是一个非常强大且实用的特性,它通过优雅的方式解决了公共字段自动填充的问题。通过本文的深入分析,我们可以看到:
- 架构设计优秀:基于MyBatis插件机制,与框架无缝集成
- 灵活性高:支持多种填充策略和条件控制
- 扩展性强:可以通过自定义
MetaObjectHandler实现复杂的填充逻辑 - 性能优化:提供了严格的填充模式和缓存机制
在实际项目中,合理使用自动填充功能可以显著减少样板代码,提高开发效率,同时保证数据的一致性和完整性。建议开发者根据具体业务场景选择合适的填充策略,并注意性能优化和数据一致性保障。
通过掌握本文介绍的使用技巧和最佳实践,你将能够充分发挥MyBatis-Plus自动填充功能的潜力,构建更加健壮和可维护的应用程序。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



