Spring Batch中的ExecutionContext:任务执行的“记忆面包” 🍞

271 阅读4分钟

Spring Batch中的ExecutionContext:任务执行的“记忆面包” 🍞

副标题:从状态保存到跨步传递,如何让数据“记住回家的路”?


一、ExecutionContext是谁?——任务执行的“记忆白板”

ExecutionContext是Spring Batch中用于存储任务执行状态的键值对容器,可以理解为任务执行的“草稿纸”。无论是Job还是Step,都可以通过它记录中间数据(如处理进度、临时结果),确保任务崩溃后能“接着干”,重启后能“记得住”。

核心定位

  • 状态保存:记录读取位置、处理计数、临时变量等。
  • 跨步传递:在多个Step之间共享数据(如Step1生成的ID传给Step2)。
  • 断点续传:任务重启时恢复之前的状态。

比喻

  • 就像学生做题时的草稿纸,记录每一步的中间结果,方便后续检查和纠错。

二、ExecutionContext的“两大分身”

Spring Batch中有两种ExecutionContext,作用域不同:

类型作用域存储位置用途
Job ExecutionContext整个Job生命周期BATCH_JOB_EXECUTION表跨Step共享数据(如全局统计信息)
Step ExecutionContext单个Step生命周期BATCH_STEP_EXECUTION表Step内部状态(如文件读取行号、分片参数)

示例

  • Job级别:记录总处理数据量。
  • Step级别:记录当前读取的文件行号。

三、用法——如何让数据“记住回家的路”?

1. 在Step中读写ExecutionContext

通过ChunkContextStepExecution访问:

public class MyProcessor implements ItemProcessor<Data, Data> {  
    @Override  
    public Data process(Data item) {  
        // 获取Step的ExecutionContext  
        StepExecution stepExecution = chunkContext.getStepContext().getStepExecution();  
        ExecutionContext stepContext = stepExecution.getExecutionContext();  
        stepContext.put("processedCount", 100);  
        return item;  
    }  
}  

2. 在Job中读写ExecutionContext

通过JobExecution访问:

public class JobListener implements JobExecutionListener {  
    @Override  
    public void beforeJob(JobExecution jobExecution) {  
        ExecutionContext jobContext = jobExecution.getExecutionContext();  
        jobContext.put("totalUsers", 0);  
    }  

    @Override  
    public void afterJob(JobExecution jobExecution) {  
        int total = jobExecution.getExecutionContext().getInt("totalUsers");  
        log.info("总用户数:{}", total);  
    }  
}  

3. 跨Step传递数据

在Step之间传递数据时,需通过Job级别的ExecutionContext:

// Step1中写入Job级别上下文  
public class Step1Listener implements StepExecutionListener {  
    @Override  
    public ExitStatus afterStep(StepExecution stepExecution) {  
        ExecutionContext jobContext = stepExecution.getJobExecution().getExecutionContext();  
        jobContext.put("key", "value");  
        return ExitStatus.COMPLETED;  
    }  
}  

// Step2中读取数据  
public class Step2Processor implements ItemProcessor<Data, Data> {  
    @Override  
    public Data process(Data item) {  
        String value = chunkContext.getStepContext()  
            .getJobExecutionContext()  
            .getString("key");  
        // 使用value  
        return item;  
    }  
}  

四、原理——ExecutionContext的“记忆魔法”

1. 持久化机制

  • Job ExecutionContext:存储在BATCH_JOB_EXECUTION表的JOB_EXECUTION_CONTEXT字段(序列化的JSON或XML)。
  • Step ExecutionContext:存储在BATCH_STEP_EXECUTION表的STEP_EXECUTION_CONTEXT字段。

2. 序列化与反序列化

  • 默认序列化:Spring Batch使用XStreamJackson将对象转为字符串存储。
  • 自定义序列化:实现ExecutionContextSerializer接口,替换默认实现。

3. 状态恢复流程

  1. 任务重启时,从数据库加载JobExecution和StepExecution。
  2. 反序列化ExecutionContext,恢复所有键值对。
  3. ItemReader/Processor/Writer通过@StepScope@JobScope获取上下文数据。

五、避坑指南——ExecutionContext的“翻车现场”

1. 作用域混淆

  • 问题:在Step中误用Job级别的ExecutionContext,导致数据覆盖或丢失。
  • 解决:明确数据作用域,跨Step共享用Job级别,Step内部用Step级别。

2. 存储大数据对象

  • 坑点:在ExecutionContext中存储大对象(如10MB的List),导致序列化性能差、数据库字段溢出。
  • 忠告:只存必要元数据(如ID、页码),不存完整数据。

3. 线程安全问题

  • 问题:多线程Step中并发修改ExecutionContext,导致数据错乱。
  • 解决:使用线程安全结构(如ConcurrentHashMap),或避免并发写。

4. 序列化兼容性

  • 问题:修改已存储对象的类结构(如删除字段),导致反序列化失败。
  • 解决:保持类版本兼容,或自定义序列化逻辑。

六、最佳实践——老司机的“记忆管理术”

1. 合理存储数据

  • 存什么:进度标记(如已处理行号)、统计值(如计数)、轻量级参数。
  • 不存什么:大集合、数据库连接、文件句柄等资源。

2. 使用监听器管理状态

通过ItemReadListenerItemProcessListener等监听器更新ExecutionContext:

public class CountListener implements ItemReadListener<Data> {  
    @Override  
    public void afterRead(Data item) {  
        ExecutionContext stepContext = chunkContext.getStepContext().getStepExecution().getExecutionContext();  
        stepContext.put("readCount", stepContext.getInt("readCount", 0) + 1);  
    }  
}  

3. 加密敏感信息

若ExecutionContext中存敏感数据(如API密钥),自定义序列化器加密:

public class EncryptedExecutionContextSerializer implements ExecutionContextSerializer {  
    @Override  
    public void serialize(Map<String, Object> context, OutputStream out) {  
        // 加密后写入  
    }  

    @Override  
    public Map<String, Object> deserialize(InputStream in) {  
        // 解密后返回  
    }  
}  

七、面试考点——如何让面试官直呼内行?

1. 问题:ExecutionContext和JobParameters的区别?

答案

  • JobParameters:任务启动参数(如文件路径、日期),只读且唯一标识JobInstance。
  • ExecutionContext:任务执行中的动态状态(如进度、计数),可读写。

2. 问题:如何保证ItemReader重启后恢复读取位置?

答案

  • ItemReader实现ItemStream接口,在open()中从ExecutionContext恢复状态(如行号)。
  • Spring Batch内置Reader(如FlatFileItemReader)已支持该机制。

3. 问题:ExecutionContext的序列化方式有哪些?

答案

  • 默认使用XStreamJackson(取决于依赖)。
  • 可通过ExecutionContextSerializer自定义(如替换为JSON-B或Protobuf)。

八、总结——ExecutionContext的终极奥义

ExecutionContext是Spring Batch的“记忆中枢”,它让任务执行过程变得有状态、可追踪、可恢复。无论是记录进度、传递参数,还是实现断点续传,都离不开这块“记忆面包”。

记住三点

  1. 作用域分明:Job级跨步共享,Step级内部使用。
  2. 轻量存储:只存元数据,不存大对象。
  3. 兼容性保障:确保序列化与类版本兼容。