Skip to content

Spring Batch 平面文件处理指南

1. 平面文件概述

1.1 什么是平面文件?

平面文件(Flat File)是一种常见的数据交换格式,广泛应用于批量数据处理场景。与XML不同,平面文件没有统一的标准结构,因此读取前必须明确了解其格式规范

NOTE

平面文件是许多遗留系统和金融系统常用的数据交换格式,掌握其处理技巧对批处理开发至关重要

1.2 平面文件类型对比

类型特点适用场景示例
分隔符文件字段间用特定字符分隔CSV、TSV等通用数据格式John,Doe,30
固定长度文件每个字段占用固定字节长度银行交易记录、主机系统数据John Doe 30

选择建议

  • 分隔符文件:字段长度不固定,数据包含文本内容时
  • 固定长度文件:处理遗留系统数据或需要严格对齐的场景

2. 核心组件解析

2.1 FieldSet - 数据映射桥梁

FieldSet 是Spring Batch用于封装原始文件行数据的核心组件,提供类型安全的数据访问方法:

kotlin
val fieldSet: FieldSet = // 从文件行解析得到

// 按索引获取数据
val firstName = fieldSet.readString(0)  // 第1列
val lastName = fieldSet.readString(1)   // 第2列
val age = fieldSet.readInt(2)           // 第3列

// 按列名获取数据(需配置列名映射)
val email = fieldSet.readString("email")

2.2 FlatFileItemReader - 文件读取器

基础配置示例

kotlin
@Bean
fun customerReader(): FlatFileItemReader<Customer> {
    return FlatFileItemReaderBuilder<Customer>()
        .name("customerReader")
        .resource(FileSystemResource("data/customers.csv"))
        .delimited()  // [!code highlight] // 声明为分隔符文件
        .delimiter(",")  // [!code highlight] // 指定逗号为分隔符
        .names("firstName", "lastName", "email")  // [!code highlight] // 列名映射
        .fieldSetMapper(object : FieldSetMapper<Customer> {
            override fun mapFieldSet(fieldSet: FieldSet): Customer {
                return Customer(
                    firstName = fieldSet.readString("firstName"),
                    lastName = fieldSet.readString("lastName"),
                    email = fieldSet.readString("email")
                )
            }
        })
        .build()
}

处理固定长度文件

kotlin
@Bean
fun fixedLengthReader(): FlatFileItemReader<Transaction> {
    return FlatFileItemReaderBuilder<Transaction>()
        .name("transactionReader")
        .resource(ClassPathResource("data/transactions.dat"))
        .fixedLength()  
        .columns(  // [!code highlight] // 定义列范围
            Range(1, 10),   // 账号
            Range(11, 30),   // 账户名
            Range(31, 42),   // 交易金额
            Range(43, 50)    // 交易日期
        )
        .names("accountNo", "accountName", "amount", "date")
        .fieldSetMapper { fieldSet ->
            Transaction(
                accountNo = fieldSet.readString("accountNo"),
                accountName = fieldSet.readString("accountName"),
                amount = fieldSet.readBigDecimal("amount"),
                date = LocalDate.parse(fieldSet.readString("date"))
            )
        }
        .build()
}

注意事项

  1. 文件编码问题:使用.encoding("UTF-8")显式指定编码
  2. 标题行处理:使用.linesToSkip(1)跳过CSV标题行
  3. 空值处理:使用.includedFields(0,2,4)选择特定列

2.3 FlatFileItemWriter - 文件写入器

基础写入配置

kotlin
@Bean
fun customerWriter(): FlatFileItemWriter<Customer> {
    return FlatFileItemWriterBuilder<Customer>()
        .name("customerWriter")
        .resource(FileSystemResource("output/customers_export.csv"))
        .delimited()  
        .delimiter("|")  // [!code highlight] // 使用管道符分隔
        .names("firstName", "lastName", "email")  
        .headerCallback { writer -> 
            writer.write("FIRST_NAME|LAST_NAME|EMAIL")  // [!code highlight] // 写入标题行
        }
        .append(true)  // 追加模式而非覆盖
        .build()
}

自定义行聚合器

kotlin
@Bean
fun customFormatWriter(): FlatFileItemWriter<Transaction> {
    return FlatFileItemWriterBuilder<Transaction>()
        .name("transactionWriter")
        .resource(FileSystemResource("output/transactions.txt"))
        .formatted()  // [!code highlight] // 格式化写入
        .format("%-10s%-20s%12.2f%8s")  // [!code highlight] // 格式化模板
        .names("accountNo", "accountName", "amount", "date")
        .lineAggregator { transaction ->
            String.format("%-10s%-20s%12.2f%8s",  
                transaction.accountNo,
                transaction.accountName,
                transaction.amount,
                transaction.date.toString()
            )
        }
        .build()
}

3. 高级应用技巧

3.1 多文件处理模式

3.2 分隔符与引号处理

kotlin
.delimited()
.delimiter(";")
.quoteCharacter('"')  // [!code highlight] // 处理带引号的内容
.escapeCharacter('\\') // 转义字符处理

3.3 大文件分块处理

大文件处理策略
kotlin
@Bean
fun chunkOrientedStep(): Step {
    return stepBuilderFactory.get("bigFileStep")
        .chunk<Customer, Customer>(1000)  // [!code highlight] // 每1000条处理一次
        .reader(customerReader())
        .processor(customerProcessor())
        .writer(customerWriter())
        .faultTolerant()
        .skipLimit(100)  // 允许跳过100条错误记录
        .skip(Exception::class.java)
        .build()
}

4. 最佳实践总结

IMPORTANT

平面文件处理黄金法则

  1. 严格验证输入格式:使用BeanWrapperFieldSetMapper自动验证字段类型
  2. 资源清理:实现ItemStream接口确保资源正确关闭
  3. 异常处理:配置skipPolicy处理格式错误记录
  4. 性能优化:对大文件使用chunk处理并设置合适提交间隔

4.1 配置检查清单

kotlin
fun validateReaderConfig() {
    // 确保所有配置项正确
    reader.afterPropertiesSet()  // [!code error] // 忘记调用会导致运行时错误
    
    // 正确方式
    val reader = customerReader()
    reader.afterPropertiesSet()  // [!code highlight] // ✅ 必须调用
}

4.2 注解配置 vs DSL配置

kotlin
@Configuration
class BatchConfig {

    @Bean
    fun reader() = FlatFileItemReaderBuilder<Customer>()
        .name("reader")
        // ... 其他配置
        .build()
}
xml
<!-- 避免使用XML配置 -->
<bean id="reader" class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="file:data/input.csv" />
    <!-- 更多冗长配置 -->
</bean>

平面文件处理是Spring Batch的核心能力之一,掌握这些技巧将使您能够高效处理各种数据交换场景。遇到复杂需求时,记住Spring Batch的扩展性设计允许您通过实现LineMapperLineAggregator接口来自定义解析逻辑!