批量操作性能优化:MyBatis-Plus批量插入和更新最佳实践

批量操作性能优化:MyBatis-Plus批量插入和更新最佳实践

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/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 批次大小优化

数据量推荐批次大小说明
< 1000100-500小批量数据,适中批次
1000-10000500-1000中等数据量,较大批次
> 100001000-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-200050-1001000
传统批量300-50020-3010-20
MyBatis-Plus批量100-20010-151-2

7.2 批次大小对性能的影响

mermaid

八、常见问题与解决方案

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 代码规范

  1. 批次大小: 根据数据量动态调整,推荐500-1000
  2. 事务管理: 显式控制事务,避免自动提交
  3. 异常处理: 完善的异常处理和重试机制
  4. 内存管理: 大数据量时分片处理,及时释放内存
  5. 监控日志: 记录批量操作性能和结果

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);
        }
    }
}

结语

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

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

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

抵扣说明:

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

余额充值