트랜잭션은 Step 전체가 아니라 Chunk 단위로 생성/종료된다.
Step 전체가 하나의 트랜잭션인 게 아니고 Step 은 여러 Chunk 트랜잭션의 모음이라고 생각하면 된다.
Chunk 1 (10건) ──[트랜잭션 시작]──> Reader 읽음 → Writer 처리 → [트랜잭션 commit/rollback]──> DB
Chunk 2 (10건) ──[트랜잭션 시작]──> Reader 읽음 → Writer 처리 → [트랜잭션 commit/rollback]──> DB
Spring Batch 에서 chunk(CHUNK_SIZE, transactionManager) 라고 설정하면 하나의 Chunk 는 하나의 트랜잭션을 말한다. Chunk 는 ItemReader, ItemProcessor, ItemWriter 로 구성 되고 더 이상 처리할 데이터가 없을 때까지 이 과정을 반복한다. Reader 가 CHUNK_SIZE 만큼 NotificationEntity 를 읽어와 하나의 트랜잭션으로 묶어서 Writer 에게 전달한다.
예를 들어 CHUNK_SIZE = 10 이면 Reader 가 DB 에서 10건을 읽어오고 Writer 가 하나의 트랜잭션 안에서 10건 전체를 처리하고 그 트랜잭션이 commit 이 되고 DB 에 반영되는 것이다.
// SendNotificationItemWriter 에 전송 후 sent = true 로 업데이트 하는 부분
if (successful) {
notificationEntity.setSent(true);
notificationEntity.setSentAt(LocalDateTime.now());
notificationRepository.save(notificationEntity);
}
JPA save() 는 영속성 컨텍스트에 엔티티를 저장한다. 실제 DB 에 반영이 되는 건 트랜잭션이 commit 될 때이다. 만약 Chunk 처리 중 하나라도 실패하면 Spring Batch 가 트랜잭션을 rollback 하고 이전 상태로 되돌린다.
Reader -> NotificationEntity 리스트(10건)
↓
Writer -> for 문 돌면서 save() 호출 (영속성 컨텍스트에 반영)
↓
Chunk 종료 -> transactionManager.commit()
↓
DB에 최종 반영
Chunk 처리 도중 중간에 10건 중 한건이라도 실패하면 전체 10건이 rollback 된다. 만약 10건 처리 중 5번째에서 예외가 발생했다면 현재 Chunk 의 이 트랜잭션은 rollback 되면서 종료된다. Chunk 도중 한건이라도 실패하면 기본적으로 Job 실패가 되니까 Step 에 SkipPolicy 등 설정이 되어있다면 실패하는 건은 건너뛰고 새로운 Chunk 트랜잭션을 시작해서 다음 Chunk로 넘어갈 수 있다. 이미 1~4번째에 대해 save() 로 컨텍스트에 들어간 엔티티들도 DB 에는 반영되지 않고(commit 전이라서) 이전 상태로 되돌린다.
핵심은 save() = 영속성 컨텍스트에 기록
Chunk 가 종료되어야 commit 이 되고 DB 반영
중간 오류 시 rollback.