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
通过ChunkContext或StepExecution访问:
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使用
XStream或Jackson将对象转为字符串存储。 - 自定义序列化:实现
ExecutionContextSerializer接口,替换默认实现。
3. 状态恢复流程
- 任务重启时,从数据库加载JobExecution和StepExecution。
- 反序列化ExecutionContext,恢复所有键值对。
- 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. 使用监听器管理状态
通过ItemReadListener、ItemProcessListener等监听器更新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的序列化方式有哪些?
答案:
- 默认使用
XStream或Jackson(取决于依赖)。 - 可通过
ExecutionContextSerializer自定义(如替换为JSON-B或Protobuf)。
八、总结——ExecutionContext的终极奥义
ExecutionContext是Spring Batch的“记忆中枢”,它让任务执行过程变得有状态、可追踪、可恢复。无论是记录进度、传递参数,还是实现断点续传,都离不开这块“记忆面包”。
记住三点:
- 作用域分明:Job级跨步共享,Step级内部使用。
- 轻量存储:只存元数据,不存大对象。
- 兼容性保障:确保序列化与类版本兼容。