Spring Batch核心模型与快速上手详解
定位: Batch 第 01 篇,讲透批处理定位、Job/Step/Chunk 核心模型、Reader-Processor-Writer 与执行元数据体系
适用版本:Spring Batch 5.x(Spring Boot 3.x)
目录
一、批处理定位
1.1 典型场景
| 场景 | 例子 |
|---|---|
| 数据导入/导出 | 对账文件导入、报表导出 |
| 批量加工 | 夜间结算、积分计算、数据迁移 |
| 定期同步 | 与外部系统的数据交换 |
1.2 与在线处理的差异
| 维度 | 在线(请求) | 批处理 |
|---|---|---|
| 触发 | 用户请求 | 定时/手动/事件 |
| 数据量 | 单条/少量 | 大批量(万~亿级) |
| 目标 | 低延迟 | 高吞吐 |
| 失败处理 | 即时报错 | 记录位置、可重跑 |
| 时效 | 实时 | 离线/窗口期 |
批处理的核心诉求:大批量、可重跑、可监控、失败可恢复------Spring Batch 就是围绕这些构建的框架。
二、核心模型
2.1 三层结构
Job(一次批处理任务)
└── Step(处理阶段)
└── Chunk(读-处理-写的批量单元)
| 概念 | 职责 |
|---|---|
| Job | 批处理的完整定义:由若干 Step 组成,带参数 |
| JobInstance | Job + 参数的一次逻辑运行 |
| JobExecution | JobInstance 的一次物理执行(重跑产生新 execution) |
| Step | 独立的处理阶段,有自己的状态 |
| StepExecution | Step 的一次执行记录 |
Job/参数/执行的区分是理解重跑与监控的基础:同一 Job 不同参数是不同实例;同一实例失败重跑是新的 execution。
2.2 两种 Step
| 类型 | 用途 |
|---|---|
| Tasklet Step | 执行单个任务(清理文件、准备环境),非面向数据 |
| Chunk-oriented Step | 面向数据的分块处理(主流),Reader-Processor-Writer |
2.3 Chunk 的语义
每次读取 chunkSize 条 → 逐条 Processor → 整块 Writer 写出并提交
→ 提交点:一个 Chunk 一个事务
提交点(commit interval)= chunkSize:每处理完一个 Chunk 提交一次。意义:
- 失败只影响当前 Chunk(之前的已提交);
- 内存占用可控(一次只持有一个 Chunk);
- 重跑从上次失败的 Chunk 附近继续。
三、Reader-Processor-Writer
3.1 三件套职责
| 组件 | 接口 | 行为 |
|---|---|---|
| ItemReader | read() |
逐条读取,返回 null 表示读完 |
| ItemProcessor | process(item) |
逐条转换/校验/过滤;返回 null 表示丢弃该条 |
| ItemWriter | write(Chunk) |
接收整块数据批量写出 |
3.2 常见实现
Reader:FlatFileItemReader(文件)、JdbcCursorItemReader / JdbcPagingItemReader(库)、
KafkaItemReader、自定义
Writer:JdbcBatchItemWriter(批量 SQL)、FlatFileItemWriter、
RepositoryItemWriter、自定义
3.3 代码示例(骨架)
java
@Bean
Step importStep(JobRepository repo, PlatformTransactionManager tm) {
return new StepBuilder("import", repo)
.<Input, Entity>chunk(500, tm) // chunkSize = 500
.reader(reader()) // 读
.processor(processor()) // 处理(可选)
.writer(writer()) // 写
.build();
}
@Bean
Job importJob(JobRepository repo, Step importStep) {
return new JobBuilder("importJob", repo)
.start(importStep)
.build();
}
四、执行元数据与仓库
4.1 三个核心组件
| 组件 | 职责 |
|---|---|
| JobRepository | 持久化 Job/Step 执行状态(开始/结束时间、状态、提交数、读写计数) |
| JobLauncher | 启动 Job(传入参数) |
| JobExplorer | 只读查询执行历史(监控/审计/判断是否可重跑) |
4.2 元数据表
Spring Batch 需要一组元数据表(BATCH_JOB_INSTANCE、BATCH_JOB_EXECUTION、BATCH_STEP_EXECUTION 等)存执行状态:
作用:
① 记录每次执行的状态与进度 → 支撑重启
② 防止同一 JobInstance 并发重复执行
③ 提供监控与审计数据
Boot 下引入依赖后自动建表(生产建议显式管理表结构)。元数据库可与业务库分离。
4.3 状态机
Job/Step 状态:
STARTING → STARTED → COMPLETED / FAILED / STOPPED
失败后可重启(从失败处继续,见第 02 篇)
五、快速上手
5.1 Boot 最小配置
依赖:spring-boot-starter-batch
数据源:元数据表所在库(默认自动建表)
配置:
spring.batch.jdbc.initialize-schema=always # 开发用;生产手动建表
spring.batch.job.enabled=false # 关闭启动自动跑(按需触发)
5.2 触发方式
启动时自动执行(Boot 默认,可关)
定时调度(@Scheduled / 调度平台触发)
手动/接口触发(JobLauncher.run(job, params))
5.3 参数与幂等
JobParameters 区分运行(如 date=2026-08-26)
同参数重复启动:默认拒绝(已完成的实例)→ 需加时间戳参数或用识别参数
六、总结
- 定位:大批量、离线、可重跑的加工框架;吞吐优先,与在线处理互补。
- 核心模型:Job(任务)→ Step(阶段,Tasklet/Chunk)→ Chunk(提交单元);JobInstance 与 JobExecution 区分逻辑运行与物理执行。
- 三件套:Reader 逐条读、Processor 逐条处理(可丢弃)、Writer 按块写;提交点 = chunkSize。
- 元数据:JobRepository 持久化状态支撑重启与防重;JobLauncher 启动、JobExplorer 查询;依赖一组元数据表。
- 上手:Boot starter + 数据源;触发方式多样;用参数区分运行,注意重复启动行为。
七、常见高频面试题
1. Spring Batch 的核心概念有哪些?
要点:三层模型。Job 是一次批处理任务的定义,由多个 Step 组成;Step 是独立处理阶段,分 Tasklet(单任务)与 Chunk 面向数据两种;Chunk 是"读-处理-写"的批量单元与事务提交点。执行概念:JobInstance(Job + 参数的逻辑运行)、JobExecution(一次物理执行,重跑产生新 execution)、StepExecution。配套组件:JobRepository(状态持久化)、JobLauncher(启动)、JobExplorer(查询)。
2. chunk size(提交间隔)是什么?怎么定?
要点:chunkSize 决定一次读取-处理-写入的批量大小,也是事务提交点------每处理完一个 Chunk 提交一次。影响:太小则提交频繁、吞吐低;太大则内存占用高、失败回滚代价大、重启粒度粗。设定依据:单条处理成本、批量写入效率(如批量 SQL 的最佳批大小)、内存限制、可接受的失败回滚范围。常见从几百起,压测调优。它本质是"吞吐与失败代价"的平衡。
3. ItemProcessor 返回 null 会怎样?和抛异常有什么区别?
要点:返回 null 表示过滤掉该条------数据被跳过不写出,批处理继续,这是"校验不通过则丢弃"的正常手段(如脏数据)。抛异常则不同------异常触发错误处理(重试/跳过策略,未配置则 Step 失败)。区分:业务上"这条不要了"用 null;"这条处理失败要按错误策略处理"抛异常。配合 skip 策略,抛异常也可配置为跳过并记录。
4. Spring Batch 如何记录和管理任务执行状态?
要点:通过 JobRepository 把执行元数据持久化到一组元数据表:BATCH_JOB_INSTANCE(逻辑实例)、BATCH_JOB_EXECUTION(每次执行的状态、开始/结束时间、退出码)、BATCH_STEP_EXECUTION(每步的读写计数、提交数、状态)。作用:① 支撑重启------从失败位置继续;② 防止同一实例并发重复执行;③ 监控审计------JobExplorer 查询。所以 Spring Batch 需要一个数据源存元数据,可与业务库分离。
5. JobInstance、JobExecution、JobParameters 的关系?
要点:Job 是任务定义;JobParameters 标识一次运行的输入(如日期);Job = Job + 参数 确定一个 JobInstance(逻辑运行);每次实际执行产生一个 JobExecution。例如同一天对账参数相同 → 同一 JobInstance;第一次跑失败、重跑一次 → 两个 JobExecution 属于同一 JobInstance。重启机制依赖此模型:失败的 execution 可重启,已完成的 instance 默认拒绝重复运行。
6. Reader/Writer 有哪些常用实现?游标读和分页读的区别?
要点:常用:读------FlatFileItemReader(文件)、JdbcCursorItemReader(游标)、JdbcPagingItemReader(分页)、KafkaItemReader;写------JdbcBatchItemWriter(批量 SQL)、FlatFileItemWriter、RepositoryItemWriter。游标读:数据库游标流式读取,内存占用低,但要求连接在读取期间保持打开(事务/连接生命周期要注意);分页读:按页查询(LIMIT/OFFSET 或 keyset),无长游标问题,适合数据在读取中可能变化的场景,但深分页有性能问题(用 keyset)。选择按数据量与一致性需求。
7. Spring Batch 和用 @Scheduled 写个循环处理有什么区别?
要点:@Scheduled + 循环是手工批处理:要自己实现进度记录、失败重跑、提交控制、并发防护、监控,规模一大问题全暴露。Spring Batch 提供框架级能力:提交点控制(chunk)、状态持久化(可重启续跑)、跳过/重试策略、并行处理、执行元数据监控、防重复执行。适用:数据量大、要求可靠可重跑、需要审计监控的批处理用框架;小数据量简单任务用定时循环即可。按复杂度选工具。
8. 同一个 Job 如何避免被重复执行?
要点:机制:Spring Batch 用 JobParameters 确定 JobInstance;对已完成的 JobInstance,再次用相同参数启动默认抛 JobInstanceAlreadyCompleteException 拒绝。实践:① 用业务参数(日期)标识运行,同一天不会跑两次;② 需要重跑时用新参数(如加时间戳或重跑标记);③ 并发防护靠元数据表的实例唯一性。理解参数语义是控制"防重与重跑"的关键。
9. 元数据表应该放在哪?和业务库一起吗?
要点:可以分离。元数据表存框架执行状态,与业务数据无关;放在独立库的好处:批处理元数据操作不影响业务库、权限与备份策略独立、多个批处理应用可共享。也可同库(简单场景)。注意:① 生产显式管理表结构(不依赖自动建表);② 元数据库故障会导致任务无法启动/记录,也需保障可用;③ 历史数据要定期清理(元数据会增长)。
10. 如何触发一个 Batch Job?
要点:方式:① 应用启动自动执行(Boot 默认,生产常关闭);② 定时调度------@Scheduled 或外部调度平台(推荐生产用调度平台,有失败告警/依赖编排);③ 手动/接口触发------注入 JobLauncher 调 run(job, params),适合补数/重跑;④ 事件触发(消息到达)。生产实践:调度平台定时 + 支持手动重跑;触发时传入正确参数区分运行;监控执行状态与时长。
