MyBatis 拦截器实现批量数据分片插入

以下是使用 MyBatis 拦截器实现批量数据分片插入的完整方案,通过拦截 Executor#update 方法,将集合参数拆分成多个批次执行,避免一次性插入大量数据导致的性能问题。

  1. 实现思路

拦截目标:拦截 Executor#update 方法,识别批量插入操作。
参数分片:若参数是集合类型,按指定批次大小(如 100 条)拆分。
分批执行:循环执行每个小批次的插入操作,保持事务一致性。
性能优化:利用 SqlSession 批处理模式提升效率。
2. 核心代码实现

2.1 自定义拦截器 BatchInsertInterceptor

java
复制

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
    )
})
public class BatchInsertInterceptor implements Interceptor {

    // 批次大小(可配置)
    private int batchSize = 100;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];

        // 仅拦截插入操作,且参数为集合类型
        if (isInsertOperation(ms) && parameter instanceof Collection) {
            Collection<?> collection = (Collection<?>) parameter;
            return batchInsert(invocation, ms, collection);
        }
        return invocation.proceed();
    }

    // 判断是否为插入操作
    private boolean isInsertOperation(MappedStatement ms) {
        return ms.getSqlCommandType() == SqlCommandType.INSERT;
    }

    // 分批次插入逻辑
    private Object batchInsert(Invocation invocation, MappedStatement ms, Collection<?> collection) throws Exception {
        Executor executor = (Executor) invocation.getTarget();
        int total = 0;
        List<List<?>> partitions = Lists.partition(new ArrayList<>(collection), batchSize);

        for (List<?> batch : partitions) {
            // 创建参数映射(适配不同参数类型)
            Object param = wrapParameter(ms, batch);
            // 执行单批次插入
            total += executor.update(ms, param);
            // 清空批处理缓存(防止内存泄漏)
            executor.clearLocalCache();
        }
        return total;
    }

    // 包装参数(适配 @Param 注解或默认参数)
    private Object wrapParameter(MappedStatement ms, List<?> batch) {
        Object originalParam = ms.getConfiguration().getObjectWrapperFactory().getWrapperFor(batch);
        return originalParam instanceof Map ? Collections.singletonMap("list", batch) : batch;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.batchSize = Integer.parseInt(properties.getProperty("batchSize", "100"));
    }
}
  1. 配置拦截器

3.1 MyBatis 配置文件中注册拦截器

xml
复制

<!-- mybatis-config.xml -->
<configuration>
    <plugins>
        <plugin interceptor="com.example.BatchInsertInterceptor">
            <property name="batchSize" value="100"/>
        </plugin>
    </plugins>
</configuration>

运行 HTML
3.2 Spring Boot 中通过配置类注册

java
复制

@Configuration
public class MyBatisConfig {

    @Bean
    public BatchInsertInterceptor batchInsertInterceptor() {
        BatchInsertInterceptor interceptor = new BatchInsertInterceptor();
        interceptor.setBatchSize(100);
        return interceptor;
    }
}
  1. 使用示例

4.1 Mapper 接口定义

java
复制

public interface UserMapper {
    void batchInsert(@Param("list") List<User> users);
}

4.2 XML 映射文件

xml
复制

<insert id="batchInsert">
    INSERT INTO user (name, age) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.age})
    </foreach>
</insert>

运行 HTML
4.3 调用方法

java
复制
// 插入 1000 条数据,自动分 10 批执行

List<User> users = generateUsers(1000);
userMapper.batchInsert(users);
  1. 关键优化点

5.1 性能优化

批处理模式:在 SqlSession 中启用批处理(需结合 BATCH 执行器类型)。
java
复制

SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

JDBC 批处理:确保数据库驱动开启 rewriteBatchedStatements=true(MySQL 需此配置)
5.2 事务管理

事务一致性:所有批次需在同一个事务中执行(推荐使用 Spring 的 @Transactional)。
异常回滚:任一批次失败时回滚全部操作。
5.3 参数适配

兼容性:支持 List、@Param(“list”)、Map 等多种参数形式。
动态 SQL:确保 标签的 collection 属性与参数名匹配。
6. 扩展功能

6.1 动态调整批次大小

通过外部配置(如 Apollo、Nacos)动态更新 batchSize:

java
复制

public void setBatchSize(int batchSize) {
    this.batchSize = batchSize;
}

6.2 监控与日志

java
复制

private Object batchInsert(Invocation invocation, MappedStatement ms, Collection<?> collection) throws Exception {
    long startTime = System.currentTimeMillis();
    // ... 分批次逻辑 ...
    log.info("Batch insert completed. Total: {}, Time: {}ms", total, System.currentTimeMillis() - startTime);
    return total;
}
  1. 注意事项

参数类型:确保 Mapper 方法的参数是 Collection 类型。
SQL 语法:数据库需支持批量插入语法(如 MySQL 的 VALUES (…), (…))。
连接池配置:增大连接池最大连接数(如 HikariCP 的 maximumPoolSize)。
总结

通过此方案,可以实现:

自动分片:无需修改业务代码,拦截器自动拆分集合参数。
性能可控:通过 batchSize 控制单次操作数据量。
事务安全:所有批次在同一事务中执行,保障数据一致性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值