chunk size=1 不能实现 mongodb 每条记录独立事务,因原生 mongoitemwriter 不支持 chunk 级事务回滚;正确做法是用 mongotemplate + clientsession 手动控制单条事务,确保 starttransaction、commit/abort、close 全流程闭环。

chunk size=1 不能直接用于 MongoDB ItemWriter
Spring Batch 的 chunk(1) 看似能实现“每条记录独立事务”,但它只对支持事务的写入器生效,而原生 MongoItemWriter(包括 RepositoryItemWriter 和 MongoTemplateItemWriter)**不支持 chunk 级事务回滚**。原因在于:MongoDB 的事务必须显式通过 ClientSession 启动并管理,而标准 ItemWriter 实现并未将 session 绑定到每个 chunk 上。
你若强行设 chunk(1) 并在 @Transactional 方法里调用 mongoTemplate.insert(),会触发以下问题:
- 事务管理器是
MongoTransactionManager,但 Spring Batch 的Step默认使用PlatformTransactionManager,两者不兼容; - 事务上下文无法跨
write()调用传递,write(List<t>)</t>入参仍是单元素列表,但 session 生命周期未对齐; - 一旦写入失败,Batch 框架会尝试重试该 chunk,但 MongoDB 文档可能已插入(无唯一约束时),导致脏数据。
正确做法:用 MongoTemplate + 手动 session 控制每条提交
绕过 ItemWriter 的事务抽象,改用 MongoTemplate 在 ItemProcessor 或自定义 ChunkListener 中逐条操作,并显式管理 session。这是目前最可控、也最贴近 MongoDB 事务语义的方式。
关键点:
- 在
process()或afterChunk()中获取ClientSession,调用session.startTransaction(); - 所有读写操作(
find、insert、update)都必须传入该 session; - 成功则
session.commitTransaction(),异常则session.abortTransaction(); - 务必在
finally块中调用session.close(),否则连接泄漏。
示例片段(在 ChunkListener 中):
public void afterChunk(ChunkContext context) {
ClientSession session = mongoTemplate.getSession();
try {
session.startTransaction();
// 执行单条逻辑:查+改+写
mongoTemplate.updateFirst(
Query.query(Criteria.where("id").is(itemId)),
Update.update("status", "processed"),
"mycollection",
session
);
session.commitTransaction();
} catch (Exception e) {
session.abortTransaction();
throw e;
} finally {
session.close();
}
}
为什么不用 @Transactional 套 MongoItemWriter?
直接给 ItemWriter 实现类加 @Transactional 注解,或把 write() 方法单独标记为事务方法,基本无效。因为:
-
ItemWriter.write()是由 Spring Batch 的ChunkOrientedTasklet调用的,事务代理不会切入该调用链; - 即便代理生效,
MongoTransactionManager与 Batch 的Step所用的PlatformTransactionManager不是同一个 bean,事务上下文无法传播; - MongoDB 事务要求所有操作在同一个
ClientSession内完成,而@Transactional无法保证 session 的复用和生命周期绑定。
换句话说:@Transactional 对 MongoDB 的作用,仅限于你手动控制 MongoTemplate + ClientSession 的场景,且必须确保该 session 被传入每一个数据库操作。
批量写入 vs 单条强一致性:选哪个?
真正需要“每条独立提交”的场景,往往意味着业务上不能容忍任何一条失败污染其他条——比如金融对账、审计日志落库、状态机驱动的事件消费。这时,牺牲吞吐换确定性是合理选择。
但要注意:
- MongoDB 事务有默认 60 秒超时,长事务会阻塞快照读,影响集群整体性能;
- 如果写入量大(如 > 10k 条/分钟),应考虑降级为“分组提交”(例如每 10 条一个事务),而非死守单条;
- 更根本的解法是重构文档模型:把本该原子变更的一组字段/子文档放在同一文档内,靠单文档原子性替代事务——这比硬扛事务边界更高效、更符合 MongoDB 设计哲学。
事务不是兜底手段,而是临时补丁。当发现你频繁依赖它来修复多集合更新、跨文档状态同步时,该重新审视聚合边界了。











