别再造轮子了:用 Spring Batch 构建企业级批处理作业的终极指南

目录

一、spring Batch 是什么?

二、Spring Batch 与 Spring Boot 的关系

三、Spring Batch 的应用场景

四、Spring Batch vs Quartz:根本不是一回事!

五、核心概念:Job, Step, JobInstance, JobExecution

5.1、架构与执行流程

5.2、Step 详解:Tasklet vs. Chunk

5.3、关键组件的作用

5.4、参数与上下文:在 Job 中传递信息

5.5、错误处理:Retry 与 Skip 策略

5.6、控制流程:Flow, Split, Decision

六、实战:用 Spring Boot 快速构建一个 Batch Job

案例一:从 CSV 文件读取数据并写入数据库

案例二:读取数据库数据并导出为 Excel

案例三:多 Step 串行与并行执行

七、如何处理失败重试与跳过

八、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 的应用场景

  1. ETL (Extract-Transform-Load):最经典的应用场景。从异构数据源(如数据库、CSV 文件、消息队列)中提取(Extract)数据,进行必要的转换(Transform)(如数据清洗、格式化、业务逻辑计算),然后**加载(Load)**到目标系统(如数据仓库、新的数据库表)。

  2. 大数据处理:虽然不如 Spark、Flink 等专为大数据设计的计算引擎强大,但在“读-处理-写”这类面向记录的场景下,Spring Batch 可以作为数据处理管道中的一个重要环节,尤其适合与 Spring Cloud Data Flow 结合,编排复杂的数据流。

  3. 常规批量处理

  • 金融清算:每日收盘后,对海量交易数据进行对账、结算。
  • 报表生成:定期汇总业务数据,生成复杂的统计报表。
  • 数据归档/迁移:将老旧数据从生产库迁移到归档库。
  • 通知服务:批量发送邮件、短信或推送通知。

四、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)

  1. 应用通过 JobLauncher 传入 JobJobParameters 来启动一个作业。

  2. JobLauncherJobRepository 中查询,判断是创建新的 JobInstanceJobExecution,还是基于已有的 JobInstance 进行重启。

  3. Job 对象根据其定义,按顺序(或并行)执行其包含的 Step

  4. Step 执行期间,StepExecution 的状态(如读取了多少条目、提交次数、当前状态等)会被周期性地持久化到 JobRepository。这是实现失败重启的关键。

  5. Step 执行业务逻辑(例如,读-处理-写)。

  6. 所有 Step 执行完毕后,JobExecution 的最终状态被更新到 JobRepository

5.2、Step 详解:Tasklet vs. Chunk

一个 Step 的具体行为由其内部实现决定,主要有两种模式:

  1. Tasklet(任务let)模式:最简单的模式,整个 Step 就是一个单一的任务。它只有一个 execute 方法,该方法会被框架循环调用,直到返回 RepeatStatus.FINISHED

    • 适用场景:执行一个简单的、非面向记录集的任务,例如:调用一个存储过程、执行一条 DDL 语句、清理临时文件等。

  2. 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:如其名,作业启动器。它是批处理作业的入口点,负责接收 JobJobParameters 并启动执行流程。

  • JobRepository:作业仓库,是整个框架的状态持久化核心。它存储了所有 JobExecutionStepExecution 以及相关的上下文信息。默认情况下,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 内部(如 ItemReaderItemWriter 之间)的数据传递。每次 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 库。

  1. ItemReader: 使用 JdbcCursorItemReaderJpaPagingItemReader 来高效地从数据库分页读取数据。

  2. ItemWriter: 创建一个自定义的 ItemWriter,它实现 ItemWriter<T> 接口。

    • open() 方法(通过 ItemStream 接口实现)中初始化 Apache POI 的 WorkbookSheet

    • 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 提供了多种并行处理模型。

  1. Multi-threaded Step (多线程步骤):在单个 Step 内部,使用多线程来并发处理 Chunk。ItemReader 必须是线程安全的。

     
    .taskExecutor(new SimpleAsyncTaskExecutor())
    .throttleLimit(10) // 并发线程数
    
  2. 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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nextera-void

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值