Spring Batch 概述
本章定位:回答"为什么需要 Spring Batch?"——理解批处理框架要解决的核心问题、Spring Batch 的设计哲学,以及在 Java 生态中的独特定位。
定义与作用
Spring Batch 是 Spring 生态系统中的批处理框架,基于 Spring Framework 构建,专注于健壮的企业级批处理应用开发。它诞生于 2007 年(源于 Accenture 与 SpringSource 的合作),是 Java 领域最成熟的开源批处理解决方案。
在飞翔科技技术部,架构师白歌经常用一句话向新人解释 Spring Batch:
"手动写一个
for循环读文件、处理、写数据库,只需要 50 行代码。但加上断点续传、事务回滚、跳过坏数据、并行扩展和生产监控,你就需要一个框架了。Spring Batch 就是这个框架。"
Spring Batch 解决的核心问题
| 问题 | 手动实现的痛点 | Spring Batch 的答案 |
|---|---|---|
| 海量数据处理性能 | for 循环逐条处理,SQL 逐条提交 | Chunk 模式批量读写,充分利用数据库批处理能力 |
| 作业状态持久化 | 自己建表、写 SQL、管理状态字段 | JobRepository 元数据表,完整记录每次执行状态 |
| 失败恢复 | 需要自己实现"从哪中断就从哪继续"的状态机 | 基于 ExecutionContext 的断点续传,从失败点自动重启 |
| 数据校验与容错 | try-catch 嵌套,边界条件易遗漏 | Skip / Retry 机制,声明式容忍坏数据和瞬时故障 |
| 并行扩展 | 手动线程池管理、数据分片逻辑 | Partitioning 分区 + Multi-threaded Step + Remote Chunking |
| 运维监控 | 自己写日志、统计、看板 | 元数据表可查询所有执行历史,配合 Spring Cloud Data Flow 可视化 |
Spring Batch 的设计哲学
| 原则 | 含义 | 类比 |
|---|---|---|
| 关注点分离(Separation of Concerns) | 业务逻辑(ItemReader/Processor/Writer)与基础设施(事务、重启、监控)完全分离 | 快递员只管取件派件,物流追踪系统独立运行 |
| 声明式编程(Declarative) | 通过注解和 Builder API 声明 Chunk 大小、事务、容错策略,无需手写循环和控制代码 | 填写"每 50 条提交一次、遇到格式错误跳过",而不是写 if-else |
| 可扩展性(Extensibility) | 所有核心接口(Reader/Writer/Listener/Policy)开放扩展,内置大量开箱即用实现 | 乐高积木——既可用标准件快速搭建,也可自制特殊零件 |
| 与 Spring 生态深度集成 | 天然集成 Spring Boot 自动配置、Spring Data 访问层、Spring Cloud Task 微服务化 | 不是孤立的工具,而是 Spring 全家桶中的标准成员 |
核心原理
Spring Batch 在整个数据处理生态中的定位
Spring Batch 处于数据管道的中间位置:从各种数据源读取、经过业务逻辑处理、写入各种目标。它的核心价值不在于"能读能写"——这些谁都能写——而在于读写的过程管理:事务、状态、容错、并行,这些才是企业级批处理的刚需。
Spring Batch 与 Spring Boot、Spring Cloud 的关系
- Spring Boot:提供
@EnableBatchProcessing自动配置,一键创建 JobRepository、JobLauncher 等基础设施 Bean - Spring Cloud Task:将批处理作业包装为可独立部署的微服务,提供任务启动/停止事件
- Spring Cloud Data Flow:在 Task 之上提供图形化 UI 编排,拖拽式构建复杂数据流水线
完整示例
场景一:飞翔科技学生管理系统——CSV 到数据库的基础 ETL
背景:运营部高英手头有一个 5000 条学生的 CSV 文件,需要导入到学生管理系统的数据库中。技术部后端开发小崔被安排实现这个需求。
操作前:小崔手动写的脚本(脆弱版)
// 操作前:手动实现——没有事务管理、无法断点续传、遇到坏数据直接崩
public void manualImport() {
List<String> lines = Files.readAllLines(Paths.get("students.csv"));
Connection conn = DriverManager.getConnection(url, user, password);
for (String line : lines) {
String[] parts = line.split(",");
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO students (name, email, age) VALUES (?, ?, ?)");
ps.setString(1, parts[0]);
ps.setString(2, parts[1]);
ps.setInt(3, Integer.parseInt(parts[2])); // 第 500 条数据 age 写成 "abc" → 崩!前 499 条也丢了
ps.executeUpdate();
}
}
操作后:使用 Spring Batch 的健壮版
@Configuration
@EnableBatchProcessing
public class StudentImportBatchConfig {
@Bean
public Job importStudentJob(JobRepository jobRepository,
Step importStep,
JobExecutionListener listener) {
return new JobBuilder("importStudentJob", jobRepository)
.incrementer(new RunIdIncrementer())
.listener(listener)
.start(importStep)
.build();
}
@Bean
public Step importStep(JobRepository jobRepository,
PlatformTransactionManager tx,
FlatFileItemReader<Student> reader,
ItemProcessor<Student, StudentEntity> processor,
JdbcBatchItemWriter<StudentEntity> writer) {
return new StepBuilder("importStep", jobRepository)
.<Student, StudentEntity>chunk(50, tx)
.reader(reader)
.processor(processor)
.writer(writer)
.faultTolerant()
.skip(FlatFileParseException.class)
.skipLimit(10)
.build();
}
}
运行结果对比:
| 维度 | 手动实现 | Spring Batch |
|---|---|---|
| 遇到坏数据 | 整个程序崩溃,已处理数据丢失 | 跳过该条,继续处理后续数据 |
| 断点续传 | 无,必须从头再来 | 从失败的那条之后自动恢复 |
| 事务边界 | 每行一个事务或全部一个事务 | 每 50 条一个事务,粒度可控 |
| 监控 | 无,只能看控制台 | BATCH_STEP_EXECUTION 表记录 read/write/skip 数量 |
| 代码量 | 30 行(脆弱版)→ 300 行(健壮版) | 约 80 行配置 + 零手工事务代码 |
场景二:飞翔科技电商系统——每日订单汇总报表
背景:运营部杨英每天需要从订单数据库生成一份 Excel 汇总报表。产品经理孔蓝要求每天凌晨 2 点自动执行,处理约 10 万条订单记录。
实现:用 Spring Batch 的 Tasklet 模式调用存储过程,再用 Chunk 模式生成报表。
@Bean
public Job dailyOrderReportJob(JobRepository jobRepository,
Step cleanupStep,
Step aggregateStep,
Step reportStep) {
return new JobBuilder("dailyOrderReportJob", jobRepository)
.start(cleanupStep) // Step 1: 清理昨日临时表
.next(aggregateStep) // Step 2: 调用存储过程聚合数据
.next(reportStep) // Step 3: 生成 Excel 报表
.build();
}
运行效果:
Job: [dailyOrderReportJob] started at 2026-06-13 02:00:00
Step 1 [cleanupStep]: COMPLETED in 1.2s
Step 2 [aggregateStep]: COMPLETED in 45.3s (100,000 rows aggregated)
Step 3 [reportStep]: COMPLETED in 12.8s (report_20260613.xlsx generated)
Job: [dailyOrderReportJob] COMPLETED in 59.3s
易错场景与避坑
反例一:@EnableBatchProcessing 遗忘导致所有 Bean 未注册
小崔第一次搭建项目时,写好了 Job 和 Step 的 Bean 定义,但启动时报 NoSuchBeanDefinitionException:
// ❌ 忘记加 @EnableBatchProcessing
@Configuration
public class BatchConfig {
@Bean
public Job myJob(...) { ... }
// 报错:JobRepository 等基础设施 Bean 不存在
}
原因:@EnableBatchProcessing 负责注册 JobRepository、JobLauncher、JobExplorer 等核心基础设施。没有它,JobBuilderFactory / StepBuilderFactory 也不会被创建。
// ✅ 加上就对了
@Configuration
@EnableBatchProcessing
public class BatchConfig { ... }
从 Spring Batch 5.x 开始,推荐使用
DefaultBatchConfiguration子类化方式替代@EnableBatchProcessing。
反例二:H2 内存数据库用于生产环境
小崔在开发时用 H2 内存数据库作为 JobRepository,测试通过后就部署到了生产:
# ❌ 生产环境:H2 内存模式,应用重启后所有元数据丢失
spring.datasource.url=jdbc:h2:mem:testdb
后果:Job 执行失败后无法重启,因为 JobRepository 中之前的状态记录已经随着 JVM 重启而消失。
正确做法:生产环境使用持久化数据库(MySQL/PostgreSQL),并配置元数据表自动初始化:
# ✅ 生产环境
spring.datasource.url=jdbc:mysql://localhost:3306/batch_meta
spring.batch.jdbc.initialize-schema=always
面试高频考点
Q1:Spring Batch 和普通 for 循环批处理有什么本质区别?
Spring Batch 提供了声明式的事务管理(Chunk = 一个事务)、自动状态持久化(JobRepository)、断点续传(ExecutionContext)、容错机制(Skip/Retry)和并行扩展(Partitioning)。这些都是手写 for 循环需要大量额外代码才能实现的。
Q2:Spring Batch 适合处理什么类型的数据量?
没有硬性上限。通过 Paging/Cursor Reader 避免一次性加载全部数据到内存,通过 Partitioning 实现并行扩展。实际生产环境中处理千万级甚至亿级数据的案例很常见。关键在于合理的 Chunk Size、分页策略和索引优化。
Q3:Spring Batch 和 Spring Cloud Data Flow 是什么关系?
Spring Batch 是批处理框架(处理逻辑层面),Spring Cloud Data Flow 是数据流水线编排平台(架构层面)。Data Flow 可以将多个 Spring Batch Job 编排成流水线,提供可视化部署和监控。两者的关系类似于 Spring MVC 和 Kubernetes——一个负责应用内部逻辑,一个负责部署和编排。
下一章:三层架构 —— 理解 Spring Batch 如何通过 Application / Core / Infrastructure 三层分离实现关注点分离。