Appearance
Spring Batch FlatFileItemReader 使用指南
概述:处理二维表格数据
在数据处理中,平面文件(Flat File) 是最常见的数据格式之一。它指任何包含二维表格数据的文件(如CSV、TSV或固定宽度文件)。Spring Batch 框架提供了 FlatFileItemReader
类,专门用于高效读取和解析这类文件。
核心依赖配置
FlatFileItemReader
有两个核心依赖:
- Resource:表示要读取的文件资源
- LineMapper:负责将每行文本转换为对象
Kotlin 配置示例
kotlin
@Bean
fun flatFileItemReader(): FlatFileItemReader<Player> {
val reader = FlatFileItemReader<Player>()
reader.setResource(FileSystemResource("data/players.csv"))
reader.setLinesToSkip(1) // 跳过标题行
val lineMapper = DefaultLineMapper<Player>()
lineMapper.setLineTokenizer(DelimitedLineTokenizer())
lineMapper.setFieldSetMapper(PlayerFieldSetMapper())
reader.setLineMapper(lineMapper)
return reader
}
核心组件详解
1. LineMapper 接口
LineMapper
负责将文本行转换为领域对象:
kotlin
interface LineMapper<T> {
fun mapLine(line: String, lineNumber: Int): T
}
2. LineTokenizer 接口
LineTokenizer
将文本行拆分为字段集合:
kotlin
interface LineTokenizer {
fun tokenize(line: String): FieldSet
}
常用实现类
- DelimitedLineTokenizer:处理分隔符文件(如CSV)
- FixedLengthTokenizer:处理固定宽度文件
- PatternMatchingCompositeLineTokenizer:处理多种记录类型混合文件
3. FieldSetMapper 接口
FieldSetMapper
将字段集合映射到领域对象:
kotlin
interface FieldSetMapper<T> {
fun mapFieldSet(fieldSet: FieldSet): T
}
4. DefaultLineMapper 实现
DefaultLineMapper
组合了 Tokenizer 和 Mapper:
kotlin
class DefaultLineMapper<T> : LineMapper<T>, InitializingBean {
private lateinit var tokenizer: LineTokenizer
private lateinit var fieldSetMapper: FieldSetMapper<T>
override fun mapLine(line: String, lineNumber: Int): T {
return fieldSetMapper.mapFieldSet(tokenizer.tokenize(line))
}
// setter方法省略...
}
实战案例解析
案例1:读取CSV球员数据
数据文件内容 (players.csv):
ID,lastName,firstName,position,birthYear,debutYear
AbduKa00,Abdul-Jabbar,Karim,rb,1974,1996
AbduRa00,Abdullah,Rabih,rb,1975,1999
领域对象定义:
kotlin
data class Player(
val ID: String,
val lastName: String,
val firstName: String,
val position: String,
val birthYear: Int,
val debutYear: Int
)
自定义映射器:
kotlin
class PlayerFieldSetMapper : FieldSetMapper<Player> {
override fun mapFieldSet(fieldSet: FieldSet): Player {
return Player(
ID = fieldSet.readString(0),
lastName = fieldSet.readString(1),
firstName = fieldSet.readString(2),
position = fieldSet.readString(3),
birthYear = fieldSet.readInt(4),
debutYear = fieldSet.readInt(5)
)
}
}
案例2:按字段名映射(推荐)
kotlin
// 配置Tokenizer时指定字段名
tokenizer.setNames("ID", "lastName", "firstName", "position", "birthYear", "debutYear")
// 映射器实现
class PlayerMapper : FieldSetMapper<Player> {
override fun mapFieldSet(fs: FieldSet): Player {
return Player(
ID = fs.readString("ID"),
lastName = fs.readString("lastName"),
firstName = fs.readString("firstName"),
position = fs.readString("position"),
birthYear = fs.readInt("birthYear"),
debutYear = fs.readInt("debutYear")
)
}
}
优势
使用字段名映射使代码更易读且健壮,即使字段顺序变化也不会影响功能
案例3:自动映射(BeanWrapperFieldSetMapper)
kotlin
@Bean
fun fieldSetMapper(): FieldSetMapper<Player> {
val mapper = BeanWrapperFieldSetMapper<Player>()
mapper.setTargetType(Player::class.java)
return mapper
}
// 配置LineMapper
lineMapper.setFieldSetMapper(fieldSetMapper())
IMPORTANT
使用自动映射时,字段名必须与类属性名完全匹配。Spring 会自动进行类型转换(如String转Int)
处理特殊文件格式
固定宽度文件处理
示例文件内容:
UK21341EAH4121131.11customer1
UK21341EAH4221232.11customer2
字段定义:
- ISIN (12字符)
- Quantity (3字符)
- Price (5字符)
- Customer (9字符)
Kotlin 配置:
kotlin
@Bean
fun fixedLengthTokenizer(): FixedLengthTokenizer {
val tokenizer = FixedLengthTokenizer()
tokenizer.setNames("ISIN", "Quantity", "Price", "Customer")
tokenizer.setColumns(
Range(1, 12),
Range(13, 15),
Range(16, 20),
Range(21, 29)
)
return tokenizer
}
处理多记录类型文件
示例混合文件内容:
USER;Smith;Peter;;T;20014539;F
LINEA;1044391041ABC037.49G201XX1383.12H
LINEB;2134776319DEF422.99M005LI
Kotlin 配置:
kotlin
@Bean
fun patternMatchingLineMapper(): PatternMatchingCompositeLineMapper<Any> {
val lineMapper = PatternMatchingCompositeLineMapper<Any>()
// 配置不同行类型的Tokenizer
val tokenizers = mapOf(
"USER*" to userTokenizer(),
"LINEA*" to lineATokenizer(),
"LINEB*" to lineBTokenizer()
)
lineMapper.setTokenizers(tokenizers)
// 配置不同行类型的Mapper
val mappers = mapOf(
"USER*" to userFieldSetMapper(),
"LINE*" to lineFieldSetMapper()
)
lineMapper.setFieldSetMappers(mappers)
return lineMapper
}
CAUTION
模式匹配使用前缀匹配(*表示通配符),更具体的模式应放在前面
异常处理机制
常见异常类型
异常类型 | 触发场景 | 解决方案 |
---|---|---|
IncorrectTokenCountException | 字段数量不匹配 | 检查字段定义与实际数据 |
IncorrectLineLengthException | 行长度不符合预期 | 检查固定宽度配置或启用非严格模式 |
FlatFileParseException | 通用解析错误 | 检查数据格式和编码 |
错误处理示例
kotlin
// 配置非严格模式处理行长度不一致
tokenizer.setStrict(false)
try {
reader.read()
} catch (ex: IncorrectTokenCountException) {
logger.error("字段数量不匹配: 预期=${ex.expectedCount}, 实际=${ex.actualCount}")
// [!code error] // 实际处理逻辑:跳过、记录或终止
} catch (ex: IncorrectLineLengthException) {
logger.error("行长度错误: 预期=${ex.expectedLength}, 实际=${ex.actualLength}")
// [!code warning] // 可能需要调整列配置或清理数据
}
完整异常处理策略示例
kotlin
@Bean
fun reader(): FlatFileItemReader<Player> {
val reader = FlatFileItemReader<Player>()
// 配置跳过行回调
reader.setSkippedLinesCallback { line ->
logger.warn("跳过无效行: $line")
}
// 配置错误监听器
val listener = object : ItemReadListener<Player> {
override fun onReadError(ex: Exception) {
when (ex) {
is FlatFileParseException -> {
logger.error("解析错误 (行 ${ex.lineNumber}): ${ex.input}")
}
// 其他异常处理...
}
}
}
return reader
}
最佳实践总结
- 优先使用字段名映射:提高代码可读性和健壮性
- 处理异常情况:使用严格模式开发,生产环境适当放宽
- 利用BeanWrapper自动映射:简化简单场景配置
- 合理跳过无效行:使用
linesToSkip
和skippedLinesCallback
- 记录详细日志:包含行号和原始内容,便于排查问题
::: success 通过合理配置 FlatFileItemReader
,您可以高效处理各种平面文件格式,构建健壮的批处理作业。结合Spring Batch的其余组件,实现完整的数据处理流水线。 :::