[Spring Batch] FlatFileItemReader

신지훈·2025년 10월 11일
0

JAVA/Spring

목록 보기
8/8

Spring Batch에서 chunk지향 처리에는 크게 3단계로 ItemReader, ItemWriter, ItemProcessor가 있는데 그중 ItemReader이 한 종류인 FlatFileItemReader에 대해 살펴보자.

들어가기에 앞서 이 블로그는 정확히 어떤 메소드를 통해 객체가 매핑되는지 살펴보는 것이 아니다. 데이터가 어떤 흐름을 거쳐 원하는 객체로 변환되는지 흐름을 살펴보고 그 요소에는 어떤 것들이 있으며 각각의 역할을 파악하는데 목적이 있다.

1. FlatFileItemReader

FlatFileItemReader는 IteamReader의 한 종류로 CSV, TSV파일과 같이 플랫한 파일을 한 줄씩 읽어서 우리가 지정한 도매인 객체로 변환하여 반환하는 클래스이다.

FlatFileItemReader가 어떻게 객체로 반환하는지 살펴보고 우리가 원하는 객체를 어떻게 설정해주는지 확인해보자.

Spring Batch에는 파일을 객체로 변환해주는 LineMapper는 인터페이스가 있다. 인터페이스를 살펴보면 우리가 원하는 문자열과 줄을 받아서 T타입으로 변환하는 인터페이스로 원래라면 우리가 직접 구현해야 한다.

public interface LineMapper<T> {
    T mapLine(String line, int lineNumber) throws Exception;
}

하지만 다행스럽게도 Spring Batch가 DefaultLineMapper라는 기본 구현체를 제공하기에 FlatFileItemReader에서는 이 구현체를 기본으로 사용한다.

그럼 DefaultLineMapper에 대해 살펴보자.

2. DefaultLineMapper

DefaultLineMapper에서 하는 일은 크게 2가지로 먼저 데이터 한줄을 각각의 데이터로 나누는 토큰화, 각각의 데이터를 객체의 필드에 맞게 매핑의 역할을 한다. 이 두 가지 역할은 구현체를 살펴보면 LineTokenizer, FieldSetMapper가 각각의 역할을 맡는다.

public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean {
    private LineTokenizer tokenizer;
    private FieldSetMapper<T> fieldSetMapper;
	...
}

1) LineTokenizer

Spring Batch에는 크게 구분자로 구분된 형식의 라인을 토큰화하는 DelimitedLineTokenizer와 고정 길이로 구분된 데이터를 토큰화하는 FixedLengthTokenizer가 있다.

DelimitedLineTokenizer은 20250101,수,4,지원25,12,3439,승차,7의 데이터를 구분자,를 기준으로 ["20250101","수",4,"지원25",12,3439,"승차",7] 와 같이 구분자로 데이터를 토큰화한다.

FixedLengthTokenizer은 20250101수4지원25123439승차7의 데이터를 8,1,1,4,2,4,2,1자리를 설정해 자리수대로 데이터를 잘라["20250101","수",4,"지원25",12,3439,"승차",7]와 같이 토큰화 하는 방법이다.

2)FieldSetMapper

위에서 토큰화 된 데이터들은 이 FieldSetMapper라는 인터페이스로 넘어온다. 여기의 매핑하는 메소드에는 FieldSet이라는 객체를 필요로 한다.

public interface FieldSetMapper<T> {
    T mapFieldSet(FieldSet fieldSet) throws BindException;
}

이 FieldSet도 인터페이스로 DefaultFieldSet이라는 구현체를 기본으로 사용하여 아래와 같이 대표적으로 2가지의 필드르 살펴보자.

tokens은 LineTokenizer로 토큰화된 데이터가 저장되고 이 데이터들과 매핑할 필드명은 names에 설정할 수 있다.

public class DefaultFieldSet implements FieldSet {
    ...
    private final String[] tokens;
    private List<String> names;

위에서 말했듯이 말했듯이 FieldSetMapper역시 인터페이스로 Spring Batch에서는 BeanWrapperFieldSetMapper가 기본 구현체로 사용된다.

3. 정리 및 예시

1) 정리

정리하면 FlatFileItemReader는 원하는 객체로 매핑을 위해 LineMapper(DefaultLineMapper) 가 필요하다.

LineMapper는 토큰화와 매핑을 위해 LineTokenizer과 FieldSetMapper가 필요하다.

LineTokenizer은 데이터를 나누는 방법에 따라 DelimitedLineTokenizer, FiexdLengthTokenizer가 있다.

FieldSetMapper(BeanWrapperFieldSetMapper)은 토큰화된 데이터 배열과 매핑할 필드명이 저장되어 있는 리스트를 가진 FieldSet(DefaultFieldSet)의 객체를 받아 처리한다.

db필요한 이유, 스키마 자동 생성
data.sql

2) 예시

Builder를 통해 간단한 방법도 살펴보겠지만, 위의 내용을 따라 구현하면 다음과 같이 코드를 나타낼 수 있다.

@Configuration
public class BatchReaderConfig {

    @Bean
    public FlatFileItemReader<BusDataRow> busDataItemReader() {
    	// ItemReader 생성
        LoggingFlatFileItemReader<BusDataRow> reader = new LoggingFlatFileItemReader<>();
        reader.setResource(new ClassPathResource("sample.csv"));
        reader.setLinesToSkip(1); // 맨 윗줄의 컬럼명을 건너뛰기 위해 사용

        // LineTokenizer 생성
        DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
        tokenizer.setNames("date", "day", "time", ...); //필드명 설정
        tokenizer.setDelimiter(","); // 구분자 설정

		// FieldSetMapper 생성
        BeanWrapperFieldSetMapper<BusDataRow> fieldSetMapper = new BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setTargetType(BusDataRow.class); // 매핑할 객체 설정


		// LineMapper 생성 및 LineTokenizer, FieldSetMapper 설정
        DefaultLineMapper<BusDataRow> lineMapper = new DefaultLineMapper<>();
        lineMapper.setLineTokenizer(tokenizer);
        lineMapper.setFieldSetMapper(fieldSetMapper);

		// ItemReader에 LineMapper설정		
        reader.setLineMapper(lineMapper);
        return reader;
    }
}

위의 다음과 같이 builder를 통해 좀 더 간결히 나타낼 수 있다. 하지만 위의 코드를 보고 이해한다면 ItemReader를 사용하는데 좀 더 편하지 않을까 생각된다.

@Configuration
public class ItemReaderConfig {

    @Bean
    @StepScope
    public FlatFileItemReader<BusDataRow> busDataItemReader() {
        return new FlatFileItemReaderBuilder<BusWeatherData>()
                .name("BusDataItemReader")
                .resource(new ClassPathResource("busWeatherData.csv"))
                .encoding("UTF-8")
                .delimited()
                .names("date", "day", "time", ...)
                .targetType(BusDataRow.class)
                .linesToSkip(1)
                .strict(true)
                .build();
    }
profile
주주주주니어 개발자

0개의 댓글