FlatFileItemReader와는 반대로 도메인 객체를 문자열로 변환해서 쓴다.FieldExtratorLineAggregatorT를 Object의 배열 형태로 반환한다.public interface FieldExtractor<T> {
Object[] extract(T item);
}
FieldExtractor도 스프링에서 기본 구현체를 제공한다.
BeanWrapperFieldExtractor
getter 메서드로 필드값을 추출RecordFieldExtractor
T 도메인 객체를 받아 문자열로 결합하여 반환한다.public interface LineAggregator<T> {
String aggregate(T item);
}
DelimitedLineAggregatorFormatterLineAggregatorLineAggregator.aggregate() 내부에서 FieldExtractor를 사용하는 구조기에 두 컴포넌트 모두 T 타입 객체를 파라미터로 받는다.
LineAggregator를 구현한 ExtractorLineAggregator 추상 클래스 내부에서 FieldExtractor를 사용한다.DelimitedLineAggregator, FormatterLineAggregator은 ExtractorLineAggregator를 구현한다.FlatFileItemWriter를 구성한다.@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()
name()
FlatFileItemWriter의 고유 이름 지정resource()
WritableResource **타입의 리소스를 지정delimited()
LineAggregator 구현체로 DelimitedLineAggregator가 지정된다.
FlatFileItemReaderBuilder에선delimited를 호출하면DelimitedLineTokenizer가 지정되는 것과 유사
delimiter()
FieldExtractor 구성 방법
FlatFileItemWriterBuilder의 fieldExtractor() 메서드를 사용FieldExtractor를 사용해야할 때 사용FlatFileItemWriterBuilder의 sourceType() 메서드로 전달한 도메인 객체에 따라 자동 구성되는 방식BeanWrapperFieldExtractor, RecordFieldExtractor만 지원한다.sourceType을 호출하지 않아도 자동으로 BeanWrapperFieldExtractor가 선택된다.names()
headerCallback()
FlatFileHeaderCallback이 전달된다.FlatFileItemWriter 초기화 시 호출되어 헤더를 작성한다.footerCallback()도 있다.fieldExtractor를 등록할 수 있다.
fieldExtractor를 등록하면 sourceType과 names는 무시되므로 제거하면 된다.@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") }
FormatterLineAggregator를 사용해야 한다.@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()
formatted()
LineAggregator 구현체로 FormatterLineAggregator를 지정한다.FormatterLineAggregator 동작 방식
String customRecord = String.format("피해자: %s | 처형방식: %s", fields)format()
String.format에서 사용하는 것과 동일한 포맷을 사용FlatFileItemWriterBuilder에서 다음 옵션을 통해 어떻게 파일을 처리할지 조정할 수 있다.
shouldDeleteIfExists
truefalse로 설정하면 FlatFileItemWriter는 예외를 던진다.append
falsetrue로 설정되면 shouldDeleteIfExists가 자동으로 false로 설정되며 기존 파일이 있어도 예외가 발생하지 않는다.shouldDeleteIfEmpty
falsetrue일 때 결과 파일에 헤더와 푸터 외에 데이터가 하나도 쓰여지지 않은 경우 파일을 삭제한다.FlatFileItemWriter는 데이터를 즉시 파일에 쓰지 않고 내부 버퍼에 일시적으로 저장해둔다.
beforeCommit() 콜백 호출 시FlatFileItemWriterBuilder의 transactional() 메서드로 설정 가능하다.
trueFlatFileItemWriterBuilder는 forceSync() 메서드를 제공한다.
falsetrue로 설정하면 데이터를 캐시가 아닌 디스크에 즉시 동기화 시킨다.forceSync는 이 문제를 피하기 위해 설정하는 것MultiResourceItemWriter
ItemWriter 구현체delegateItemWriter())@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()
}
name()
resource()
ItemWriter에 전달할 리소스 지정itemCountLimitPerResource()
delegate()
ItemWriter 지정resourceSuffixCreator
_001.txt, _002.txt