Job 配置
本章定位:全面掌握 Spring Batch 5.x 中 Job 的配置方式——从
@EnableBatchProcessing到 Builder API 的每个细节。
定义与作用
Job 配置是将 Step 编排为完整批处理作业的声明式定义。Spring Batch 5.x 使用 Java Config + Builder API 作为标准配置方式,替代了早期的 XML 配置。
飞翔科技后端开发小崔从 Spring Batch 4.x 迁移到 5.x 时发现的最大变化是:JobBuilderFactory 和 StepBuilderFactory 被废弃,改为直接使用 JobBuilder 和 StepBuilder,需要显式传入 JobRepository 和 PlatformTransactionManager。
核心原理
Spring Batch 5.x 配置架构
JobBuilder API 核心方法
| 方法 | 作用 | 说明 |
|---|---|---|
start(Step) | 设置第一个 Step | 必须调用 |
next(Step) | 添加下一个顺序 Step | 简单顺序流 |
on(String) | 根据 ExitStatus 条件分支 | 返回 FlowBuilder.TransitionBuilder |
to(Step) | 条件成立时跳转到的 Step | 配合 on() 使用 |
from(Step) | 从指定 Step 开始定义新分支 | 一个 Step 可以有多个分支 |
end() | 结束 Job 定义 | 返回 Job 实例 |
listener(JobExecutionListener) | 注册作业监听器 | 可多次调用 |
incrementer(JobParametersIncrementer) | 设置参数递增器 | 常用 RunIdIncrementer |
preventRestart() | 禁止 Job 重启 | 默认允许重启 |
validator(JobParametersValidator) | 设置参数校验器 | 启动前校验参数 |
StepBuilder API 核心方法
| 方法 | 作用 | 说明 |
|---|---|---|
chunk(int, PlatformTransactionManager) | 创建 Chunk Step | 指定 Chunk 大小和事务管理器 |
tasklet(Tasklet, PlatformTransactionManager) | 创建 Tasklet Step | 指定 Tasklet 和事务管理器 |
reader(ItemReader) | 设置 Reader | Chunk Step 必选 |
processor(ItemProcessor) | 设置 Processor | 可选 |
writer(ItemWriter) | 设置 Writer | Chunk Step 必选 |
faultTolerant() | 启用容错模式 | 解锁 skip/retry 配置 |
skip(Class) | 指定可跳过的异常类型 | 需先调用 faultTolerant() |
skipLimit(int) | 最大跳过次数 | 需先调用 faultTolerant() |
retry(Class) | 指定可重试的异常类型 | 需先调用 faultTolerant() |
retryLimit(int) | 最大重试次数 | 需先调用 faultTolerant() |
listener(Object) | 注册 Step 级别监听器 | 支持多种监听器类型 |
transactionManager(PlatformTransactionManager) | 指定事务管理器 | 可选,默认使用全局 |
完整示例
场景一:飞翔科技——标准三阶段 Job 的完整配置
背景:小崔需要为运营部配置一个标准的订单处理 Job,包含清理、导入和报告三个阶段。
@Configuration
@EnableBatchProcessing
public class OrderProcessingConfig {
// ==================== Job 定义 ====================
@Bean
public Job orderProcessingJob(JobRepository jobRepository,
Step cleanupStep,
Step importStep,
Step reportStep,
JobExecutionListener orderJobListener) {
return new JobBuilder("orderProcessingJob", jobRepository)
.incrementer(new RunIdIncrementer())
.listener(orderJobListener)
.start(cleanupStep)
.next(importStep)
.next(reportStep)
.build();
}
// ==================== Step 1: 清理(Tasklet) ====================
@Bean
public Step cleanupStep(JobRepository jobRepository,
PlatformTransactionManager tx) {
return new StepBuilder("cleanupStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("清理临时数据完成");
return RepeatStatus.FINISHED;
}, tx)
.build();
}
// ==================== Step 2: 导入(Chunk) ====================
@Bean
public Step importStep(JobRepository jobRepository,
PlatformTransactionManager tx,
FlatFileItemReader<Order> orderReader,
ItemProcessor<Order, OrderEntity> orderProcessor,
JdbcBatchItemWriter<OrderEntity> orderWriter) {
return new StepBuilder("importStep", jobRepository)
.<Order, OrderEntity>chunk(50, tx)
.reader(orderReader)
.processor(orderProcessor)
.writer(orderWriter)
.faultTolerant()
.skip(FlatFileParseException.class)
.skipLimit(100)
.build();
}
// ==================== Step 3: 报告(Tasklet) ====================
@Bean
public Step reportStep(JobRepository jobRepository,
PlatformTransactionManager tx) {
return new StepBuilder("reportStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("导入报告已生成");
return RepeatStatus.FINISHED;
}, tx)
.build();
}
// ==================== JobExecutionListener ====================
@Bean
public JobExecutionListener orderJobListener() {
return new JobExecutionListener() {
@Override
public void beforeJob(JobExecution jobExecution) {
System.out.println("订单处理开始: " + jobExecution.getJobParameters().getString("orderDate"));
}
@Override
public void afterJob(JobExecution jobExecution) {
System.out.println("订单处理完成,状态: " + jobExecution.getStatus());
}
};
}
}
场景二:条件分支 Job 配置
背景:运营部杨英的每日报表 Job 需要根据前一天的销售额决定是否生成详细分析报告。销售额 > 10 万元时才执行分析 Step。
@Bean
public Job salesReportJob(JobRepository jobRepository,
Step dailyReportStep,
Step detailAnalysisStep,
Step notifyStep) {
return new JobBuilder("salesReportJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(dailyReportStep)
.on("HIGH_SALES").to(detailAnalysisStep) // 高销售额 → 详细分析
.from(dailyReportStep)
.on("*").to(notifyStep) // 其他 → 简单通知
.from(detailAnalysisStep)
.on("*").to(notifyStep) // 分析完成后也通知
.end()
.build();
}
操作前后对比:
| 场景 | 销售额 | dailyReportStep ExitStatus | 执行路径 |
|---|---|---|---|
| 平淡日 | 8 万 | COMPLETED | dailyReport → notify |
| 火爆日 | 15 万 | HIGH_SALES | dailyReport → detailAnalysis → notify |
| 失败 | — | FAILED | dailyReport → (Job 结束,状态 FAILED) |
易错场景与避坑
反例一:5.x 迁移时继续使用 JobBuilderFactory
// ❌ Spring Batch 5.x 中 JobBuilderFactory 已废弃
@Autowired
private JobBuilderFactory jobBuilderFactory; // 编译警告
// ❌ StepBuilderFactory 也已废弃
@Autowired
private StepBuilderFactory stepBuilderFactory;
正确做法:
// ✅ 直接使用 JobBuilder / StepBuilder,注入 JobRepository
@Bean
public Job myJob(JobRepository jobRepository, Step step) {
return new JobBuilder("myJob", jobRepository)
.start(step)
.build();
}
@Bean
public Step myStep(JobRepository jobRepository,
PlatformTransactionManager tx,
ItemReader<String> reader,
ItemWriter<String> writer) {
return new StepBuilder("myStep", jobRepository)
.<String, String>chunk(10, tx)
.reader(reader)
.writer(writer)
.build();
}
反例二:忘记传 PlatformTransactionManager 到 chunk()
// ❌ 不传事务管理器——编译通过但运行时使用默认事务管理器
return new StepBuilder("step", jobRepository)
.<String, String>chunk(10) // 缺少 tx 参数!
.reader(reader)
.writer(writer)
.build();
问题:在多数据源场景下,不指定事务管理器可能导致事务应用到错误的数据源,造成数据不一致。
正确做法:始终显式传入事务管理器。
面试高频考点
Q1:Spring Batch 5.x 相比 4.x 在配置方式上有哪些变化?
JobBuilderFactory和StepBuilderFactory被废弃,改为直接使用JobBuilder和StepBuilder,需要显式注入JobRepository。chunk()方法需要传入PlatformTransactionManager。@EnableBatchProcessing的自动配置行为有所调整,推荐使用DefaultBatchConfiguration子类化。
Q2:Job 的条件分支中 on("*") 和 on("COMPLETED") 的区别?
on("*")匹配任意 ExitStatus(是通配符),on("COMPLETED")只匹配 COMPLETED。分支按声明顺序匹配,第一个匹配的分支生效。通常将on("*")放在最后作为默认分支。
Q3:faultTolerant() 方法做了什么?
它创建了一个
FaultTolerantStepBuilder,解锁了 skip()、retry()、noRollback()、skipPolicy()、retryPolicy() 等容错配置方法。不调用faultTolerant()时,这些方法不可用——Step 中任何异常都会导致 Chunk 回滚和 Step 失败。
上一章:Job 详解下一章:JobParameters 详解