事务边界
本章定位:彻底理解 Spring Batch 的事务边界——Chunk 事务的 COMMIT/ROLLBACK 行为、多数据源下的 TransactionManager 选择和事务传播。
定义与作用
Spring Batch 的事务管理遵循 "一个 Chunk = 一个事务" 的铁律。每个 Chunk 的 Reader、Processor、Writer 操作在同一事务中执行,Chunk 结束时 COMMIT。
飞翔科技数据库管理员孔蓝的忠告:
"Spring Batch 的事务边界很简单:Chunk 多大,事务就包多大。但魔鬼在细节里——Reader 是否参与事务、Writer 用的事务管理器对不对、Reader 和 Writer 能否用不同事务——这些才是出 bug 的地方。"
核心原理
关键事务行为
| 阶段 | 行为 | 说明 |
|---|---|---|
| Chunk 开始 | BEGIN | 开启新事务 |
| read() | 事务中 | 读取的行可能被事务锁定 |
| process() | 事务中 | 内存操作,不提交 |
| write() | 事务中 | 批量写入 |
| 成功 → | COMMIT | 提交事务 + 更新 ExecutionContext |
| 失败 → | ROLLBACK | 回滚事务 + 进入容错处理 |
完整示例
场景一:Reader/Writer 不同数据源
@Configuration
public class MultiDataSourceBatchConfig {
@Bean
public PlatformTransactionManager readerTm(
@Qualifier("readerDataSource") DataSource readerDs) {
return new DataSourceTransactionManager(readerDs);
}
@Bean
public PlatformTransactionManager writerTm(
@Qualifier("writerDataSource") DataSource writerDs) {
return new DataSourceTransactionManager(writerDs);
}
@Bean
public Step multiDsStep(JobRepository jobRepository,
@Qualifier("writerTm") PlatformTransactionManager writerTm) {
return new StepBuilder("multiDsStep", jobRepository)
.<Order, Order>chunk(100, writerTm) // Writer 的事务管理器
.reader(orderReader()) // 用自己的 DS
.writer(orderWriter()) // 用另一个 DS
.build();
}
}
场景二:Reader 不参与事务
@Bean
@StepScope
public JdbcCursorItemReader<Order> nonTransactionalReader(DataSource ds) {
JdbcCursorItemReader<Order> reader = new JdbcCursorItemReader<>();
reader.setDataSource(ds);
reader.setSql("SELECT * FROM orders");
reader.setRowMapper(new BeanPropertyRowMapper<>(Order.class));
reader.setUseSharedExtendedConnection(false); // Reader 使用独立连接
return reader;
}
事务行为对比:
Reader 参与事务:
Chunk 事务包含 Reader 的连接
→ Reader 读取的行被锁定
→ 直到 Chunk COMMIT 才释放锁
→ 适合:防止数据被并发修改
Reader 不参与事务:
Chunk 事务只包含 Writer
→ Reader 连接在 read() 后立即释放
→ 不会长时间占用数据库锁
→ 适合:高并发读场景
易错场景与避坑
反例一:事务管理器与数据源不匹配
// ❌ Step 的事务管理器用的是 DataSource A
// ❌ Writer 用的是 DataSource B
// → Writer 的 INSERT 不在事务中 → 无法回滚!
@Bean
public Step brokenStep(JobRepository jobRepository,
@Qualifier("dsA_tm") PlatformTransactionManager tm,
@Qualifier("dsB_writer") ItemWriter<Order> writer) {
return new StepBuilder("brokenStep", jobRepository)
.<Order, Order>chunk(100, tm) // TM 管理 DS-A
.reader(reader)
.writer(writer) // Writer 写 DS-B
.build();
}
反例二:Chunk Size=1 → 事务滥用
// ❌ 每条数据都开启+提交事务
.chunk(1, tx)
// 100 万条 → 100 万个事务 → COMMIT 开销极高
面试高频考点
Q1:Chunk 事务中 Reader 读到的数据在 Write 时会被其他事务修改吗?
取决于隔离级别和
useSharedExtendedConnection。如果 Reader 使用共享连接且隔离级别为 REPEATABLE_READ,读到的数据在事务期间不会被修改。如果useSharedExtendedConnection=false,Reader 不参与事务,数据可能在读取后被修改。
Q2:如何让 JobRepository 使用独立的数据库?
①配置两个 DataSource:
primaryDataSource(业务库)和batchDataSource(元数据库)。②用@BatchDataSource或BatchConfigurer指定batchDataSource给JobRepository。Step 的事务管理器使用primaryDataSource。
上一章:Chunk 处理模型详解下一章:作业参数与启动