FlatFileItemReader 详解
本章定位:深入掌握 Spring Batch 最常用的文件读取器——FlatFileItemReader,覆盖 CSV、定长字段、自定义 LineTokenizer 和 FieldSetMapper 的完整用法。
定义与作用
FlatFileItemReader 是 Spring Batch 中用于读取平面文件(CSV、定长、TSV 等)的内置 Reader。它从 Resource 中逐行读取,通过 LineMapper 将每行文本转换为领域对象。
飞翔科技后端开发小崔在处理运营部高英的各种 CSV 文件时:
"FlatFileItemReader 就像一个智能流水线:
LineTokenizer把一行文本拆成字段,FieldSetMapper把字段映射成对象。你只需要配置分隔符和字段名,剩下的解析、跳过空行、异常处理都交给框架。"
核心原理
核心组件职责
| 组件 | 接口 | 职责 |
|---|---|---|
Resource | org.springframework.core.io.Resource | 定位文件(文件系统、classpath、URL) |
LineMapper | LineMapper<T> | 将一行文本映射为对象 |
LineTokenizer | LineTokenizer | 将一行文本拆分为 FieldSet |
FieldSetMapper | FieldSetMapper<T> | 将 FieldSet 映射为领域对象 |
DefaultLineMapper 的处理流水线
完整示例
场景一:飞翔科技——CSV 格式学生成绩导入
背景:运营部高英提供了一个 5000 行的学生成绩 CSV。小崔用 FlatFileItemReader 读取。
grades.csv:
student_id,course_name,score
STU001,数据结构,85
STU002,操作系统,92
STU003,计算机网络,78
@Configuration
public class GradeReaderConfig {
@Bean
@StepScope
public FlatFileItemReader<GradeDTO> gradeReader(
@Value("#{jobParameters['input.file']}") String inputFile) {
DefaultLineMapper<GradeDTO> lineMapper = new DefaultLineMapper<>();
// Step 1: Tokenizer——按逗号拆分
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter(",");
tokenizer.setNames("studentId", "courseName", "score");
tokenizer.setStrict(false); // 字段数不匹配时不抛异常
lineMapper.setLineTokenizer(tokenizer);
// Step 2: FieldSetMapper——字段映射为对象
BeanWrapperFieldSetMapper<GradeDTO> fieldSetMapper =
new BeanWrapperFieldSetMapper<>();
fieldSetMapper.setTargetType(GradeDTO.class);
lineMapper.setFieldSetMapper(fieldSetMapper);
return new FlatFileItemReaderBuilder<GradeDTO>()
.name("gradeReader")
.resource(new FileSystemResource(inputFile))
.lineMapper(lineMapper)
.linesToSkip(1) // 跳过标题行
.encoding("UTF-8")
.strict(true)
.build();
}
}
场景二:定长文件读取——银行对账文件
金融行业的定长文件,每列固定宽度:
20260613STU001张三 数据结构 085
20260613STU002李四 操作系统 092
@Bean
public FlatFileItemReader<BankRecord> fixedLengthReader() {
return new FlatFileItemReaderBuilder<BankRecord>()
.name("fixedLengthReader")
.resource(new FileSystemResource("bank_records.txt"))
.fixedLength()
.columns(new Range(1, 8), // 日期
new Range(9, 14), // 学号
new Range(15, 20), // 姓名
new Range(21, 30), // 课程
new Range(31, 33)) // 分数
.names("date", "studentId", "name", "course", "score")
.targetType(BankRecord.class)
.strict(true)
.build();
}
操作前后对比:
| 维度 | 手动 BufferedReader | FlatFileItemReader |
|---|---|---|
| 行解析 | line.split(",") | DelimitedLineTokenizer |
| 字段映射 | 手动 new GradeDTO() + setXxx() | BeanWrapperFieldSetMapper 自动映射 |
| 异常处理 | try-catch 包裹 | 通过 strict 和容错策略声明式处理 |
| 续读 | 手动记录行号 | 框架自动通过 ExecutionContext 管理 |
| 编码 | 手动指定 | encoding("UTF-8") |
| 跳过空行 | 手动判断 | 框架自动跳过 |
易错场景与避坑
反例一:字段名和 DTO 属性名不匹配
// ❌ CSV 文件头: studentId,name,age
// ❌ DTO 属性: id,studentName,age
tokenizer.setNames("studentId", "name", "age");
fieldSetMapper.setTargetType(StudentDTO.class);
// BeanWrapperFieldSetMapper 按名称匹配 → name 字段无法映射!
正确做法:确保 setNames() 中的字段名和 DTO 的属性名完全一致(区分大小写)。
反例二:strict=true + 文件不存在
// ❌ strict=true 但文件路径拼写错误
return new FlatFileItemReaderBuilder<GradeDTO>()
.name("reader")
.resource(new FileSystemResource("wrong_path.csv"))
.strict(true) // 文件不存在 → IllegalStateException
.build();
正确做法:生产环境使用 @StepScope + @Value 从 JobParameters 动态获取文件路径,确保参数校验在 Step 执行前完成。
面试高频考点
Q1:DefaultLineMapper 的工作流程?
mapLine(line, lineNumber)→ LineTokenizer 将字符串拆分为 FieldSet → FieldSetMapper 将 FieldSet 映射为领域对象。两个步骤可独立替换(如改用 PatternMatchingCompositeLineMapper 根据行前缀选择不同的映射策略)。
Q2:如何处理 CSV 中包含分隔符的字段(如 "北京,朝阳区")?
使用
DelimitedLineTokenizer时,框架默认不支持引号转义。如果 CSV 包含逗号的字段用双引号包裹,需要自定义LineTokenizer,或使用 OpenCSV 等第三方库替代默认的 tokenizer。
上一章:ItemReader 详解下一章:JdbcItemReader 详解