1. 问题现象与背景分析
最近在开发一个批量导入数据的后台服务时,遇到了一个棘手的问题:使用MyBatis-Plus的saveBatch方法在异步线程中执行批量插入时,数据没有成功持久化到数据库。这个现象在同步调用时完全正常,但一旦放到@Async标记的异步方法中就失效了。
先简单还原下问题场景:我们有一个商品导入功能,需要处理Excel文件并批量保存到数据库。为了提高响应速度,采用了Spring的异步处理机制。核心代码如下:
java复制@Async
@Transactional
public void asyncBatchInsert(List<Product> products) {
productService.saveBatch(products, 1000); // 每批1000条
}
在测试中发现,虽然方法执行没有报错,日志也显示执行了INSERT语句,但数据库表中始终没有新增记录。这个问题在开发环境中容易被忽视,因为测试数据量小,同步执行时一切正常。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题根因探究
2.1 MyBatis-Plus批量保存机制
首先需要理解saveBatch的工作原理。MyBatis-Plus的批量操作并不是真正的JDBC批量,而是通过以下方式实现:
- 按照batchSize分割列表
- 对每个分片执行普通的insert操作
- 默认情况下不会复用SqlSession
关键点在于,它仍然是在循环中执行单条INSERT语句,只是在外层做了分片处理。这与JDBC的addBatch/executeBatch有本质区别。
2.2 Spring事务传播机制
问题的核心在于事务的传播行为。当方法被@Async标记时:
- Spring会通过AOP代理执行方法
- 事务切面会在新线程中开启事务
- 方法执行完毕后立即提交事务
但在异步场景下,存在几个关键时间点:
- 事务的开启是在异步线程中
- saveBatch的执行也是在同一个线程
- 但MyBatis-Plus的SqlSession管理可能与事务不同步
2.3 线程隔离与连接持有
经过DEBUG跟踪发现,问题出在数据库连接的获取和释放时机:
- 事务管理器在新线程中获取连接
- MyBatis-Plus在执行saveBatch时会创建新的SqlSession
- 这两个Session可能没有正确关联
- 导致最终提交时,实际执行的INSERT没有被包含在事务中
3. 解决方案与实现
3.1 方案一:强制使用事务性SqlSession
修改MyBatis配置,确保使用事务作用域的SqlSession:
yaml复制mybatis-plus:
configuration:
default-executor-type: reuse # 重用预编译语句
global-config:
db-config:
sql-session-factory: org.mybatis.spring.SqlSessionFactoryB
