TIL

FlatFileItemWriter

FlatFileItemWriter

FieldExtractor

public interface FieldExtractor<T> {
    Object[] extract(T item);
}

LineAggregator

public interface LineAggregator<T> {
    String aggregate(T item);
}

구분자 형식의 파일 쓰기

FlatFileItemWriterBuilder

@Bean
@StepScope
fun deathNoteWriter(
    @Value("#{jobParameters['outputDir']}") outputDir: String,
): FlatFileItemWriter<DeathNote> =
    FlatFileItemWriterBuilder<DeathNote>()
        .name("deathNoteWriter")
        .resource(FileSystemResource("$outputDir/death_notes.csv"))
        .delimited()
        .delimiter(",")
        .sourceType(DeathNote::class.java)
        .names("victimId", "victimName", "executionDate", "causeOfDeath")
        .headerCallback { writer: Writer -> writer.write("처형ID,피해자명,처형일자,사인") }
        .build()

FlatFileItemReaderBuilder에선 delimited를 호출하면 DelimitedLineTokenizer가 지정되는 것과 유사

커스텀 FieldExtractor 구성

@Bean
@StepScope
fun deathNoteWriter(
    @Value("#{jobParameters['outputDir']}") outputDir: String,
): FlatFileItemWriter<DeathNote> =
    FlatFileItemWriterBuilder<DeathNote>()
        .name("deathNoteWriter")
        .resource(FileSystemResource("$outputDir/death_notes.csv"))
        .delimited()
        .delimiter(",")
        .fieldExtractor(fieldExtractor())
        .headerCallback { writer: Writer -> writer.write("처형ID,피해자명,처형일자,사인") }
        .build()
        
fun fieldExtractor(): FieldExtractor<DeathNote> =
    RecordFieldExtractor(DeathNote::class.java)
        .apply { setNames("victimId", "executionDate", "causeOfDeath") }

커스텀 포맷 형식으로 파일 쓰기

@Bean
@StepScope
fun deathNoteFormatterLineWriter(
    @Value("#{jobParameters['outputDir']}") outputDir: String?,
): FlatFileItemWriter<DeathNote> =
    FlatFileItemWriterBuilder<DeathNote>()
        .name("deathNoteWriter")
        .resource(FileSystemResource("$outputDir/death_note_report.txt"))
        .formatted()
        .format("처형 ID: %s | 처형일자: %s | 피해자: %s | 사인: %s")
        .sourceType(DeathNote::class.java)
        .names("victimId", "executionDate", "victimName", "causeOfDeath")
        .headerCallback { writer: Writer -> writer!!.write("================= 처형 기록부 =================") }
        .footerCallback { writer: Writer -> writer!!.write("================= 처형 완료 ==================") }
        .build()

파일 처리 옵션

FlatFileItemWriter 롤백 전략

파일 쓰기와 OS 캐시: forceSync

대용량 파일의 분할 처리: MultiResourceItemWriter

@Bean
@StepScope
fun multiResourceItemWriter(
    @Value("#{jobParameters['outputDir']}") outputDir: String?,
): MultiResourceItemWriter<DeathNote?> {
    return MultiResourceItemWriterBuilder<DeathNote?>()
        .name("multiDeathNoteWriter")
        .resource(FileSystemResource("$outputDir/death_note"))
        .itemCountLimitPerResource(10)
        .delegate(delegateItemWriter())
        .resourceSuffixCreator { index: Int -> String.format("_%03d.txt", index) }
        .build()
}