인프런 커뮤니티 질문&답변

dohyun_lim님의 프로필 이미지
dohyun_lim

작성한 질문수

스프링 배치

TaskletStep - 개념 및 API 소개

spring batch 트랜잭션 질문입니다.

작성

·

2.7K

1

아래 설명해주신것처럼 스프링배치에서 외부에서 @Transactional 을 쓰는것을 허용하지 않는다는것을 확인했습니다.

https://brunch.co.kr/@anonymdevoo/50

여기를보고 semaphore.acquire를 하고 외부 트랜잭션이 끝나지 않아서 afterCompletion에서 해당 semaphore.release를 호출하지 못한다는것을 확인하였습니다.

하지만 아래처럼

@RequiredArgsConstructor
@Configuration
public class TaskletStepConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob() {
        return this.jobBuilderFactory.get("batchJob")
                .incrementer(new RunIdIncrementer())
                .start(taskStep())
                .next(chunkStep())
                .build();
    }

    @Bean
    @Transactional
    public Step taskStep() {
        return stepBuilderFactory.get("taskStep")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("step1 has executed");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
    @Bean
    @Transactional
    public Step chunkStep() {
        return stepBuilderFactory.get("chunkStep")
                .<String, String>chunk(3)
                .reader(new ListItemReader(Arrays.asList("item1","item2","item3")))
                .processor(new ItemProcessor<String, String>() {
                    @Override
                    public String process(String item) throws Exception {
                        return item.toUpperCase();
                    }
                })
                .writer(list -> {
                    list.forEach(item -> System.out.println(item));
                })
                .build();
    }

}

@Transactional을 걸어도 잘 동작하는데 제가 놓친 부분이있을까요?

디버깅을 거며 확인했는데도 잘 모르겠습니다..

브랜치는 4.2.4.1입니다.

답변 2

1

정수원님의 프로필 이미지
정수원
지식공유자

외부에서의 @Transactional 이란 의미는 스프링 배치의 Job 자체를 감싸고 있는 트랜잭션을 의미합니다.

예를 들어 아래처럼 배치실행을 외부 트랜잭션이 감싸고 있을 때 예외를 발생하게 됩니다.
@Transactional
public void batchJob(){
jobLauncher.run();
}

질문하신 내용의 코드에서 Step 에 선언한 @Transactional 은 스프링 배치가 실행하게 되면 내부적으로 실행되는 Step 에 선언한 트랜잭션이기 때문에 스프링 배치에서 사용하고 있는 트랜잭션을 그대로 이어받게 됩니다.
즉 Job 의 외부 트랜잭션이 아니라 내부 트랜잭션입니다.
제가 테스트 한 바로는 그렇게 결과가 나오고 있습니다
다만 배치의 다양한 환경설정이나 상황에 따라 트랜잭션의 범위가 원칙을 벗어난 예외를 발생시킬 수 있습니다.
그럴 경우 스프링 배치의 트랜잭션과 사용자가 직접 정의한 트랜잭션의 충돌이 어떤 시점과 원인에 의해서 발생하는지 흐름을 잘 따라가면서 디버깅하면서 분석하면 크게 어려운 부분은 없을 것 같습니다.
트랜잭션은 스프링 배치에 국한 된 것이 아닌 스프링 전반에 걸친 공통적인 영역이기 때문에 스프링에서 제어하는 트랜잭션 코어에 대한 충분한 이해가 더 필요할 수 있습니다.

부연설명하자면 스프링의 트랜잭션은 AOP 에 의한 프록시 실행으로 처리가 이루어지기 때문에 조금 더 로우레벨에서의 처리과정을 학습하시면 더 도움이 된다고 할 수 있습니다.

0

안녕하세요~ 위에 링크 걸어주신 글 저자입니다.

spring-batch에서 트랜잭션이 문제가 되는 부분은 Job 실행 당시에 Job을 감싸하고 있는 트랜잭션입니다.

위에 첨부하신 코드는 Job을 실행하는 코드가 아닙니다. Job과 Step을 빌드하는 코드에 트랜잭션을 붙여주신거라서 케이스가 다릅니다.

```

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MethodInterceptor() {
      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {
         if (TransactionSynchronizationManager.isActualTransactionActive()) {
            throw new IllegalStateException(
                  "Existing transaction detected in JobRepository. "
                        + "Please fix this and try again (e.g. remove @Transactional annotations from client).");

```

spring-batch 코드를 보시면 MethodInterceptor에서 활성 트랜잭션 여부를 체크하는 로직을 구현해 Batch Job실행 시점에 기존에 활성화된 트랜잭션이 있는지 체크하도록 코드가 작성돼있는걸 확인하실 수 있습니다.

dohyun_lim님의 프로필 이미지
dohyun_lim
질문자

아하 완전히 엉뚱한곳에 아무 역할도 못하는 @Transactional을 썼군요.

답변 감사합니다!

dohyun_lim님의 프로필 이미지
dohyun_lim

작성한 질문수

질문하기