
CSV 파일은 가장 일반적인 파일 형식이며, 구분자를 기준으로 데이터를 분리하는 구조를 가진다. Spring Batch에서는 DelimitedLineTokenizer를 사용하여 CSV 데이터를 처리한다.
예를 들어 다음과 같은 CSV 파일이 있다고 가정한다.
errorId,errorTime,severity,processId,message
ERR001,2024-01-19 10:15:23,CRITICAL,1234,SYSTEM_CRASH
ERR002,2024-01-19 10:15:25,FATAL,1235,MEMORY_OVERFLOW
Spring Batch에서는 다음과 같이 Reader를 구성할 수 있다.
@Bean
public FlatFileItemReader<SystemFailure> reader() {
return new FlatFileItemReaderBuilder<SystemFailure>()
.name("systemFailureReader")
.resource(new ClassPathResource("system_failure.csv"))
.linesToSkip(1)
.delimited()
.delimiter(",")
.names(
"errorId",
"errorTime",
"severity",
"processId",
"message"
)
.targetType(SystemFailure.class)
.build();
}
delimited() 설정을 사용하면 내부적으로 DelimitedLineTokenizer가 적용된다. Tokenizer는 CSV 문자열을 필드 단위로 분리하고 FieldSet을 생성하며, 이후 FieldSetMapper를 통해 도메인 객체로 변환된다.
로그 파일처럼 문자열 구조가 일정하지 않거나 복잡한 패턴을 가지는 경우에는 RegexLineTokenizer를 사용할 수 있다.
예를 들어 다음과 같은 로그 문자열을 처리한다고 가정한다.
[ERROR][Thread-157][CPU:92%] Thread deadlock detected
이 경우 정규식을 이용해 데이터를 분리할 수 있다.
@Bean
public LineTokenizer tokenizer() {
RegexLineTokenizer tokenizer = new RegexLineTokenizer();
tokenizer.setRegex("\\[(.*?)\\]\\[(.*?)\\]\\[(.*?)\\] (.*)");
tokenizer.setNames(
"level",
"thread",
"cpu",
"message"
);
return tokenizer;
}
RegexLineTokenizer는 정규식의 캡처 그룹을 기준으로 문자열을 분리하여 FieldSet을 생성한다. 이를 통해 구조가 일정하지 않은 로그 데이터도 안정적으로 파싱할 수 있다.
하나의 파일 안에 여러 형식의 데이터가 섞여 있는 경우에는 PatternMatchingCompositeLineMapper를 사용할 수 있다.
예를 들어 다음과 같은 로그 파일이 있다고 가정한다.
ERROR,ERR001,2024-01-19,SYSTEM_CRASH
INFO,Service started
WARN,Memory usage high
각 라인의 구조가 다르기 때문에 패턴에 따라 서로 다른 Tokenizer를 적용해야 한다.
PatternMatchingCompositeLineMapper<Object> lineMapper =
new PatternMatchingCompositeLineMapper<>();
Map<String, LineTokenizer> tokenizers = new HashMap<>();
tokenizers.put("ERROR*", errorTokenizer());
tokenizers.put("INFO*", infoTokenizer());
tokenizers.put("WARN*", warnTokenizer());
lineMapper.setTokenizers(tokenizers);
PatternMatchingCompositeLineMapper는 라인의 패턴을 검사한 뒤 해당 패턴에 맞는 Tokenizer를 선택하여 데이터를 처리한다. 이를 통해 하나의 파일 안에서 여러 형식의 데이터를 처리할 수 있다.
실제 배치 환경에서는 하나의 파일이 아니라 여러 개의 파일을 순차적으로 처리해야 하는 경우가 많다. 예를 들어 다음과 같은 파일이 있다고 가정한다.
log-20240101.csv
log-20240102.csv
log-20240103.csv
이 경우 MultiResourceItemReader를 사용하여 여러 파일을 하나의 Reader처럼 처리할 수 있다.
@Bean
public MultiResourceItemReader<SystemFailure> multiReader() {
MultiResourceItemReader<SystemFailure> reader =
new MultiResourceItemReader<>();
reader.setResources(new Resource[] {
new ClassPathResource("log-20240101.csv"),
new ClassPathResource("log-20240102.csv"),
new ClassPathResource("log-20240103.csv")
});
reader.setDelegate(reader());
return reader;
}
MultiResourceItemReader는 여러 파일을 순차적으로 읽으며 내부적으로 지정된 Reader(FlatFileItemReader)를 사용하여 데이터를 처리한다.