Spring Batch에서는 Step에서 Tasklet(interface)을 실행한다.
Tasklet은 기능을 수행하고, Step에서 Tasklet 앞/뒤로 원하는 작업을 하라고 이런 전략을 취하는 것 같다.
public interface Tasklet {
@Nullable
RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception;
}
Tasklet은 익명 클래스과 ChunkOrientedTasklet이 있다.
프로젝트에서는 ChunkOrientedTasklet을 사용했다.
@Bean
public Step crawlingStep() {
return stepBuilderFactory.get("crawlingStep")
.<Place, Optional<CrawlingResult>>chunk(batchConfiguration.getChunk())
.reader(placeReader())
.processor(instagramBatchProcessor)
.writer(instagramBatchWriter)
.build();
}
요렇게 사용하면 chunkOrientedTasklet이 내부적으로 사용된다는 것이다!
공식 문서를 보면 chunk 단위가 하나의 트랜잭션으로 관리된다고 나와있다.
Chunk oriented processing refers to reading the data one at a time and creating 'chunks' that are written out within a transaction boundary.
즉 reader/processor가 item 당 1번씩 + writer가 chunk에 1번씩 이 전체가 하나의 트랜잭션이라는 것이다.
기존에는 Writer에 Transaction이 붙어있었는데 그렇다면 진짜 Writer에 Transcation이 없어도 의도한대로 동작할까?
// @Transactional 트랜잭션 지우기!
@Override
public void write(List<? extends Optional<CrawlingResult>> items) {
int count = 0;
for (CrawlingResult crawlingResult : items) {
// 저장 로직
count++;
if (i == 1) {
throw new RuntimeException();
}
}
}
트랜잭션을 빼고 item을 1개만 저장한 뒤 예외를 던지도록 만들었다.
/*
chunkSize : 2
item 2개를 저장해둔다.
실행 시작!
*/
assertThat(stepExecution.getWriteCount()).isEqualTo(0);
assertThat(stepExecution.getRollbackCount()).isEqualTo(1);
위 테스트가 통과한다. 즉 @Transaction이 없어도 chunk 단위가 하나의 트랜잭션으로 동작한다.
트랜잭션과 관련해서 잘못 이해한 것이 있었다. 트랜잭션은 그냥 rollback할 때만 사용했다. 하지만 트랜잭션은 논리적인 작업단위이다. 동시성 제어(동시에 row를 기록-갱신/삭제)할 수 없을 뿐 아니라 모순성이 없게 해준다. 예를 들어 내가 x라는 데이터를 select했으면 트랜잭션이 끝날 때까지 x 데이터를 수정/삭제할 수 없게 막아주다. 따라서 Spring Batch에서는 read한 데이터를 가지고 작업을 처리하는 순간에 수정/삭제할 수 없게 보장해주는 것이다.