본문 바로가기

Backend/Batch

[Batch] 4. Spring Batch- Reader / Processor / Writer

이번 글에서는 Spring Batch의 핵심 구성요소인 Reader / Processor / Writer를 “실무 기준”으로 이해할 수 있도록 정리합니다.

Chunk 기반 Step에서 가장 중요한 부분이 바로 이 3요소이며, 구조를 어떻게 잡느냐에 따라 성능, 유지보수성, 장애 대응까지 큰 차이가 납니다.


✔ Reader / Processor / Writer는 왜 중요한가?

Spring Batch에서 Chunk 기반 Step은 아래 순서로 반복 실행됩니다.

Reader → Processor → Writer → (반복)

즉, Reader가 데이터를 어떻게 읽고, Processor가 무엇을 가공하며, Writer가 어디로 저장하느냐가 전체 배치 품질을 결정합니다.

  • Reader: 어디에서 데이터를 읽을 것인가?
  • Processor: 읽어온 데이터를 어떻게 변환할 것인가?
  • Writer: 변환된 데이터를 어디로 보낼 것인가?

이제 실무 사례 기반으로 하나씩 살펴보겠습니다.


1. ItemReader — 데이터를 읽는 방식 선택하기

Reader는 “무엇을 읽을지”에 따라 전략이 달라집니다.

✔ 1) JpaPagingItemReader (페이징 기반)

가장 많이 사용하는 Reader. 대량 데이터 읽기에 적합하며, 페이징 방식으로 안정적으로 처리합니다.


@Bean
public JpaPagingItemReader<MailEntity> waitingMailReader() {
    return new JpaPagingItemReaderBuilder<MailEntity>()
            .name("waitingMailReader")
            .entityManagerFactory(entityManagerFactory)
            .queryString("SELECT m FROM MailEntity m WHERE m.status = 'WAIT'")
            .pageSize(100)
            .build();
}

실무 장점

  • 대량 데이터 읽을 때 안전함
  • DB 부하가 적음
  • Chunk 크기와 페이징 크기가 잘 맞음

✔ 2) JdbcCursorItemReader (커서 기반)

데이터량이 중간 수준일 때 더 빠르게 읽고 싶을 때 좋습니다.


@Bean
public JdbcCursorItemReader<User> userCursorReader() {
    return new JdbcCursorItemReaderBuilder<User>()
            .name("userCursorReader")
            .dataSource(dataSource)
            .sql("SELECT id, name, email FROM users")
            .rowMapper(new UserRowMapper())
            .build();
}

단점: 대량 데이터에는 커서 방식이 불안정할 수 있음 (DB 부하↑)


✔ 3) FlatFileItemReader (CSV/파일)

외부에서 전달된 파일을 읽어들일 때 사용.


@Bean
public FlatFileItemReader<CsvUser> csvReader() {
    return new FlatFileItemReaderBuilder<CsvUser>()
            .name("csvReader")
            .resource(new ClassPathResource("users.csv"))
            .delimited()
            .names("id", "name", "email")
            .targetType(CsvUser.class)
            .build();
}

2. ItemProcessor — 데이터를 변환하는 단계

Processor는 말 그대로 데이터 가공 로직을 넣는 위치입니다. DB → 모델 매핑, 단순 계산, 문자열 변환, 유효성 체크 등 로직을 넣을 수 있습니다.

✔ 기본 Processor 예시


@Bean
public ItemProcessor<MailEntity, MailMessage> mailProcessor() {
    return mailEntity -> {
        MailMessage msg = new MailMessage();
        msg.setTo(mailEntity.getEmail());
        msg.setSubject(mailEntity.getSubject());
        msg.setBody(mailEntity.getContent());
        return msg;
    };
}

✔ Processor 실무 활용 패턴

  • null 반환 → Writer에서 제외됨 (필터링 기능)
  • 외부 API 결과를 조합해 변환
  • 암호화/복호화 처리
  • 유효성 체크 후 불량 데이터 로그 저장

실무 팁: Processor에 비즈니스 로직을 몰아넣지 말고, 서비스/헬퍼 클래스로 분리하는 것이 유지보수에 좋음


3. ItemWriter — 데이터를 저장하거나 외부로 보내는 단계

Writer는 “최종 목적지” 역할을 합니다. DB 저장뿐 아니라 Kafka, S3, 외부 API 호출 등을 Writer에서 처리할 수 있습니다.

✔ 1) JpaItemWriter — DB 저장


@Bean
public JpaItemWriter<UserHistory> userHistoryWriter() {
    return new JpaItemWriterBuilder<UserHistory>()
            .entityManagerFactory(entityManagerFactory)
            .build();
}

✔ 2) Kafka Writer — 메시지 큐로 데이터 전송

Kakao/메일 알림 발송 자동화할 때 사용했던 방식.


@Bean
public ItemWriter<MailMessage> kafkaMailWriter() {
    return items -> items.forEach(kafkaProducer::send);
}

Writer는 대량으로 메시지를 쏘는 경우도 많기 때문에 “Chunk 단위로 묶어서 처리”하는 게 매우 중요합니다.


✔ 3) Custom Writer — 외부 API 전송


@Bean
public ItemWriter<OrderResult> apiWriter() {
    return items -> {
        for (OrderResult item : items) {
            externalApi.sendOrder(item);
        }
    };
}

Writer는 Chunk(예: 100건) 단위로 실행되므로 실패 시 해당 Chunk만 재시도 가능 → 안정적


4. 실무 배치 구조 예시


@Bean
public Step sendMailStep() {
    return stepBuilderFactory.get("sendMailStep")
            .<MailEntity, MailMessage>chunk(100)
            .reader(waitingMailReader())
            .processor(mailProcessor())
            .writer(kafkaMailWriter())
            .build();
}
  • Reader: 발송 대기 메일 조회
  • Processor: 템플릿 변환 + 데이터 매핑
  • Writer: Kafka 발송

이 구조는 실무에서 가장 많이 사용되는 형태입니다.


5. 최종 정리 — 실무 기준 선택 가이드

  • Reader: 데이터 소스(DB / 파일 / API)에 따라 선택
  • Processor: 가공, 필터링, 유효성 체크
  • Writer: 저장 또는 외부 발송

특히 “대량 처리”를 다루는 기업에서는 Reader 성능 + Writer 안정성이 가장 중요한 포인트임.