以下是使用 MyBatis 拦截器实现批量数据分片插入的完整方案,通过拦截 Executor#update 方法,将集合参数拆分成多个批次执行,避免一次性插入大量数据导致的性能问题。
- 实现思路
拦截目标:拦截 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"));
}
}
- 配置拦截器
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;
}
}
- 使用示例
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);
- 关键优化点
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;
}
- 注意事项
参数类型:确保 Mapper 方法的参数是 Collection 类型。
SQL 语法:数据库需支持批量插入语法(如 MySQL 的 VALUES (…), (…))。
连接池配置:增大连接池最大连接数(如 HikariCP 的 maximumPoolSize)。
总结
通过此方案,可以实现:
自动分片:无需修改业务代码,拦截器自动拆分集合参数。
性能可控:通过 batchSize 控制单次操作数据量。
事务安全:所有批次在同一事务中执行,保障数据一致性。