MyBatis-Plus自动填充功能实现原理与使用技巧

MyBatis-Plus自动填充功能实现原理与使用技巧

【免费下载链接】mybatis-plus mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/baomidou/mybatis-plus

引言

在日常开发中,我们经常需要处理一些公共字段的自动填充,比如创建时间、更新时间、创建人、更新人等。手动处理这些字段不仅繁琐,还容易出错。MyBatis-Plus的自动填充功能(Auto Fill)正是为了解决这一痛点而生,它能够优雅地实现字段的自动填充,让开发者专注于业务逻辑的实现。

本文将深入剖析MyBatis-Plus自动填充功能的实现原理,并分享实用的使用技巧和最佳实践。

自动填充功能概述

MyBatis-Plus的自动填充功能通过MetaObjectHandler接口实现,支持以下四种填充策略:

填充策略描述适用场景
FieldFill.DEFAULT默认不处理普通字段
FieldFill.INSERT插入时填充创建时间、创建人
FieldFill.UPDATE更新时填充更新时间、更新人
FieldFill.INSERT_UPDATE插入和更新时填充逻辑删除标记

实现原理深度解析

核心架构

MyBatis-Plus的自动填充功能基于MyBatis的插件机制实现,主要涉及以下核心组件:

mermaid

执行流程

自动填充的执行流程如下:

mermaid

关键源码分析

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. 填充性能优化

问题现象:大量数据操作时,填充操作影响性能。

解决方案

  • 使用strictInsertFillstrictUpdateFill方法,避免不必要的反射调用
  • 实现条件填充逻辑,只在必要时执行填充
  • 使用缓存减少重复的填充检查

实战案例:审计日志自动填充

场景描述

在微服务架构中,通常需要记录每个操作的审计信息,包括操作人、操作时间等。使用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的自动填充功能是一个非常强大且实用的特性,它通过优雅的方式解决了公共字段自动填充的问题。通过本文的深入分析,我们可以看到:

  1. 架构设计优秀:基于MyBatis插件机制,与框架无缝集成
  2. 灵活性高:支持多种填充策略和条件控制
  3. 扩展性强:可以通过自定义MetaObjectHandler实现复杂的填充逻辑
  4. 性能优化:提供了严格的填充模式和缓存机制

在实际项目中,合理使用自动填充功能可以显著减少样板代码,提高开发效率,同时保证数据的一致性和完整性。建议开发者根据具体业务场景选择合适的填充策略,并注意性能优化和数据一致性保障。

通过掌握本文介绍的使用技巧和最佳实践,你将能够充分发挥MyBatis-Plus自动填充功能的潜力,构建更加健壮和可维护的应用程序。

【免费下载链接】mybatis-plus mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/baomidou/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值