批量操作性能优化:MyBatis-Plus批量插入和更新最佳实践
引言:为什么需要批量操作?
在日常开发中,我们经常需要处理大量数据的插入和更新操作。传统的单条记录操作方式在面对成千上万条数据时,性能瓶颈会变得非常明显。MyBatis-Plus 提供了强大的批量操作功能,能够显著提升数据处理效率,减少数据库连接开销,优化系统性能。
通过本文,你将掌握:
- MyBatis-Plus 批量操作的核心原理
- 多种批量插入和更新的实现方式
- 性能优化策略和最佳实践
- 常见问题排查和解决方案
一、MyBatis-Plus 批量操作核心组件
1.1 MybatisBatch 核心类
MybatisBatch 是 MyBatis-Plus 批量操作的核心类,提供了丰富的批量操作方法:
public class MybatisBatch<T> {
private final SqlSessionFactory sqlSessionFactory;
private final Collection<T> dataList;
private final int batchSize;
// 执行批量操作
public List<BatchResult> execute(String statement);
public List<BatchResult> execute(BatchMethod<T> batchMethod);
// 批量保存或更新
public List<BatchResult> saveOrUpdate(BatchMethod<T> insertMethod,
BiPredicate<BatchSqlSession, T> insertPredicate,
BatchMethod<T> updateMethod);
}
1.2 BatchMethod 方法封装
BatchMethod 封装了 Mapper 方法调用,简化批量操作:
public static class Method<T> {
private final String namespace;
public Method(Class<?> mapperClass) {
this.namespace = mapperClass.getName();
}
// 常用方法封装
public BatchMethod<T> insert();
public BatchMethod<T> updateById();
public BatchMethod<T> deleteById();
}
二、批量插入最佳实践
2.1 基础批量插入
// 方式一:使用 MybatisBatchUtils 工具类
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
userList,
UserMapper.class.getName() + ".insert"
);
// 方式二:使用 Method 封装
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
userList,
method.insert()
);
2.2 自定义批次大小
// 设置批次大小为1000
int batchSize = 1000;
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
userList,
UserMapper.class.getName() + ".insert",
batchSize
);
2.3 事务控制批量插入
@Transactional
public void batchInsertWithTransaction(List<User> users) {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = new MybatisBatch<>(
sqlSessionFactory,
users,
1000 // 批次大小
).execute(false, method.insert()); // autoCommit=false
// 处理结果
processBatchResults(results);
}
三、批量更新最佳实践
3.1 根据ID批量更新
public void batchUpdateById(List<User> users) {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
users,
method.updateById()
);
}
3.2 条件批量更新
public void batchUpdateWithCondition(List<User> users) {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
users,
method.update(
user -> user, // 实体参数
user -> Wrappers.<User>lambdaUpdate()
.set(User::getStatus, user.getStatus())
.eq(User::getId, user.getId())
)
);
}
四、批量保存或更新(SaveOrUpdate)
4.1 智能保存或更新
public void batchSaveOrUpdate(List<User> users) {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = MybatisBatchUtils.saveOrUpdate(
sqlSessionFactory,
users,
method.insert(),
(sqlSession, user) -> {
// 判断是否存在的逻辑
return userMapper.selectById(user.getId()) == null;
},
method.updateById()
);
}
4.2 使用 BatchSqlSession 避免缓存问题
public void batchSaveOrUpdateSafe(List<User> users) {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = new MybatisBatch<>(sqlSessionFactory, users)
.saveOrUpdate(
method.insert(),
(batchSession, user) -> {
// 使用 BatchSqlSession 避免一级缓存问题
return batchSession.selectOne(
method.get("selectById").getStatementId(),
user.getId()
) == null;
},
method.updateById()
);
}
五、性能优化策略
5.1 批次大小优化
| 数据量 | 推荐批次大小 | 说明 |
|---|---|---|
| < 1000 | 100-500 | 小批量数据,适中批次 |
| 1000-10000 | 500-1000 | 中等数据量,较大批次 |
| > 10000 | 1000-2000 | 大数据量,需要分批次处理 |
5.2 事务管理优化
// 推荐的事务管理方式
@Transactional(propagation = Propagation.REQUIRED)
public void optimizedBatchOperation(List<User> users) {
int batchSize = 1000;
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
// 手动控制提交,避免自动提交的开销
List<BatchResult> results = new MybatisBatch<>(
sqlSessionFactory,
users,
batchSize
).execute(false, method.insert());
}
5.3 内存使用优化
// 流式处理大数据量
public void processLargeData(Stream<User> userStream) {
int batchSize = 1000;
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
userStream
.collect(Collectors.groupingBy(i -> i.hashCode() % 10)) // 分组处理
.forEach((key, userList) -> {
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
userList,
method.insert(),
batchSize
);
// 及时清理内存
userList.clear();
});
}
六、实战案例:电商订单批量处理
6.1 订单批量创建场景
@Service
public class OrderBatchService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Transactional
public BatchResultDTO batchCreateOrders(List<Order> orders) {
MybatisBatch.Method<Order> method = new MybatisBatch.Method<>(OrderMapper.class);
// 设置合适的批次大小
int optimalBatchSize = calculateOptimalBatchSize(orders.size());
List<BatchResult> results = new MybatisBatch<>(
sqlSessionFactory,
orders,
optimalBatchSize
).execute(false, method.insert());
return processOrderBatchResults(results);
}
private int calculateOptimalBatchSize(int totalSize) {
if (totalSize <= 1000) return 100;
if (totalSize <= 5000) return 500;
return 1000;
}
}
6.2 订单状态批量更新
public void batchUpdateOrderStatus(List<OrderStatusUpdate> updates) {
MybatisBatch.Method<OrderStatusUpdate> method =
new MybatisBatch.Method<>(OrderMapper.class);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
updates,
method.update(
update -> update,
update -> Wrappers.<Order>lambdaUpdate()
.set(Order::getStatus, update.getNewStatus())
.set(Order::getUpdateTime, new Date())
.eq(Order::getId, update.getOrderId())
),
500 // 批次大小
);
}
七、性能对比分析
7.1 不同操作方式性能对比
| 操作方式 | 1000条数据耗时(ms) | 内存占用(MB) | 数据库连接数 |
|---|---|---|---|
| 单条插入 | 1500-2000 | 50-100 | 1000 |
| 传统批量 | 300-500 | 20-30 | 10-20 |
| MyBatis-Plus批量 | 100-200 | 10-15 | 1-2 |
7.2 批次大小对性能的影响
八、常见问题与解决方案
8.1 内存溢出问题
问题现象: 处理大数据量时出现 OutOfMemoryError
解决方案:
// 分片处理大数据量
public void processLargeDataInChunks(List<User> allUsers) {
int chunkSize = 10000;
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
for (int i = 0; i < allUsers.size(); i += chunkSize) {
int end = Math.min(i + chunkSize, allUsers.size());
List<User> chunk = allUsers.subList(i, end);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
chunk,
method.insert(),
1000 // 内部批次大小
);
// 及时释放内存
chunk.clear();
System.gc();
}
}
8.2 事务超时问题
解决方案:
@Transactional(timeout = 300) // 设置合适的事务超时时间
public void batchOperationWithTimeout(List<Data> dataList) {
// 分批处理,避免单次操作时间过长
int batchSize = calculateSafeBatchSize(dataList.size());
// ... 批量操作逻辑
}
8.3 批量操作失败处理
public void safeBatchOperation(List<User> users) {
try {
MybatisBatch.Method<User> method = new MybatisBatch.Method<>(UserMapper.class);
List<BatchResult> results = MybatisBatchUtils.execute(
sqlSessionFactory,
users,
method.insert()
);
// 检查批量操作结果
for (BatchResult result : results) {
if (!Arrays.stream(result.getUpdateCounts()).allMatch(count -> count == 1)) {
log.warn("批量操作存在失败记录");
// 重试或记录失败数据
}
}
} catch (Exception e) {
log.error("批量操作失败", e);
// 失败重试或补偿机制
}
}
九、最佳实践总结
9.1 配置建议
# application.yml 配置建议
mybatis-plus:
configuration:
default-executor-type: batch # 默认使用批量执行器
jdbc-type-for-null: NULL # 避免NULL值处理问题
9.2 代码规范
- 批次大小: 根据数据量动态调整,推荐500-1000
- 事务管理: 显式控制事务,避免自动提交
- 异常处理: 完善的异常处理和重试机制
- 内存管理: 大数据量时分片处理,及时释放内存
- 监控日志: 记录批量操作性能和结果
9.3 性能监控
// 添加性能监控
public class MonitoredBatchOperation {
@Around("execution(* *.batch*(..))")
public Object monitorBatchPerformance(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("批量操作耗时: {}ms, 处理记录数: {}", duration, getRecordCount(pjp));
return result;
} catch (Throwable e) {
log.error("批量操作失败", e);
throw new RuntimeException(e);
}
}
}
结语
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



