[SYSTEM BUILD] 7. Spring Batch Listener

y001·2026년 2월 3일

Spring Batch Guide

목록 보기
10/19
post-thumbnail

1. Spring Batch Listener

Spring Batch에서 Listener는 배치 처리 흐름의 특정 시점을 감지하고, 그 시점에 필요한 로직을 실행할 수 있도록 제공되는 확장 지점이다. 배치의 핵심 처리 로직을 변경하지 않으면서도, 실행 전후의 상태를 관찰하거나 부가 작업을 자연스럽게 끼워 넣을 수 있도록 설계되어 있다.

배치 작업은 단순히 데이터를 처리하는 것에서 끝나지 않는다. 언제 시작되었는지, 얼마나 처리되었는지, 어디에서 실패했는지, 실패 이후 어떤 조치를 취해야 하는지까지 함께 관리되어야 한다. Listener는 이러한 요구를 처리 로직과 분리된 형태로 구현할 수 있게 해준다.

Spring Batch는 Job, Step, Chunk, Item이라는 실행 단위마다 서로 다른 Listener 인터페이스를 제공한다. 각 Listener는 호출되는 시점과 책임이 명확하게 구분되어 있으며, 이를 적절히 활용하면 배치 실행 흐름을 안정적으로 관찰하고 제어할 수 있다.

2. JobExecutionListener

JobExecutionListener는 Job 실행의 시작과 종료 시점에 호출되는 Listener이다. Job 전체의 생명주기를 기준으로 동작하며, 배치 작업의 가장 바깥 단을 담당한다.

public interface JobExecutionListener {

    default void beforeJob(JobExecution jobExecution) {
    }

    default void afterJob(JobExecution jobExecution) {
    }
}

beforeJob()은 Job이 실제로 실행되기 직전에 호출된다. 이 시점은 아직 Step이 시작되기 전이며, Job 단위의 초기화 작업을 수행하기에 적절하다. 예를 들어 Job 파라미터를 로그로 남기거나, 외부 시스템과의 연계를 위한 사전 준비 작업을 이곳에서 수행할 수 있다.

afterJob()은 Job 실행이 종료된 이후 호출된다. 중요한 점은 Job이 성공했는지 실패했는지와 관계없이 항상 호출된다는 것이다. 이 특성 덕분에 Job의 최종 실행 결과를 기준으로 한 후처리를 안전하게 수행할 수 있다. 실행 결과 로그 기록, 성공·실패 알림 전송, 임시 리소스 정리와 같은 작업이 대표적이다.

JobExecutionListener는 Job 전체를 하나의 단위로 바라보아야 할 때 사용한다. Step 내부의 세부 흐름에는 관여하지 않고, Job의 시작과 끝만을 명확히 관리하는 것이 목적이다.

3. StepExecutionListener

StepExecutionListener는 Step 실행 전후 시점에 호출되는 Listener이다. Job보다 더 안쪽에서 동작하며, 개별 Step의 실행 상태를 관찰하는 역할을 한다.

public interface StepExecutionListener extends StepListener {

    default void beforeStep(StepExecution stepExecution) {
    }

    default ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}

beforeStep()은 Step 실행 직전에 호출된다. 정확히는 StepScope가 활성화된 직후 시점이며, Step 실행에 필요한 상태를 준비하기에 적합한 시점이다. ExecutionContext에 값을 세팅하거나, Step 단위로 필요한 초기화 작업을 수행할 수 있다.

afterStep()은 Step 실행이 종료된 이후 호출된다. 이 역시 Step의 성공 여부와 무관하게 항상 호출된다. Step에서 처리된 데이터 수를 집계하거나, 실패 시 추가 조치를 취하거나, Step 단위 리소스를 정리하는 데 주로 사용된다.

StepExecutionListener는 Step 단위의 실행 결과를 관리하고 싶을 때 사용한다. JobExecutionListener가 Job 전체를 보는 관점이라면, StepExecutionListener는 Job 내부의 각 단계를 명확히 구분하여 관리하기 위한 Listener이다.

4. ChunkListener

Spring Batch의 청크 지향 처리에서는 일정 개수의 아이템을 하나의 단위로 묶어 읽기와 쓰기를 반복한다. ChunkListener는 이 청크 단위 처리의 흐름에 개입하기 위한 Listener로, 청크 처리의 시작 시점, 정상 종료 시점, 그리고 처리 중 오류가 발생한 시점에 각각 호출된다.

public interface ChunkListener extends StepListener {

    default void beforeChunk(ChunkContext context) {
    }

    default void afterChunk(ChunkContext context) {
    }

    default void afterChunkError(ChunkContext context) {
    }
}

beforeChunk()는 하나의 청크 처리가 시작되기 직전에 호출된다. afterChunk()는 청크 처리가 정상적으로 완료된 이후 호출되며, 트랜잭션 커밋이 완료된 시점이다. 반대로 afterChunkError()는 청크 처리 도중 예외가 발생했을 때 호출되며, 트랜잭션 롤백 이후 시점에 실행된다.

이 호출 시점의 차이는 실무에서 중요하다. afterChunk()는 데이터가 실제로 반영된 이후에 실행되므로 처리 완료 기준의 로깅이나 통계 수집에 적합하다. 반면 afterChunkError()는 롤백 이후 호출되기 때문에 실패 상황에 대한 알림 처리나 보정 로직을 수행하는 데 적합하다.

이름과 달리 ChunkListener는 청크 지향 Step뿐만 아니라 Tasklet 지향 Step에서도 동작한다. Tasklet의 execute() 메서드 실행 전후와 실행 중 예외가 발생한 경우에도 동일하게 호출된다.

여기서 StepExecutionListener와의 차이가 분명해진다. Tasklet에서 RepeatStatus.CONTINUABLE을 반환하면 execute()가 반복 실행되는데, 이때 ChunkListenerexecute() 호출마다 실행된다. 반면 StepExecutionListener는 Step의 시작과 종료 시점에만 각각 한 번씩 호출된다.

따라서 Tasklet이 반복 실행되는 흐름을 세밀하게 관찰하거나 실행 단위별 상태를 추적해야 한다면, StepExecutionListener보다 ChunkListener를 사용하는 것이 적절하다.

5. Item 단위 Listener

1) ItemReadListener

public interface ItemReadListener<T> extends StepListener {

    default void beforeRead() {
    }

    default void afterRead(T item) {
    }

    default void onReadError(Exception ex) {
    }
}

afterRead()ItemReader.read() 호출 이후에 실행된다. 단, 더 이상 읽을 데이터가 없어 read()null을 반환하는 경우에는 호출되지 않는다. 이 특성 때문에 실제로 읽힌 데이터에 대해서만 후처리를 수행할 수 있다.

2) ItemProcessListener

public interface ItemProcessListener<T, S> extends StepListener {

    default void beforeProcess(T item) {
    }

    default void afterProcess(T item, S result) {
    }

    default void onProcessError(T item, Exception e) {
    }
}

ItemProcessor.process()null을 반환하더라도 afterProcess()는 호출된다. Spring Batch에서 process 결과가 null이라는 것은 해당 아이템을 필터링하겠다는 의미이므로, 이 시점에서도 흐름을 관찰할 수 있도록 설계되어 있다.

3) ItemWriteListener

public interface ItemWriteListener<S> extends StepListener {

    default void beforeWrite(Chunk<? extends S> items) {
    }

    default void afterWrite(Chunk<? extends S> items) {
    }

    default void onWriteError(Exception exception, Chunk<? extends S> items) {
    }
}

afterWrite()는 트랜잭션 커밋 이전에 호출된다. 이후에 ChunkListener.afterChunk()가 호출된다.

0개의 댓글