Skip to content

Spring Batch FlatFileItemReader 使用指南

概述:处理二维表格数据

在数据处理中,平面文件(Flat File) 是最常见的数据格式之一。它指任何包含二维表格数据的文件(如CSV、TSV或固定宽度文件)。Spring Batch 框架提供了 FlatFileItemReader 类,专门用于高效读取和解析这类文件。

核心依赖配置

FlatFileItemReader 有两个核心依赖:

  1. Resource:表示要读取的文件资源
  2. 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

字段定义:

  1. ISIN (12字符)
  2. Quantity (3字符)
  3. Price (5字符)
  4. 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
}

最佳实践总结

  1. 优先使用字段名映射:提高代码可读性和健壮性
  2. 处理异常情况:使用严格模式开发,生产环境适当放宽
  3. 利用BeanWrapper自动映射:简化简单场景配置
  4. 合理跳过无效行:使用linesToSkipskippedLinesCallback
  5. 记录详细日志:包含行号和原始内容,便于排查问题

::: success 通过合理配置 FlatFileItemReader,您可以高效处理各种平面文件格式,构建健壮的批处理作业。结合Spring Batch的其余组件,实现完整的数据处理流水线。 :::