Step 详解
本章定位:深入理解 Step 的执行模型——它是 Job 的执行单元,是 Chunk 循环和 Tasklet 逻辑的实际载体。
定义与作用
Step 是 Job 中的独立执行单元,代表批处理过程中的一个逻辑阶段。每个 Step 封装了具体的数据处理逻辑(Chunk 流水线或 Tasklet 操作),拥有独立的事务边界、执行状态和重启策略。
飞翔科技架构师白歌在团队技术分享中解释:
"Job 是剧本,Step 是每一场戏。只有所有场次演完,整部剧(Job)才算完成。如果第三场戏演砸了,导演(Spring Batch)可以从第三场重新开始,不用从头演。"
Step 的职责:
| Step 负责 | Step 不负责 |
|---|---|
| 封装 Reader → Processor → Writer 循环(Chunk)或单一操作(Tasklet) | 跨 Step 的数据传递(那是 ExecutionContext 的职责) |
| 管理自己的事务边界 | Job 级别的编排流程(那是 Job 的职责) |
| 记录自己的执行状态(read/write/skip count) | 数据库连接管理 |
| 应用容错策略(Skip/Retry) | 调度和定时触发 |
核心原理
Step 生命周期
Step 类型决策流程
完整示例
场景一:飞翔科技——Chunk Step 处理学生成绩
背景:小崔需要实现一个 Step,从 CSV 读取学生成绩,计算 GPA,写入数据库。
@Configuration
@EnableBatchProcessing
public class GradeStepConfig {
@Bean
public Step gradeProcessingStep(JobRepository jobRepository,
PlatformTransactionManager tx,
FlatFileItemReader<GradeDTO> reader,
ItemProcessor<GradeDTO, GradeEntity> processor,
JdbcBatchItemWriter<GradeEntity> writer) {
return new StepBuilder("gradeProcessingStep", jobRepository)
.<GradeDTO, GradeEntity>chunk(50, tx)
.reader(reader)
.processor(processor)
.writer(writer)
.faultTolerant()
.skip(FlatFileParseException.class)
.skipLimit(10)
.listener(new StepExecutionListener() {
@Override
public void beforeStep(StepExecution stepExecution) {
System.out.println("成绩处理开始");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("成绩处理完成: read=" + stepExecution.getReadCount()
+ ", write=" + stepExecution.getWriteCount()
+ ", skip=" + stepExecution.getSkipCount());
return stepExecution.getExitStatus();
}
})
.build();
}
}
运行结果:
成绩处理开始
Chunk 1: read 50, processed 50, wrote 50 → COMMIT (skip=0)
Chunk 2: read 50, processed 50, wrote 50 → COMMIT (skip=0)
Chunk 3: read 50, processed 49, wrote 49 → COMMIT (skip=1, line 123 parse error)
...
Chunk 100: read 50, processed 50, wrote 50 → COMMIT (skip=0)
成绩处理完成: read=5000, write=4980, skip=10
场景二:Tasklet Step 执行存储过程
@Bean
public Step settlementStep(JobRepository jobRepository,
PlatformTransactionManager tx) {
return new StepBuilder("settlementStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
String month = chunkContext.getStepContext()
.getJobParameters().get("settlementMonth").toString();
System.out.println("开始结算: " + month);
// 调用存储过程
contribution.incrementWriteCount(1);
return RepeatStatus.FINISHED;
}, tx)
.build();
}
操作前后对比:
易错场景与避坑
反例一:Chunk Step 忘记配置 ItemReader
// ❌ 编译通过但运行时崩溃
return new StepBuilder("step", jobRepository)
.<String, String>chunk(10, tx)
// 忘记 .reader()
.writer(writer)
.build();
// NullPointerException: reader is required
反例二:Step 中修改全局状态
// ❌ Step 不应修改类级别共享状态
@Component
public class BadProcessor implements ItemProcessor<Student, Student> {
private int processedCount = 0; // ❌ 非线程安全、无法持久化
@Override
public Student process(Student item) {
processedCount++; // ❌ 重启后从 0 开始
return item;
}
}
正确做法:计数通过 StepExecution 的 ReadCount/WriteCount 管理,状态通过 ExecutionContext 持久化。
面试高频考点
Q1:Step 的 Chunk 模式和 Tasklet 模式如何选型?
Chunk 模式:数据流处理(ETL),框架自动管理事务边界和断点续传。Tasklet 模式:单一操作(清理/通知/存储过程),不需要 Reader/Processor/Writer 流水线。大数据量处理必须用 Chunk。
Q2:Step 的 ExitStatus 和 BatchStatus 有什么区别?
BatchStatus 是 Step 的完成状态(COMPLETED/STARTED/FAILED),由框架管理。ExitStatus 是 Step 的退出状态码(可自定义),用于 Job 中的条件路由(
on("HIGH_SALES").to(...))。
上一章:JobRepository 详解下一章:Step 配置