MultiResourceItemReader 详解
本章定位:掌握如何用 MultiResourceItemReader 聚合多个文件,配合通配符和排序策略实现多文件批处理。
定义与作用
MultiResourceItemReader 是一个包装器 Reader,它将多个 Resource(文件)视为一个逻辑数据源,依次委托给内部的 ItemReader 逐文件读取。读完一个文件后自动切换到下一个,对下游完全透明。
飞翔科技运营部高英经常一次发来多个 CSV 文件(orders_20260613_1.csv、orders_20260613_2.csv...),小崔用 MultiResourceItemReader 一行代码解决:
"MultiResourceItemReader 就像一个自动换碟机——你给它一堆唱片(文件),它会一张一张放完,中间不需要你手动切换。"
核心原理
完整示例
场景一:飞翔科技——多分片订单文件聚合处理
背景:运营部杨英从 ERP 系统导出订单数据时,系统自动拆分为多个文件,需要统一处理。
@Bean
@StepScope
public MultiResourceItemReader<Order> multiOrderReader(
@Value("file:/data/orders/orders_*.csv") Resource[] resources) {
// 内部委托的 FlatFileItemReader
FlatFileItemReader<Order> delegate = new FlatFileItemReaderBuilder<Order>()
.name("orderDelegateReader")
.delimited()
.names("orderId", "customerName", "amount", "orderDate")
.targetType(Order.class)
.linesToSkip(1)
.build();
MultiResourceItemReader<Order> reader = new MultiResourceItemReader<>();
reader.setName("multiOrderReader");
reader.setResources(resources); // 多个文件
reader.setDelegate(delegate); // 委托读取器
reader.setStrict(false); // 部分文件不存在不抛异常
reader.setSaveState(true); // 支持断点续传(记录当前文件索引)
return reader;
}
场景二:使用 PathMatchingResourcePatternResolver 动态发现文件
@Bean
public MultiResourceItemReader<Student> dynamicMultiReader() throws IOException {
PathMatchingResourcePatternResolver resolver =
new PathMatchingResourcePatternResolver();
// 自动扫描 /data/students/ 下所有 CSV 文件
Resource[] resources = resolver.getResources(
"file:/data/students/*.csv");
// 按文件名排序,确保处理顺序一致
Arrays.sort(resources, Comparator.comparing(Resource::getFilename));
FlatFileItemReader<Student> delegate = new FlatFileItemReaderBuilder<Student>()
.name("studentDelegate")
.delimited()
.names("studentId", "name", "age")
.targetType(Student.class)
.build();
MultiResourceItemReader<Student> reader = new MultiResourceItemReader<>();
reader.setName("dynamicMultiReader");
reader.setResources(resources);
reader.setDelegate(delegate);
reader.setSaveState(true);
return reader;
}
操作前后对比:
操作前:手动循环文件
String[] files = {"orders_1.csv", "orders_2.csv", "orders_3.csv"};
for (String file : files) {
BufferedReader br = new BufferedReader(new FileReader(file));
// ... 手动读行、处理、跟踪进度
}
崩溃后:无法知道当前处理到哪个文件的哪一行
操作后:MultiResourceItemReader
框架自动按序处理所有文件,saveState 记录 (fileIndex, lineNumber)
崩溃后自动从上次中断的文件和行号恢复
易错场景与避坑
反例一:委托 Reader 不是 prototype 作用域
// ❌ 委托 Reader 是单例 → 第二次使用时状态残留
@Bean
public FlatFileItemReader<Order> delegateReader() {
return new FlatFileItemReaderBuilder<Order>()
.name("reader")
.resource(...) // 单例模式下 resource 固定
.build();
}
@Bean
public MultiResourceItemReader<Order> reader() {
MultiResourceItemReader<Order> mr = new MultiResourceItemReader<>();
mr.setDelegate(delegateReader()); // ❌ 单例 delegate
return mr;
}
正确做法:MultiResourceItemReader 每次切换文件时会调用 delegate.close() 和 delegate.open(executionContext),delegate 无需是 prototype。但需要确保 delegate 支持 ItemStream 接口(FlatFileItemReader 默认实现)。
反例二:忘记设置 saveState=true
// ❌ saveState=false → 崩溃后从头处理所有文件
reader.setSaveState(false);
面试高频考点
Q1:MultiResourceItemReader 如何实现断点续传?
它通过 ExecutionContext 保存两个关键值:
CurrentResource(当前文件索引)和委托 Reader 的ExecutionContext(文件内行号)。重启时恢复这两个值,继续处理。
Q2:和 FlatFileItemReader 直接读大文件相比,MultiResourceItemReader 的优势?
1)支持通配符动态发现文件(新增文件自动纳入);2)按文件名排序保证处理顺序;3)单个文件损坏不影响其他文件;4)并行处理时可以按文件粒度分片。