乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 批处理概述与 Spring Batch 核心理念

    • Spring Batch 概述
    • Job-Instance-Execution 三层生命周期
    • 三层架构
    • Chunk 处理模型
    • Tasklet 处理模型
  • 第2章 Job与作业配置

    • Job 详解
    • Job 配置
    • JobLauncher 详解
    • JobParameters 详解
    • JobRepository 详解
  • 第3章 Step与执行模型

    • Step 详解
    • Step 配置
    • ExecutionContext 详解
    • ItemStream 与状态管理
    • StepScope 与 JobScope
  • 第4章 ItemReader数据读取

    • ItemReader 详解
    • FlatFileItemReader 详解
    • JdbcItemReader 详解
    • MultiResourceItemReader 详解
  • 第5章 ItemProcessor 数据处理

    • ItemProcessor 详解
  • 第6章 ItemWriter 数据写出

    • ItemWriter 详解
    • FlatFileItemWriter 详解
    • CompositeItemWriter 详解
    • JdbcBatchItemWriter 详解
  • 第7章 Chunk 处理与事务边界

    • Chunk 处理模型详解
    • 事务边界
  • 第8章 作业参数与启动

    • 命令行与 Web 启动
  • 第9章 监听器与拦截器

    • 监听器详解
  • 第10章 重启重试与跳过策略

    • 重启与重试
    • 跳过策略
  • 第11章 作业流与条件决策

    • 作业流与条件决策
  • 第12章 分区与并行处理

    • 分区详解
    • 并行 Step 与 Split
  • 第13章 与 Spring Boot 集成实践

    • Spring Boot 集成
  • 第15章 运维与监控

    • 大作业设计模式
    • 运维与监控

ItemStream 与状态管理

本章定位:深入掌握 ItemStream 接口——Reader/Writer 的资源和状态管理器,理解 open/update/close 生命周期和 ExecutionContext 驱动的断点续传机制。

定义与作用

ItemStream 是 Spring Batch 中用于管理资源和持久化状态的接口。实现了 ItemStream 的 Reader/Writer 可以在 Step 执行期间安全地打开/关闭资源,并在每个 Chunk 提交后将进度写入 ExecutionContext。

public interface ItemStream {
    void open(ExecutionContext executionContext) throws ItemStreamException;
    void update(ExecutionContext executionContext) throws ItemStreamException;
    void close() throws ItemStreamException;
}

飞翔科技架构师白歌的比喻:

"ItemStream 是工人手中的签到簿。open() 翻开签到簿看看上次做到哪了,update() 做完一批活记下进度,close() 下班合上签到簿。如果中途停电,明天翻开就知道从哪继续。"

核心原理

三个方法的调用时机

方法调用时机典型操作
open()Step 启动时(首次或重启)打开文件/连接,从 ExecutionContext 恢复进度
update()每个 Chunk 提交后将当前进度写入 ExecutionContext
close()Step 结束时(正常或异常)关闭文件/连接

完整示例

场景一:自定义文件 Reader 实现断点续传

public class ResumableFileReader implements ItemReader<String>, ItemStream {

    private final String filePath;
    private BufferedReader reader;
    private int currentLine = 0;
    private int lastCommittedLine = 0;

    public ResumableFileReader(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void open(ExecutionContext executionContext) {
        // 1. 恢复上次进度的行号
        if (executionContext.containsKey("current.line")) {
            this.currentLine = executionContext.getInt("current.line");
            this.lastCommittedLine = this.currentLine;
            System.out.println("Restart: 从第 " + (currentLine + 1) + " 行继续");
        }

        // 2. 打开文件并跳过已处理的行
        try {
            this.reader = new BufferedReader(new FileReader(filePath));
            for (int i = 0; i < currentLine; i++) {
                reader.readLine(); // 跳过已处理的行
            }
        } catch (IOException e) {
            throw new ItemStreamException("Failed to open file", e);
        }
    }

    @Override
    public String read() throws Exception {
        if (reader == null) return null;
        String line = reader.readLine();
        if (line != null) {
            currentLine++;
        }
        return line;
    }

    @Override
    public void update(ExecutionContext executionContext) {
        // 3. 每个 Chunk 提交后保存当前进度
        lastCommittedLine = currentLine;
        executionContext.putInt("current.line", currentLine);
    }

    @Override
    public void close() {
        try {
            if (reader != null) reader.close();
        } catch (IOException ignored) {}
    }
}

场景二:Step 配置中使用自定义 Reader

@Bean
public Step resumableStep(JobRepository jobRepository,
                            PlatformTransactionManager tx) {
    return new StepBuilder("resumableStep", jobRepository)
            .<String, String>chunk(50, tx)
            .reader(new ResumableFileReader("data/orders.txt"))
            .writer(items -> {
                for (String item : items) {
                    if (item.contains("CRASH_AFTER_THIS")) {
                        throw new RuntimeException("Simulated crash!");
                    }
                    System.out.println("Processed: " + item);
                }
            })
            .build();
}

操作前后对比:

首次执行:
  Chunk 1: 50 行 → COMMIT → ExecutionContext: current.line=50
  Chunk 2: 50 行 → COMMIT → ExecutionContext: current.line=100
  Chunk 3: 读到第 123 行 "CRASH_AFTER_THIS" → write() 抛异常 → ROLLBACK
  JobExecution: FAILED | ExecutionContext 中 current.line=100

重启执行(相同参数):
  open(): 从 ExecutionContext 恢复 current.line=100
  reader 跳过前 100 行,从第 101 行开始
  Chunk 1: 第 101-150 行 → 正常处理 → COMMIT
  ✅ 前 100 行不会重复处理

易错场景与避坑

反例一:忘记在 open() 中恢复状态

// ❌ open() 中忽略 ExecutionContext
@Override
public void open(ExecutionContext executionContext) {
    // 不读取执行上下文 → 重启时丢失进度
    this.reader = new BufferedReader(new FileReader(filePath));
    // 每次重启都从第 0 行开始 → 前面已处理的数据被重复处理!
}

反例二:update() 在 read() 内部而非单独调用

// ❌ 每条 read 都更新 ExecutionContext → 性能灾难
@Override
public String read() {
    String line = reader.readLine();
    if (line != null) {
        currentLine++;
        // ❌ 不要在这里更新!每条数据写一次 DB 太慢
    }
    return line;
}

正确做法:update() 只由框架在每个 Chunk 提交后调用一次。

面试高频考点

Q1:ItemStream 的 update() 和 open() 是如何配合的?

update() 在每个 Chunk COMMIT 后被框架调用,将当前进度写入 ExecutionContext。open() 在 Step 启动时被调用,从中恢复进度。两者之间框架负责将 ExecutionContext 持久化到 BATCH_STEP_EXECUTION_CONTEXT 表。

Q2:如果 Reader 没有实现 ItemStream,重启时会怎样?

框架不会自动管理该 Reader 的状态。重启时 Reader 从头开始读取,可能导致数据重复处理。因此任何需要断点续传的 Reader/Writer 都必须实现 ItemStream。


上一章:ExecutionContext 详解下一章:ItemReader 详解

上一页
ExecutionContext 详解
下一页
StepScope 与 JobScope