目录
二、Spring Batch 与 Spring Boot 的关系
四、Spring Batch vs Quartz:根本不是一回事!
五、核心概念:Job, Step, JobInstance, JobExecution
5.6、控制流程:Flow, Split, Decision
六、实战:用 Spring Boot 快速构建一个 Batch Job
八、Spring Batch + Scheduling 定时执行 Job

一、spring Batch 是什么?
Spring Batch 是一个轻量级、功能全面的批处理框架,隶属于 Spring 生态系统。它旨在简化和优化处理大量数据的自动化任务。其核心优势在于提供了可重启(Restartability)、可追-溯(Traceability)、**事务管理(Transaction Management)和性能优化(Performance Optimization)**的标准化能力。
简单来说,Spring Batch 提供了一套成熟的“流水线”模型,你只需要关注具体的业务逻辑(读数据、处理数据、写数据),而框架会帮你处理所有棘手的底层问题,如事务、并发、重启、日志和监控。

二、Spring Batch 与 Spring Boot 的关系
Spring Batch 可以独立使用,但与 Spring Boot 结合时,它的威力才能得到最大程度的释放。Spring Boot 遵循“约定优于配置”的原则,为 Spring Batch 提供了强大的自动配置能力:
-
自动配置
JobRepository:自动配置数据源,用于存储批处理作业的元数据。 -
自动配置
JobLauncher:提供启动 Job 的入口。 -
自动扫描和执行:可以轻松地在应用启动时自动执行 Job。
-
简化的依赖管理:只需引入
spring-boot-starter-batch即可获得所有核心依赖。
可以说,Spring Boot 是现代 Spring Batch 应用的最佳实践和标准启动器。
三、Spring Batch 的应用场景
-
ETL (Extract-Transform-Load):最经典的应用场景。从异构数据源(如数据库、CSV 文件、消息队列)中提取(Extract)数据,进行必要的转换(Transform)(如数据清洗、格式化、业务逻辑计算),然后**加载(Load)**到目标系统(如数据仓库、新的数据库表)。
-
大数据处理:虽然不如 Spark、Flink 等专为大数据设计的计算引擎强大,但在“读-处理-写”这类面向记录的场景下,Spring Batch 可以作为数据处理管道中的一个重要环节,尤其适合与 Spring Cloud Data Flow 结合,编排复杂的数据流。
-
常规批量处理:
- 金融清算:每日收盘后,对海量交易数据进行对账、结算。
- 报表生成:定期汇总业务数据,生成复杂的统计报表。
- 数据归档/迁移:将老旧数据从生产库迁移到归档库。
- 通知服务:批量发送邮件、短信或推送通知。
四、Spring Batch vs Quartz:根本不是一回事!
这是一个常见的误区。很多人会将 Spring Batch 与 Quartz 混淆,但它们解决的是完全不同的问题。
-
Quartz:一个纯粹的任务调度框架(Scheduler)。它的核心职责是“When”——在什么时间点、以什么频率触发一个任务。它不关心任务具体做什么。
-
Spring Batch:一个批处理框架(Batch Framework)。它的核心职责是“What & How”——定义一个复杂的批处理任务的逻辑、流程和执行策略。它不关心任务何时被触发。
正确的姿势是协同工作:使用 Quartz 设置定时规则(例如,每天凌晨 2 点),触发 JobLauncher 来启动一个 Spring Batch 作业。
五、核心概念:Job, Step, JobInstance, JobExecution
理解 Spring Batch 的领域模型是掌握它的第一步。
-
Job:整个批处理作业的抽象,是你想要执行的那个“任务”本身。一个 Job 由一个或多个 Step 组成。
-
Step:一个独立的、具体的处理阶段。比如,“从 CSV 读取数据并写入数据库”可以是一个 Step,“生成报表文件”是另一个 Step。一个 Job 就是这些 Step 的有序组合。
-
JobInstance:一次逻辑上的 Job 运行。它由 Job 的名称和一组用于识别的
JobParameters唯一确定。例如,“2025-08-05 的日报表生成任务”就是一个JobInstance。这个实例无论成功还是失败,都代表着这一次逻辑运行。 -
JobExecution:一次物理上的 JobInstance 尝试。一个
JobInstance可能有多次JobExecution。例如,如果“2025-08-05 的日报表生成任务”第一次运行失败了,你可以重新发起,这将产生一个新的JobExecution,但它们都属于同一个JobInstance。正是这种设计,使得失败重启成为可能。
5.1、架构与执行流程
下面是 Spring Batch 的经典架构图和执行流程,它清晰地展示了各个组件如何协同工作。
架构图 (Conceptual Architecture)

+------------------+ +-------------------+ +-----------------+
| JobLauncher | ----> | Job | ----> | Step |
+------------------+ +-------------------+ +--------+--------+
| | |
v v v
+------------------+ +-------------------+ +-----------------+
| JobRepository | <---- | JobExecution | <---- | StepExecution |
| (Stores Metadata)| +-------------------+ +-----------------+
+------------------+
执行流程 (Execution Flow)
-
应用通过
JobLauncher传入Job和JobParameters来启动一个作业。 -
JobLauncher从JobRepository中查询,判断是创建新的JobInstance和JobExecution,还是基于已有的JobInstance进行重启。 -
Job对象根据其定义,按顺序(或并行)执行其包含的Step。 -
在
Step执行期间,StepExecution的状态(如读取了多少条目、提交次数、当前状态等)会被周期性地持久化到JobRepository。这是实现失败重启的关键。 -
Step执行业务逻辑(例如,读-处理-写)。 -
所有
Step执行完毕后,JobExecution的最终状态被更新到JobRepository。
5.2、Step 详解:Tasklet vs. Chunk
一个 Step 的具体行为由其内部实现决定,主要有两种模式:
-
Tasklet(任务let)模式:最简单的模式,整个
Step就是一个单一的任务。它只有一个execute方法,该方法会被框架循环调用,直到返回RepeatStatus.FINISHED。-
适用场景:执行一个简单的、非面向记录集的任务,例如:调用一个存储过程、执行一条 DDL 语句、清理临时文件等。
-
-
Chunk(分块)模式:这是 Spring Batch 最核心、最强大的模式,专为面向记录的处理设计。它遵循“读-处理-写”的范式。
+--------------+ +-----------------+ +--------------+ | ItemReader | ---> | ItemProcessor | ---> | ItemWriter | +--------------+ +-----------------+ +--------------+ | | | (Read one item) (Process one item) (Write a chunk)-
ItemReader:负责从数据源(文件、数据库、JMS等)中一次读取一个数据项(Item)。当它返回null时,表示数据已全部读取完毕。 -
ItemProcessor:可选的处理环节。负责对ItemReader读取到的数据项进行业务转换或处理。如果某个数据项不应被继续处理,ItemProcessor可以返回null,该数据项将被过滤掉。 -
ItemWriter:负责将处理后的数据项进行输出。与 Reader 不同,ItemWriter一次接收一个数据项列表(即一个“Chunk”),并进行批量写入。
分块模式的优势:
-
事务性:框架会自动为每个 Chunk 的处理创建一个事务。如果在写入一个 Chunk 的过程中发生错误,整个 Chunk 的操作会回滚,保证了数据的一致性。
-
性能:批量写入(例如,JDBC Batch Update)远比单条写入高效。
-
可重启性:框架会记录每个成功提交的 Chunk。如果作业失败,重启后可以从上一个失败的 Chunk 开始,而不是从头再来。
commit-interval参数用于定义一个 Chunk 的大小。
-
5.3、关键组件的作用
-
JobLauncher:如其名,作业启动器。它是批处理作业的入口点,负责接收Job和JobParameters并启动执行流程。 -
JobRepository:作业仓库,是整个框架的状态持久化核心。它存储了所有JobExecution、StepExecution以及相关的上下文信息。默认情况下,Spring Boot 会使用内存数据库(如 H2)或配置好的数据源来创建所需的元数据表。在生产环境中,必须使用持久化数据库(如 MySQL, PostgreSQL)来配置JobRepository,否则重启功能将失效。
5.4、参数与上下文:在 Job 中传递信息
-
JobParameters:用于识别一个JobInstance。它是一组键值对,通常在启动 Job 时传入。例如,new JobParametersBuilder().addString("runDate", "2025-08-05").toJobParameters()。同一Job配合不同的JobParameters会产生不同的JobInstance。 -
ExecutionContext:一个键值对集合,用于在批处理执行期间存储需要共享的数据。它分为两种:-
StepExecutionContext: 作用域是单个StepExecution。用于Step内部(如ItemReader和ItemWriter之间)的数据传递。每次Step执行都是新的。 -
JobExecutionContext: 作用域是整个JobExecution。用于在不同 Step 之间共享数据。例如,第一个 Step 计算出一个总数,存入JobExecutionContext,后续的 Step 可以读取这个总数。这个上下文会随着JobExecution一同被持久化到JobRepository。
-
5.5、错误处理:Retry 与 Skip 策略
健壮性是 Spring Batch 的核心卖点。
-
Retry(重试):当处理一个 Item 时发生瞬时性异常(如网络抖动、数据库死锁),我们希望重试这个操作。可以配置重试策略,比如重试3次,仅对特定异常(如
OptimisticLockingFailureException)生效。如果重试耗尽仍失败,任务将告失败。 -
Skip(跳过):当处理一个 Item 时发生确定性异常(如数据格式错误、违反业务约束),我们不希望整个 Job 失败,而是希望跳过这个“脏数据”,记录下来,然后继续处理下一个。可以配置跳过的异常类型和最大跳过次数。
5.6、控制流程:Flow, Split, Decision
除了简单的线性执行 Step1 -> Step2 -> Step3,Spring Batch 还支持复杂的流程控制。
-
Flow:可以将一组 Step 封装成一个可复用的“流”。
-
Split:允许并行执行多个 Flow。例如,可以同时从两个不同的文件读取数据,这两个读取操作可以放在一个 Split 的两个并行 Flow 中执行,以提高效率。
-
Decision:实现条件流转。基于上一个 Step 的退出状态(
ExitStatus),JobExecutionDecider可以决定下一个要执行的 Step 是哪一个,实现if-then-else的逻辑。
六、实战:用 Spring Boot 快速构建一个 Batch Job
案例一:从 CSV 文件读取数据并写入数据库
这是最经典的 ETL 场景。假设我们有一个 users.csv 文件,需要将其中的用户数据写入数据库的 users 表。
1. 添加依赖 (pom.xml)
2. 准备 CSV 文件和实体类
src/main/resources/users.csv:
id,firstName,lastName
1,John,Doe
2,Jane,Smith
3,Peter,Jones
User.java 实体类:
@Entity
public class User {
@Id
private Long id;
private String firstName;
private String lastName;
// Getters and Setters
}
3. 编写 Batch 配置
@Configuration
@EnableBatchProcessing // 开启 Spring Batch 支持
public class CsvToDbJobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
// 1. 定义 ItemReader
@Bean
public FlatFileItemReader<User> csvItemReader() {
return new FlatFileItemReaderBuilder<User>()
.name("csvItemReader")
.resource(new ClassPathResource("users.csv"))
.delimited()
.names("id", "firstName", "lastName")
.targetType(User.class)
.build();
}
// 2. 定义 ItemProcessor (可选)
@Bean
public ItemProcessor<User, User> userItemProcessor() {
// 示例:将姓氏转换为大写
return user -> {
user.setLastName(user.getLastName().toUpperCase());
return user;
};
}
// 3. 定义 ItemWriter
@Bean
public JpaItemWriter<User> jpaItemWriter(EntityManagerFactory entityManagerFactory) {
JpaItemWriter<User> writer = new JpaItemWriter<>();
writer.setEntityManagerFactory(entityManagerFactory);
return writer;
}
// 4. 定义 Step
@Bean
public Step csvToDbStep(FlatFileItemReader<User> csvItemReader,
ItemProcessor<User, User> userItemProcessor,
JpaItemWriter<User> jpaItemWriter) {
return stepBuilderFactory.get("csvToDbStep")
.<User, User>chunk(10) // Chunk aize = 10
.reader(csvItemReader)
.processor(userItemProcessor)
.writer(jpaItemWriter)
.build();
}
// 5. 定义 Job
@Bean
public Job csvToDbJob(Step csvToDbStep) {
return jobBuilderFactory.get("csvToDbJob")
.incrementer(new RunIdIncrementer()) // 使用 run.id 作为 JobParameter,保证每次运行都是新的 JobInstance
.flow(csvToDbStep)
.end()
.build();
}
}
现在,启动 Spring Boot 应用,这个 Job 就会被执行,数据会自动从 CSV 导入 H2 数据库。
案例二:读取数据库数据并导出为 Excel
Spring Batch 没有内置的 Excel ItemWriter。我们需要自定义一个,通常会借助 Apache POI 库。
-
ItemReader: 使用
JdbcCursorItemReader或JpaPagingItemReader来高效地从数据库分页读取数据。 -
ItemWriter: 创建一个自定义的
ItemWriter,它实现ItemWriter<T>接口。-
在
open()方法(通过ItemStream接口实现)中初始化 Apache POI 的Workbook和Sheet。 -
在
write(List<? extends T> items)方法中,遍历items列表,为每个 item 创建一行数据并写入Sheet。 -
在
close()方法(ItemStream接口)中,将Workbook写入文件输出流并关闭资源。
-
案例三:多 Step 串行与并行执行
串行执行:
@Bean
public Job multiStepJob(Step step1, Step step2, Step step3) {
return jobBuilderFactory.get("multiStepJob")
.start(step1)
.next(step2)
.next(step3)
.build();
}
并行执行:
@Bean
public Job parallelStepJob(Step stepA, Step stepB, Step stepC) {
Flow flowA = new FlowBuilder<Flow>("flowA").start(stepA).build();
Flow flowB = new FlowBuilder<Flow>("flowB").start(stepB).build();
return jobBuilderFactory.get("parallelStepJob")
.start(flowA)
.split(new SimpleAsyncTaskExecutor()) // 使用线程池并行执行
.add(flowB) // 并行执行 flowB
.next(stepC) // flowA 和 flowB 都执行完毕后,再执行 stepC
.end()
.build();
}
七、如何处理失败重试与跳过
在 chunk() 定义中添加策略即可:
@Bean
public Step resilientStep() {
return stepBuilderFactory.get("resilientStep")
.<Input, Output>chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.faultTolerant() // 开启容错模式
.skipLimit(100) // 最多跳过 100 个 Item
.skip(InvalidDataException.class) // 遇到此异常时跳过
.retryLimit(3) // 最多重试 3 次
.retry(DataAccessException.class) // 遇到此异常时重试
.listener(new MySkipListener()) // 添加监听器以记录跳过的数据
.build();
}
八、Spring Batch + Scheduling 定时执行 Job
在你的应用中启用调度,并创建一个定时任务来启动 Job。或者配合XXL-JOB 来执行
@Component
@EnableScheduling // 开启定时任务支持
public class JobScheduler {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job csvToDbJob;
// 每天凌晨 1 点执行
@Scheduled(cron = "0 0 1 * * ?")
public void runCsvToDbJob() throws Exception {
JobParameters params = new JobParametersBuilder()
.addString("JobID", String.valueOf(System.currentTimeMillis()))
.toJobParameters();
jobLauncher.run(csvToDbJob, params);
}
}
九、多线程处理与性能优化
当数据量巨大时,单线程处理会成为瓶颈。Spring Batch 提供了多种并行处理模型。
-
Multi-threaded Step (多线程步骤):在单个 Step 内部,使用多线程来并发处理 Chunk。
ItemReader必须是线程安全的。.taskExecutor(new SimpleAsyncTaskExecutor()) .throttleLimit(10) // 并发线程数 -
Partitioning (分区):最强大、最可扩展的并行模型。它将一个
Step分割成多个“迷你 Step”(worker steps),每个 worker 负责处理一部分数据(一个分区),这些 worker 可以并行执行(甚至可以部署在不同的物理机上)。
- Partitioner: 负责创建数据分区。例如,按 ID 范围 (
1-1000,1001-2000...) 或按地区代码进行分区。 - Worker Steps: 每个分区对应一个
StepExecution,它们共享相同的Step配置,但使用不同的ExecutionContext(其中包含分区信息)。
结语
Spring Batch 远不止是一个工具,它是一种经过千锤百炼的批处理编程思想和最佳实践的集合。将开发者从繁琐的底层技术细节中解放出来,专注于实现核心业务价值。
当你面临海量数据处理需求时,请抵制住从头编写 while 循环和手动管理线程的诱惑。拥抱 Spring Batch,你将获得一个更稳定、更健壮、更易于维护和扩展的企业级解决方案。
Spring Batch 官方文档:https://docs.spring.io/spring-batch/docs/current/reference/html/

5824

被折叠的 条评论
为什么被折叠?



