Job 详解
本章定位:掌握 Spring Batch 的顶级概念 Job——它的职责边界、组成结构、配置方式以及与 JobInstance/JobExecution 的协作机制。
定义与作用
Job 是 Spring Batch 中的顶级批处理概念,封装了整个批处理过程的抽象。一个 Job 由一到多个 Step 按顺序或条件编排组成,是启动批处理任务的基本单元。
飞翔科技架构师白歌在团队培训中这样定义:
"Job 是你对整个批处理任务的'剧本'——第一幕做什么、第二幕做什么、什么情况下跳到备选路径。Step 是每一幕的具体内容。你不需要写主循环和异常处理,Spring Batch 帮你演完这出戏。"
Job 的职责边界:
| Job 负责 | Job 不负责 |
|---|---|
| 定义 Step 的执行顺序和条件分支 | 具体的数据读取/处理/写入(那是 Step 的职责) |
| 注册监听器(JobExecutionListener) | 单个 Chunk 的事务管理(那是 Step 的内部机制) |
| 配置 JobParametersIncrementer | 数据库连接和资源管理 |
| 设置是否可重启(restartable) | 作业调度和定时触发(那是外部调度器的职责) |
核心原理
Job 的执行流程
Job 与 Step 的关系
Job
├── Step 1: 数据校验(Tasklet)
├── Step 2: 数据导入(Chunk: Reader → Processor → Writer)
│ ├── Chunk 1: 50 条 → COMMIT
│ ├── Chunk 2: 50 条 → COMMIT
│ └── ...
└── Step 3: 发送通知(Tasklet)
关键规则:
| 规则 | 说明 |
|---|---|
| 每个 Job 至少包含一个 Step | 没有 Step 的 Job 没有意义 |
| Step 按声明顺序执行 | 除非通过 on() 指定条件分支 |
| 一个 Step 失败不等于 Job 立即失败 | 通过条件分支可跳转到恢复 Step |
| Job 是单例 Bean | 但每次 run() 创建新的 JobInstance 和 JobExecution |
完整示例
场景一:飞翔科技——学生数据导入的三阶段 Job
背景:产品经理孔蓝设计的学生数据导入流程包含三个阶段——先校验文件格式、再导入数据、最后生成导入报告。小崔用 Job 编排这三个 Step。
@Configuration
@EnableBatchProcessing
public class StudentImportJobConfig {
@Bean
public Job studentImportJob(JobRepository jobRepository,
Step validateStep,
Step importStep,
Step reportStep,
JobExecutionListener listener) {
return new JobBuilder("studentImportJob", jobRepository)
.incrementer(new RunIdIncrementer())
.listener(listener)
.start(validateStep) // Step 1: 校验 CSV 格式
.next(importStep) // Step 2: 导入数据
.next(reportStep) // Step 3: 生成报告
.build();
}
}
运行结果:
Job: [studentImportJob] started with run.id=1
JobExecutionListener.beforeJob() → 作业开始,文件: students_20260613.csv
Step 1 [validateStep]: COMPLETED (CSV 格式正确,共 5000 行)
Step 2 [importStep]: COMPLETED (read=5000, write=4980, skip=20)
Step 3 [reportStep]: COMPLETED (报告已生成: import_report_20260613.txt)
JobExecutionListener.afterJob() → 作业完成,耗时: 12.3s, 状态: COMPLETED
场景二:带条件分支的 Job——处理导入失败
背景:运营部高英反映有时候 CSV 文件格式有严重问题(如缺少必填列),此时不应该继续导入,而是应该发送告警邮件。
@Bean
public Job studentImportWithFailureHandling(JobRepository jobRepository,
Step validateStep,
Step importStep,
Step reportStep,
Step alertStep) {
return new JobBuilder("studentImportWithFailureHandling", jobRepository)
.incrementer(new RunIdIncrementer())
.start(validateStep)
.on("FAILED").to(alertStep) // 校验失败 → 发告警
.from(validateStep)
.on("*").to(importStep) // 校验成功 → 导入
.from(importStep)
.on("COMPLETED").to(reportStep) // 导入成功 → 报告
.from(importStep)
.on("FAILED").to(alertStep) // 导入失败 → 告警
.end()
.build();
}
运行结果(校验失败场景):
Job: [studentImportWithFailureHandling] started
Step 1 [validateStep]: FAILED (缺少必填列: email)
Step 4 [alertStep]: 已发送告警邮件到 ops@feixiang.net
Step 4 [alertStep]: COMPLETED
Job: [studentImportWithFailureHandling] COMPLETED (通过告警路径结束)
易错场景与避坑
反例一:在 Job Bean 中写业务逻辑
小崔刚学 Spring Batch 时,把数据处理逻辑写在了 Job 的 @Bean 方法里:
// ❌ 错误:Job 的 Bean 方法只应做配置,不应包含运行时逻辑
@Bean
public Job badJob(JobRepository jobRepository, Step step) {
// ❌ 这些代码在 Bean 初始化时执行,不是在 Job 运行时执行!
List<String> lines = Files.readAllLines(Paths.get("data.csv"));
for (String line : lines) {
processLine(line);
}
return new JobBuilder("badJob", jobRepository)
.start(step)
.build();
}
问题:@Bean 方法在 Spring 容器初始化时执行,不是 Job 运行时。数据处理逻辑必须在 Step 内部的 Reader/Processor/Writer 或 Tasklet 中实现。
反例二:restartable 设置不当
// ❌ 设置为不允许重启,但业务需要重启能力
@Bean
public Job job(JobRepository jobRepository, Step step) {
return new JobBuilder("job", jobRepository)
.preventRestart() // 禁止重启
.start(step)
.build();
}
// 第一次执行失败后,第二次直接用相同参数启动 → JobRestartException
正确做法:只在确实不需要重启的场景(如发送一次性通知)才设置 preventRestart()。大多数 ETL 作业应该保持默认可重启。
面试高频考点
Q1:Job 和 Step 的生命周期是如何关联的?
Job 的生命周期由其中的 Step 的执行结果决定。所有 Step 成功完成 → Job COMPLETED。任一 Step 失败且没有条件分支处理 → Job FAILED。Step 之间有先后顺序和条件路由,Job 负责整体编排。
Q2:如何禁止同一个 Job 的重复执行?
Spring Batch 设计上防止了同一 JobInstance 的重复执行(COMPLETED 后不可再启动)。通过使用
RunIdIncrementer或在 JobParameters 中加入时间戳,可以保证每次执行产生新的 JobInstance,实现"每天跑同一个 Job"而不冲突。
Q3:Job 可以动态添加 Step 吗?
Job 是静态定义(Spring Bean),在容器启动时就确定了 Step 编排。但可以通过
JobExecutionDecider在运行时动态决定执行路径,或者使用Flow实现更灵活的编排。如果需要完全动态的 Step 组合,考虑使用JobBuilder在JobLauncher.run()之前编程式构建。
上一章:Tasklet 处理模型下一章:Job 配置