Skip to content

Spring Batch FlatFileItemWriter 详解教程

📌 本文将通过生动易懂的方式,全面讲解 Spring Batch 中的 FlatFileItemWriter 组件,帮助初学者掌握文件写入的核心技术

🧩 一、FlatFileItemWriter 概述

1.1 文件写入的挑战

写入平面文件面临的核心问题与文件读取类似:

  • 需要支持分隔符格式固定宽度格式
  • 必须确保事务性写入(在批处理失败时能回滚)
  • 需要处理文件创建、覆盖等文件系统操作

1.2 核心组件关系

🔧 二、核心组件解析

2.1 LineAggregator 接口

LineAggregator 负责将单个对象转换为可写入文件的字符串:

kotlin
interface LineAggregator<T> {
    fun aggregate(item: T): String
}

TIP

LineAggregatorLineTokenizer 的逻辑逆操作:

  • LineTokenizer: 字符串 → FieldSet
  • LineAggregator: 对象 → 字符串

PassThroughLineAggregator

最简单的实现,直接调用对象的 toString() 方法:

kotlin
class PassThroughLineAggregator<T> : LineAggregator<T> {
    override fun aggregate(item: T): String {
        return item.toString()  
    }
}

NOTE

当您需要直接控制字符串生成但仍需事务支持时,此实现非常有用

2.2 FieldExtractor 接口

负责将对象转换为字段数组,供 LineAggregator 使用:

kotlin
interface FieldExtractor<T> {
    fun extract(item: T): Array<Any>
}

BeanWrapperFieldExtractor

最常用的实现,通过反射获取对象属性:

kotlin
val extractor = BeanWrapperFieldExtractor<Name>().apply {
    names = arrayOf("firstName", "lastName", "age")  
}

val name = Name("张", "三", 30)
val values = extractor.extract(name)

// 结果:["张", "三", 30]

IMPORTANT

names 属性顺序决定了字段在输出文件中的排列顺序

🚀 三、文件写入实战

3.1 基础文件写入

最简单的配置示例:

kotlin
@Bean
fun simpleItemWriter(): FlatFileItemWriter<Any> {
    return FlatFileItemWriterBuilder<Any>()
        .name("simpleItemWriter")
        .resource(FileSystemResource("output/data.txt"))
        .lineAggregator(PassThroughLineAggregator())  
        .build()
}

TIP

此配置适合直接写入字符串或简单对象

3.2 分隔符文件写入

写入 CSV 等分隔符格式文件:

kotlin
@Bean
fun csvItemWriter(): FlatFileItemWriter<Customer> {
    val extractor = BeanWrapperFieldExtractor<Customer>().apply {
        names = arrayOf("name", "email", "joinDate")  
    }
    
    return FlatFileItemWriterBuilder<Customer>()
        .name("customerWriter")
        .resource(FileSystemResource("output/customers.csv"))
        .delimited()
        .delimiter(",")  
        .names("name", "email", "joinDate")
        .build()
}
kotlin
@Bean
fun csvItemWriterShort(): FlatFileItemWriter<Customer> {
    return FlatFileItemWriterBuilder<Customer>()
        .name("customerWriter")
        .resource(FileSystemResource("output/customers.csv"))
        .delimited()
        .delimiter("|")  
        .names("id", "name", "creditScore")
        .build()
}

3.3 固定宽度文件写入

适用于银行对账单等需要严格对齐的场景:

kotlin
@Bean
fun fixedWidthWriter(): FlatFileItemWriter<Transaction> {
    val extractor = BeanWrapperFieldExtractor<Transaction>().apply {
        names = arrayOf("account", "amount", "date")
    }
    
    val aggregator = FormatterLineAggregator<Transaction>().apply {
        fieldExtractor = extractor
        format = "%-15s%10.2f%8td/%<tm/%<tY"
    }
    
    return FlatFileItemWriterBuilder<Transaction>()
        .name("transactionWriter")
        .resource(FileSystemResource("output/transactions.txt"))
        .lineAggregator(aggregator)
        .build()
}
kotlin
@Bean
fun fixedWidthWriterShort(): FlatFileItemWriter<Transaction> {
    return FlatFileItemWriterBuilder<Transaction>()
        .name("transactionWriter")
        .resource(FileSystemResource("output/transactions.txt"))
        .formatted()
        .format("%-15s%10.2f%8td/%<tm/%<tY")  
        .names("account", "amount", "date")
        .build()
}

NOTE

格式化字符串说明

  • %-15s:左对齐字符串,宽度15字符
  • %10.2f:10位宽浮点数,保留2位小数
  • %8td/%<tm/%<tY:日期格式(日/月/年)

⚙️ 四、高级配置技巧

4.1 文件创建处理

控制文件创建行为:

kotlin
@Bean
fun smartFileWriter(): FlatFileItemWriter<Any> {
    return FlatFileItemWriterBuilder<Any>()
        .name("smartWriter")
        .resource(FileSystemResource("output/smart.txt"))
        .lineAggregator(PassThroughLineAggregator())
        .append(true)  // [!code highlight] // 追加模式
        .shouldDeleteIfEmpty(true)  // [!code highlight] // 空文件时删除
        .shouldDeleteIfExists(false)  // [!code highlight] // 不覆盖现有文件
        .build()
}

WARNING

重启作业时的文件处理策略

  • 默认:文件存在时追加写入
  • 设置 shouldDeleteIfExists=true:重启时删除并重建文件

4.2 自定义格式转换

实现自定义字段转换逻辑:

kotlin
class StatusFormatter : FieldExtractor<Order> {
    override fun extract(item: Order): Array<Any> {
        return arrayOf(
            item.orderId,
            item.amount,
            when (item.status) {  
                "PENDING" -> "待处理"
                "SHIPPED" -> "已发货"
                else -> "未知状态"
            }
        )
    }
}

@Bean
fun customFormatWriter(): FlatFileItemWriter<Order> {
    return FlatFileItemWriterBuilder<Order>()
        .name("orderWriter")
        .resource(FileSystemResource("output/orders.csv"))
        .delimited()
        .delimiter(",")
        .fieldExtractor(StatusFormatter())  
        .build()
}

🏁 五、总结与最佳实践

组件适用场景推荐实现
LineAggregator对象→字符串转换FormatterLineAggregator
FieldExtractor对象→字段数组BeanWrapperFieldExtractor
文件格式数据特点配置方式
分隔符变长字段.delimited().delimiter(",")
固定宽度严格对齐.formatted().format(...)

IMPORTANT

最佳实践建议

  1. 优先使用 FlatFileItemWriterBuilder 的流畅API
  2. 复杂格式使用 FormatterLineAggregator
  3. 生产环境务必设置 shouldDeleteIfExists 策略
  4. 大文件写入启用缓冲(默认开启)
kotlin
// 完整最佳实践示例
@Bean
fun optimalFileWriter(): FlatFileItemWriter<Customer> {
    return FlatFileItemWriterBuilder<Customer>()
        .name("optimalWriter")
        .resource(FileSystemResource("output/optimal.csv"))
        .delimited()
        .delimiter("|")
        .names("id", "name", "email", "joinDate")
        .headerCallback { writer ->  // [!code highlight] // 写文件头
            writer.write("ID|姓名|邮箱|注册日期")
        }
        .footerCallback { writer ->  // [!code highlight] // 写文件尾
            writer.write("生成时间:${LocalDateTime.now()}")
        }
        .shouldDeleteIfExists(true)
        .transactional(false)  // [!code highlight] // 禁用事务获得更高性能
        .build()
}

通过本教程,您已掌握 Spring Batch 文件写入的核心技术。接下来可以尝试:

  1. 实现多格式文件导出
  2. 结合 Spring Cloud Task 创建文件导出微服务
  3. 添加文件加密功能增强安全性

⚡️ 行动建议:立即在您的项目中添加一个文件导出功能,实践本教程中的技术!