전문은 line의 맨 처음 시작 문자 (H, D, T 등)에 따라서 라인의 포맷, 필드가 달라진다.

배치에서 파일을 읽어와 맨 처음 시작 문자를 보고, 적절하게 분기해서 lineMapping 해야 하는 상황이었다.

 

기존 코드에서는 FlatFileItemReader를 상속한 TelegramFileItemReader가 있고,

여기서 LineMapper들을 가지고 있으면서, 분기처리해서 적절한 lineMapper를 불러주는 방식으로 처리하고 있었다.

 

헌데 내 생각에는 FlatFileItemReader는 resource에서 data를 읽어오는 책임이지, 읽어온 line 내부에 대해서는 관여하지 않는게 좋아보였다.

말 그대로 FileItemReader니까, FileItemRead만 제대로 하면 OK인 것이고, 상속하면서 override해야 하는 군더더기 코드 때문에 뭔가 fit해 보이지 않았다.

내가 원하는 것은 읽어오는 쪽의 기능 확장이 아니라, mapping하는 쪽의 기능 확장이므로..

 

즉, 읽어온 line의 첫번째 char를 보고 적절한 parsing 로직을 결정하는 것은 LineMapper에서 하는게 더 책임이 명확하지 않을까?

 

class TelegramClassifierLineMapper<T : Any>(
    val headerLineMapper: LineMapper<T>,
    val dataLineMapper: LineMapper<T>,
    val trailerLineMapper: LineMapper<T>
): LineMapper<T> {
    override fun mapLine(line: String, lineNumber: Int): T {
        return when (val recodeType = line.first()) {
            'H' -> headerLineMapper.mapLine(line, lineNumber)
            'D' -> dataLineMapper.mapLine(line, lineNumber)
            'T' -> trailerLineMapper.mapLine(line, lineNumber)
            else -> throw IllegalStateException("Unknown recodeType : $recodeType")
        }
    }
}

 

이런 생각 하에 위와 같이 작성하고 있었는데... 이런 분기처리는 상당히 일반적인 니즈이고, 그렇다면 스프링배치에서 제공하고 있는 클래스가 있지 않을까? 싶었다.

기존에도 LineMapper 구현체는 몇번 보긴 했는데. 찬찬히 다시 살펴보니...

 

PatternMatchingCompositeLineMapper 라는 구현체가 있다!

https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/item/file/mapping/PatternMatchingCompositeLineMapper.html

 

val lineMapper = PatternMatchingCompositeLineMapper<Telegram>().apply {
    setTokenizers(
        mapOf(
            "H*" to headerLineTokenizer,
            "D*" to dataLineTokenizer,
            "T*" to trailerLineTokenizer
        )
    )
    setFieldSetMappers(
        mapOf(
            "H*" to headerFieldSetMapper,
            "D*" to dataFieldSetMapper,
            "T*" to trailerFieldSetMapper
        )
    )
}