개발놀이터

스프링 배치 JobParameter 본문

Spring/Spring Batch

스프링 배치 JobParameter

마늘냄새폴폴 2022. 9. 2. 19:21

JobParameter란?

우선 JobParameter에 대해 알아보자면, 스프링 문서에는 이렇게 나와있습니다..

 

https://docs.spring.io/spring-batch/docs/current/reference/html/domain.html#jobparameters

 

The Domain Language of Batch

This section describes stereotypes relating to the concept of a batch job. A Job is an entity that encapsulates an entire batch process. As is common with other Spring projects, a Job is wired together with either an XML configuration file or Java-based co

docs.spring.io

 

"Having discussed JobInstance and how it differs from Job, the natural question to ask is: "How is one JobInstance distinguished from another?" The answer is: JobParameters. A JobParameters object holds a set of parameters used to start a batch job. They can be used for identification or even as reference data during the run"

 

해석하면 이렇습니다.

 

"Job을 어떻게 구분할까? 이 질문은 즉 이런 의미이다. 어떻게 JobInstance를 다른 JobInstance와 구분할까? 그에 대한 답은 바로 JobParameter이다. JobParameters 객체는 배치 Job을 실행하기 위해 사용되는 파라미터이다. JobParameter는 실행되는 도중에 reference를 식별하기 위해 사용한다."

 

즉 JobParameter는 JobInstance를 구별하기 위한 파라미터라는 것인데요.

 

 

JobInstance를 구별한다는 것은 무슨 의미일까요?

 

JobLauncher의 run메서드 중 일부 발췌

위의 코드는 JobLauncher의 run 메서드중 일부입니다. JobExecution을 선언한 뒤에 jobRepository에서 마지막으로 실행된 JobExecution을 찾아서 lastExecution에 저장하죠. 그리고 null인지 null이 아닌지를 판별해서 각각의 로직을 수행합니다.

 

null이라는 의미는 해당 Job과 JobParameter를 가진 JobExecution이 없다는 의미겠죠. null인 경우에는 새로운 JobExecution을 만들어서 새로 실행한다는 의미입니다. 

 

근데 지금 JobInstance 얘기하고 있는거 아니었나요? 갑자기 JobExecution이 왜나오나요?

 

우리는 이를 이해하기 위해선 JobInstance와 JobExecution에 대해 이해해야 합니다.

 

제 스프링 배치 첫번째 포스팅에서 이에 대해 간단하게 나와있습니다. 

 

두 단어가 비슷해 보이지만 의미는 완전히 다릅니다. Job이 한번 실행 될 때 JobInstance라는 실행 단위 인스턴스가 만들어집니다. JobExecution은 JobInstance에 대한 실행을 나타냅니다. 뭔가 설명이 막걸리스럽죠?

 

오늘 실행한 Job이 있다고 합시다. 위에서 설명한대로 JobInstance가 만들어지겠죠. 그런데 오늘 실행이 되어야 할 JobInstance가 예기치 못한 오류로 정상적으로 처리되지 않고 실패로 끝났습니다. 다음날 같은 시점에 Job이 실행될 때 어제 처리가 실패되었던 JobInstance는 그대로 끝나고 새로운 실행 단위인 JobInstance가 만들어질까요?

 

아닙니다. 한번 실패한 JobInstance는 그 다음날에도 그대로 실행됩니다.

 

그렇다면 그 JobInstance는 결과적으로 2번의 실행을 하게 되는 셈이죠. 그런데 만약 2번째 실행에도 실패했다? 그렇다면 그 다음날인 3번째 실행을 하게 되죠. 이렇게 1개의 JobInstance는 여러번의 실행을 가질 수 있습니다. 그리고 그 여러번의 실행 단위를 JobExecution이라고 하죠.

 

 

뭔가 이해하기 조금 까다로울것으로 예상하는데요.

 

즉 알기쉽게 설명하자면 이런 것입니다. 우리는 JobParameter로 JobInstance를 구별할 수 있다고 했는데요. 그 이야기는 JobParameter가 같으면 같은 JobInstance라는 의미입니다. 

 

여기까진 이해하셨죠?

 

그렇다면 다른 JobParameter인 경우엔 어떨까요? 확실히 이번엔 JobInstance가 다르겠죠? 하지만 JobExecution은 어떨까요? JobExecution은 같은 JobParamter이던 다른 JobParameter이던 상관없이 새로운 객체가 생성됩니다. 

 

그러니까 즉 JobExecution은 실행될 때마다 새로 생성된다는 것입니다.

 

이를 달리 말하면 어떤 의미가 될까요? JobInstance가 JobExecution을 포함하고 있다. 라고 해석할 수도 있는 것입니다.

 

스프링 공식 문서에도 이를 명시하고 있습니다. 

 

Therefore, each JobInstance can have multiple executions ...중략

 

해석하면 다음과 같습니다. "그러므로 JobInstance는 많은 execution을 가질 수 있다."

 

 

이제 JobInstance와 JobExecution에 대해서 이해하셨다면 다시 JobParameter로 넘어오겠습니다.

 

그럼 JobParameter가 다르던 말던 JobExecution은 새로 생성된다는 거잖아요? 그런데 왜 null인지 null이 아닌지를 체크하는것이죠? 

 

이에 대한 대답은 아주 간단합니다.

 

JobExecution의 중복을 확인했을 때 중복인 경우 물을 필요도 없이 JobInstance가 같은 JobInstance이기 때문에 execution 만 확인하면 된다는 것이죠

 

 

이제 알겠어요! 그래도 난 항상 새로운 JobInsatnce를 생성해서 배치를 돌리고싶어요! 라고 하신다면

 

방법이 두가지입니다.

 

1. JobParameter로 시간값을 넣는다.

2. RunIdIncrementer를 사용한다.

 

1. JobParameter로 시간값을 넣는다.

JobLauncher로 Job을 실행할 때 JobParametersBuilder를 이용해서 JobParameter에 현재 시간을 계속 집어넣어 주는것이지요

 

@Service
@RequiredArgsConstructor
@Slf4j
public class ScheduleService {

    private final JobLauncher jobLauncher;
    private final JobExplorer jobExplorer;
    private final Job inactiveMemberJob;


//    @Scheduled(cron = "0/5 0 0 0 0 0 0")
    public void runInactiveMemberScheduler() {
        try {
            jobLauncher.run(
                    inactiveMemberJob, new JobParametersBuilder(jobExplorer)
                            .getNextJobParameters(inactiveMemberJob)
                            .addString("requestDate", LocalDateTime.now().toString())
                            .addString("test", "Test")
                            .toJobParameters()
            );
        } catch(Exception e) {
            log.error(e.getMessage());
            throw new WhyHappenedException(e.getMessage(), e.getCause());
        }
    }
}

JobParameterBuilder에 JobExplorer를 주입받아서 사용하면 됩니다. 이렇게 하고 실행되는 JobParameter를 확인해보면 항상 다른 값이 들어가있는 것을 확인할 수 있습니다.

 

 

 

2. RunIdIncrementer를 사용한다.

우리는 JobBuilderFactory를 이용해서 Job을 만들었습니다. 그리고 그 Job의 속성중에는 incrementer라는 속성이 있습니다. 우리는 이 incrementer에 RunIdIncrementer를 주입해서 사용하면 됩니다. 

 

    @Bean
    public Job inactiveMemberJob() {
        log.info("InactiveMemberJob execution");
        return jobBuilderFactory.get("inactiveMemberJob")
                .start(inactiveJobStep())
                .preventRestart()
                .incrementer(new RunIdIncrementer())
                .build();
    }

 

저도 그냥 간단하게 RunIdIncrementer를 사용하라고 하고 싶지만 RunIdIncrementer에는 버그가 몇개 있는데요

 

우선 스프링 부트 2.1.0 이전에는 배치 Job이 실패할 경우 이후에 파라미터를 변경해도 계속 실패한 파라미터가 사용되는 버그가 있었습니다.

 

하지만 2.1.0 이후에 이 버그는 픽스됐습니다. 하지만 다른 버그가 남아있죠. 

 

바로 이전 Job이 실행시 사용한 파라미터 중 하나가 다음 실행시 누락되면 누락된 파라미터를 재사용한다는 것인데요. 

 

때문에 RunIdIncrementer를 사용하기 보다는 Custom RunIdIncrementer를 사용하는걸 추천합니다. 

 

바로 이렇게 하면 됩니다.

 

@Slf4j
public class UniqueRunIdIncrementer extends RunIdIncrementer {

    private static final String RUN_ID = "run.id";
    private static final Long DEFAULT_VALUE = 0L;
    private static final Long INCREMENT_VALUE = 1L;

    @Override
    public JobParameters getNext(JobParameters parameters) {
        JobParameters params = (parameters == null) ? new JobParameters() : parameters;
        log.info("GetNext");
        return new JobParametersBuilder()
                .addLong(RUN_ID, params.getLong(RUN_ID, DEFAULT_VALUE) + INCREMENT_VALUE)
                .toJobParameters();
    }
}

 

이렇게 커스텀 클래스를 만들어주고

 

    @Bean
    public Job inactiveMemberJob() {
        log.info("InactiveMemberJob execution");
        return jobBuilderFactory.get("inactiveMemberJob")
                .start(inactiveJobStep())
                .preventRestart()
                .incrementer(new UniqueRunIdIncrementer())
                .build();
    }

 

이렇게 설정해주면 끝! 

 

이렇게 할 경우 JobParameter로 run.id라는 값이 실행할때마다 하나씩 올려주기 때문에 다른 JobInstance라는것을 보장받을 수 있죠. 이는 JobParameter로 시간값을 넣는 것과 비슷합니다. 

 

이상으로 JobParameter에 대해 알아봤습니다. 도움이 되셨을지 모르겠네요 다음 포스팅에서는 ItemReader에 대해서 자세히 알아보도록 하겠습니다.

 

 

Reference

https://docs.spring.io/spring-batch/docs/current/reference/html/domain.html#domainLanguageOfBatch

 

The Domain Language of Batch

This section describes stereotypes relating to the concept of a batch job. A Job is an entity that encapsulates an entire batch process. As is common with other Spring projects, a Job is wired together with either an XML configuration file or Java-based co

docs.spring.io