arrow_upward
본문 바로가기
Spring

[Spring Batch] JobParameters와 검증(JobParametersValidator)

by dawncode 2024. 7. 21.

 

이번 포스팅에서는 JobParameters와 JobParametersValidator를 사용한 JobParameters를 검증하는 방법을 알아볼 것이다.

 

JobParameters란?

JobParameters는 배치 작업을 하는데 사용되는 파라미터의 집합이다. JobParameters는 JobInsteance를 식별하고 서로 다른 파라미터를 사용해서 배치를 여러 번 실행할 수 있게 한다.

JobInstance를 식별한다는 것은 JobParameters로 동일한 작업을 구분지으며, 중복 실행하는 것을 방지해 준다. 그리고 파라미터를 동적으로 변경해서 처리할 수 있게 해준다.

 

스프링 부트에서 "시스템 변수를 사용해서 파라미터를 주입받을 수는 없을까?"라고 생각할 수 도 있지만, 시스템 변수는 스프링 배치에서 제공하는 메타데이터 스키마의 정보로 저장되지 않고, 애플리케이션 실행 시 고정된 값이기 때문에 외부에서 동적으로 전달받을 수 있는 JobParameters를 사용하는 것이 좋다.

웹 서버에서 API를 사용할 때 동적으로 파라미터를 전달받아 Job을 다르게 실행해야 한다면 JobParameters를 사용하면 쉽게 풀어낼 수 있다.

 

@RestController
@RequiredArgsConstructor
@Slf4j
public class BatchJobController {

    private final JobLauncher jobLauncher;
    private final Job helloJob;

    @GetMapping("/job/sample")
    public ResponseEntity<String> launchHelloJob(@RequestParam("batchDate") String batchDate) {
        try {
            JobParameters jobParameters = new JobParametersBuilder()
                    .addString("batchDate", batchDate)
                    .toJobParameters();

            jobLauncher.run(helloJob, jobParameters);

            return ResponseEntity.ok("HelloJob 배치 성공");
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            return ResponseEntity.internalServerError().body("HelloJob 배치 실패");
        }
    }
}

 

 

JobParamters와 @JobScope, @StepScope

스프링 배치에서 JobParameters는 @JobScope@StepScope가 적용된 위치에서 사용할 수 있다. 

일반적으로 배치는 지정된 시간에 동작하는데, 애플리케이션이 시작될 때 배치 Job이 동작하는데 필요한 Step이나 Chunk, Tasklet이 모두 스프링 Bean으로 등록되어 있을 필요는 없다. 메모리 낭비만 발생한다.

만약 애플리케이션 실행 시점에서 모든 Bean이 생성된다고 할 때 JobParameters에 정보가 없다면 예외가 발생할 수 도 있다.

 

@JobScope와 @StepScope는 그 문제를 해결해준다.

  • @JobScope: Job이 실행되는 동안에만 해당 Bean의 라이프사이클을 관리한다. Job이 시작될 때 Bean을 생성하고, Job이 끝나면 Bean이 소멸된다.
  • @StepScope: Step이 실행되는 동안에만 해당 Bean의 라이프사이클을 관리한다. Step이 시작될 때 Bean을 생성하고, Step이 끝나면 Bean이 소멸된다.

스프링 배치는 해당되는 Job과 Step이 실행될 때 필요한 Bean들만 ExecutionContext 내에서 관리하기 때문에 필요한 객체만 등록해서 사용할 수 있게 해준다. 그리고 Job과 Step 마다 새로운 인스턴스가 생성되므로 상태 관리 및 배치 작업의 일관성을 유지할 수 있다.

 

이러한 스코프 관리는 JobParameters의 Late Binding을 가능하게 해준다.

Late Binding이란 실행 시간에 값을 바인딩하는 것을 의미한다. JobParameters는 배치 작업 시 외부에서 전달되는데, @JobScope와 @StepScope로 인해 Job과 Step에 관련된 Bean을 지연시키면서, Job 실행 시점에 동적으로 파라미터를 바인딩할 수 있다

Late Binding을 적용하면서 배치 작업을 재사용할 수 있게되고, 동일한 배치 작업을 파라미터를 변경하면서 배치에 필요한 값을 동적으로 변경하면서 사용할 수 있다.

 

 

JobParameters로 주입받을 수 있는 타입

프로젝트 설정은 이전 포스팅을 확인하면 된다.

 

스프링 배치에서 JobParameters를 사용하는 방법은 여러가지가 있는데 JobParametersBuilder 사용, 스프링 설정 파일(application.yml) 이용, 커맨드라인 인수 사용 등이 있다.

 

JobParametersBuilder

JobParameters jobParameters = new JobParametersBuilder()
        .addString("batchDate", "2020-01-01")
        .toJobParameters();

 

 

application.yml

batch:
  job:
    params:
      name: hello
      batch-date: 2020-01-01

 

 

커맨드라인 인자 전달

java -jar batch-sample.jar batchDate=2020-01-01

 

 

현재 애플리케이션은 API로 호출해서 사용하진 않을 것이기 때문에 인텔리제이에서 Program Arguments로 받아서 사용할 것이다.

 

스프링 배치에서 JobParameters는Long, Double, String, Date 타입만 사용가능하다. JobParameter.ParameterType num 클래스를 확인하고 convert는 DefaultJobParametersConverter 클래스를 확인해보면 된다.

 

그리고 자료를 찾아보던 중 호돌맨님의 블로그에서 Enum까지 주입 받을 수 있다는 것을 알게 됐다.

 

 

Enum타입이 받아지는지 확인하기 위해 myState 인자를 추가했다.

 

 

문자열 batchDate와 Enum MyState가 출력되는 것으로 Enum 타입도 JobParameters로 받을 수 있음을 확인했다.

 

그런데 생각을 해보면 보통 날짜를 인자로 받을 때 문자열로 받는 일은 거의 없다. JobParameters가 Date타입을 지원한다고는 하지만 요즘에는 거의 LocalDate, LocalDateTime 객체를 사용해서 날짜를 처리하게 된다. 

 

이 문제를 처리하기 위해 JobParameters를 별도의 클래스를 생성해서 처리할 수 있고, Setter로 주입받는 것과 생성자를 사용하는 방법이 있다.

기존의 방식에서 클래스를 분리하여 Setter와 Constructor로 JobParameters를 사용하는 방법을 확인해본다.

 

Setter 주입

 

  1. 스프링 배치에서 자동으로 컨버닝이 가능한 String, Long, Double, Date, Enum 등의 타입은 필드에 @Value 애노테이션에 SpEL을 통해서 설정하면 된다.
  2. LocalDate타입은 스프링 배치에서 자동으로 컨버팅이 되지 않기 때문에 setter 메서드를 통해 문자열을 LocalDate 타입으로 파싱해주면 된다.

 

 

만들어진 클래스를 Bean으로 등록하고 @JobScope를 설정해준다.

 

JobParameters는 이제 생성한 클래스에서 getter를 통해 사용하면 된다. 제대로 값이 나오는지 비교를 위해 String 으로 받아오던 파라미터는 그대로 두었다. 

 

 

로그를 보면 클래스에서 MyState(Enum)과 batchDate(LocalDate)를 getter로 가져온 것이 출력되는 것을 볼 수 있다.

 

 

Constructor 주입

Constructor 주입 방식은 클래스의 생성자로 JobParameters를 주입받아주면 된다.

 

 

Constructor 주입 방식은 생성자에서 JobParameters를 @Value 애노테이션을 통한 SpEL로 전달해주는 방식의 차이밖에 없다. 로그는 같으므로 생략한다.

 

JobParameters 주입 방법은 두 가지 방법을 알아봤는데 사용하면서 편한 방법이나 회사에서 주로 사용하는 관습을 따라가면 될 것 같다.

 

 

JobParametersValidators

이번에는 JobParameters의 검증을 해볼 것이다. 

지금까지 batchDate라는 파라미터를 받아서 사용했었는데, 이 파라미터의 검증을 하지않고 사용했었다. 실제로 주입받은 파라미터의 포맷이 원하는 포맷이 아니면 예외가 발생할 것이다.

 

batchDate 파라미터를 포맷과 맞지 않게 설정하고 애플리케이션을 실행해본다.

 

실행했을 때 DateTimeParseException이 발생한 것을 볼 수 있는데 이제 이 예외처리를 하는 방법을 살펴보자.

 

 

스프링 배치에서는 JobParametersValidator 인터페이스를 구현해서 배치 작업 시 JobParameters가 유효한지 검증할 수 있다.

 

JobParametersValidator의 validate() 메서드를 구현해서 파라미터가 존재하는지, 파라미터가 유효한 형식인지를 검증할 수 있으며, 매개변수가 유효하지 않다면 JobParametersInvalidException 예외를 던지면 된다.

 

public class BatchDateValidator implements JobParametersValidator {

    private final String jobParam = "batchDate";

    @Override
    public void validate(JobParameters jobParameters) throws JobParametersInvalidException {
        String batchDate = jobParameters.getString(jobParam);

        if (!StringUtils.hasText(batchDate)) {
            throw new JobParametersInvalidException("정확한 문자열 형식으로 입력해주세요.");
        }

        try {
            LocalDate.parse(batchDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        } catch (DateTimeParseException e) {
            throw new JobParametersInvalidException("날짜 포맷이 잘못됐습니다. 가능한 포맷은 yyyy-MM-dd 형식입니다.");
        }
    }
}

 

생성한 JobParametersValidator는 JobBuilderFactory로 Job을 생성할 때 메서드 체이닝으로 validator를 지정해줄 수 있다. 

 

애플리케이션을 실행해서 결과를 보면 커스텀으로 구현한 예외 메시지가 제대로 출력됐음을 확인할 수 있다.

 

 

 

여러 개의 JobParameters를 검증하려면 CompositeJobParametersValidator를 사용해서 사용하려고 하는 JobParametersValidator를 등록 후 객체를 validator로 사용하면 된다.

 

MyState를 검증하는 클래스를 구현 후 validator를 등록하는 코드를 CompositeJobParametersValidator를 사용하도록 수정한다.

@Bean
public Job helloJob() {
    return jobBuilderFactory.get("helloJob")
            .incrementer(new RunIdIncrementer()) // JobParameters 고유한 run.id 값 지정
            .validator(customValidator())
            .start(helloStep())
            .build();
}

public JobParametersValidator customValidator() {
    CompositeJobParametersValidator validator = new CompositeJobParametersValidator();
    validator.setValidators(Arrays.asList(
            new BatchDateValidator(),
            new MyStateValidator()
    ));

    return validator;
}

 

등록된 JobParametersValidator가 제대로 동작하는지 애플리케이션을 실행 후 로그를 확인하면 처리한 예외 메시지가 정상적으로 출력되는 것을 확인할 수 있다.

 

 

 

인텔리제이의 Program Arguments를 수정하면서 CompositeJobParametersValidator를 통해 여러 개의 Validator를 등록해서 파라미터 검증이 동작하는지까지 확인했다.