테스트 소스는 깃헙에 올렸으니 참고하시기 바랍니다.
https://github.com/CodingToastBread/spring-batch-5/tree/job-parameter-study
spring boot 3 + spring batch 5
기반으로 생성된 runnable jar
를
실행할 때 spring batch
에서 사용할 파라미터(= job parameter) 를 전달할 수 있습니다.
이러한 파라미터는 Job 의 논리적인 실행 단위인
고유한 JobInstance
생성에 기여를 함으로 매우 중요합니다.
그러니 java -jar batch.jar <파라미터>
처럼 실행할 때,
<파라미터>
를 전달하는 방법을 아는 것도 중요합니다.
Spring Batch
에서 사용하기 위한 파라미터는 전달 시에 특별한 패턴을 지켜야 합니다.
이러한 패턴은 Spring Batch
가 외부에서 전달된 파라미터를 Java
코드에서
사용하기 위한 변환기인 DefaultJobParametersConverter
의 Java Doc
에서 확인 가능합니다. 그 내용은 다음과 같습니다.
key=value,type,identifying
처럼 부여하면 됩니다.value,type,identifying
?value (필수 O)
: 사용할 값입니다.type (필수 X)
: identifying (필수 X)
: 이해가 잘 안 가면 실제로 사용하는 예시를 통해서 이해를 해봅시다.
간단한 java -jar
실행 예시를 보면 더 이해가 되리라 생각합니다.
아래 명령어는 제가 리눅스 환경에서 사용한 파라미터 설정 예시입니다.
java -jar target/batch.jar \
taskVersion=1,java.lang.Long \
taskManagerId=1,java.lang.Integer \
taskName=XmlToDatabase,java.lang.String \
taskSummary="transfer data to database" \
executDate=2024-10-12,java.time.LocalDate \
executeTimeStamp=2011-12-03T10:15:30,java.time.LocalDateTime \
( 위에서 설명했던 패턴과 이 예시를 비교해가면서 스스로 감을 잡아보시기 바랍니다 😁)
실행 후에 batch_job_execution_params
테이블을 살펴보면 아래와 같이
타입과 값이 잘 적용되어서 들어간 것을 확인할 수 있습니다.
참고: 위 예시에는 작성 안했지만 Resource 도 파라미터로 생성 가능합니다!
ex :inputFile=/home/code/1.txt,org.springframework.core.io.FileSystemResource
그런데 앞서 사용한 파라미터 설정에서는 identifying=false
설정을 하나도 안했습니다.
그래서 작성한 파라미터 모두 JobInstance 고유성 판별에 사용됩니다.
그렇기 때문에 위처럼 파라미터 세팅을 똑같이 하고 다시 실행하면 당연히
JobInstanceAlreadyCompleteException
예외가 터지게 됩니다.
주의:
한번 실행할 때마다 고유값을 잡 파라미터에 추가하는
incrementer
같은 게 없는 상태에서 실행해야 이 예외를 볼 수 있습니다!
그런데 위 파라미터들 중에서 하나만 identify=false
설정을 하면 어떻게 될까요?
그러면 기존에 사용되던 identify=true
인 파라미터가 하나 줄어드므로,
앞서 생성된 JobInstance 와 별개의 새로운 JobInstance 를 생성하고 Job 이 실행됩니다.
테스트 해보죠!
저는 파라미터 중에서 taskSummary="transfer data to database"
의 설정을
taskSummary="transfer data to database",java.lang.String,false
로 바꿔서 다시 실행했습니다. 결과적으로 Job 실행이 잘됐고,
batch_job_execution_params
테이블에 정상적으로 파라미터 정보가 들어갔습니다.
(다만 이전과 다르게 taskSummary
에 identifying
컬럼 값이 N
입니다)
앞서 봤던 CLI 환경에서 파라미터를 지정하는 방법 알아봤고, 이번에는
코드에서 파라미터를 지정하고, 이걸 JobLaucher( 또는 JobOperator )에 사용하는
법을 알아봅시다.
package coding.toast.batch.job;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Properties;
@Component
public class SimpleRun implements ApplicationRunner {
private final JobExplorer jobExplorer;
private final JobOperator jobOperator;
private final JobLauncher jobLauncher;
private final Job job;
public SimpleRun(JobExplorer jobExplorer,
JobOperator jobOperator,
JobLauncher jobLauncher,
@Qualifier("simpleJob") Job job) {
this.jobExplorer = jobExplorer;
this.jobOperator = jobOperator;
this.jobLauncher = jobLauncher;
this.job = job;
}
@Override
public void run(ApplicationArguments args) throws Exception {
JobParameters jobParameters =
new JobParametersBuilder(jobExplorer)
.addLong("taskVersion", 1L)
// Class<?> 인자를 통해서 타입을 직접 지정할 수도 있습니다.
.addJobParameter("taskManagerId", 1, Integer.class)
.addString("taskName", "XmlToDatabase")
// 끝에 false 를 준것은 identifying=false 설정입니다.
.addString("taskSummary", "transfer data to database", false)
.addLocalDate("executeDate",
LocalDate.of(2024, 12, 3))
.addLocalDateTime("executeTimeStamp",
LocalDateTime.of(2021, 12, 3, 10, 15, 30))
.toJobParameters();
// 1. jobLauncher 로 실행하기
jobLauncher.run(job, jobParameters);
// 2. jobOperator 로 실행하기
// DefaultJobParametersConverter converter
// = new DefaultJobParametersConverter();
// Properties jobProps = converter.getProperties(jobParameters);
// jobOperator.start("simpleJob", jobProps);
}
}
이쯤하면 JobParameter 설정법은 어느정도 감이 잡혔을 거라 생각됩니다.
이제 다음으로 넘어가죠 👏
파라미터 전달을 했을 때, 실제 우리가 작성한 Spring Batch 코드에서는
이러한 파라미터들을 어떻게 조회할 수 있을까요? 그 방법들을 알아봅시다.
전달한 파라미터를 Step Bean
메소드에서 받아서 사용할 수 있습니다.
@Bean
@JobScope // Step Bean 메소드에서는 @JobScope 사용!
public Step simpleStep(
@Value("#{jobParameters}") Map<String,Object> jobParameters,
@Value("#{jobParameters[taskSummary]}") String summary) {
System.out.printf("test summary : %s%n", summary);
return new StepBuilder("simpleStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
for (Map.Entry<String, Object> entry : jobParameters.entrySet()) {
System.out.println("==== SEE JobParameters [START] ====");
System.out.println(entry)
System.out.println("==== SEE JobParameters [END] ====");
}
return RepeatStatus.FINISHED;
}, transactionManager)
.build();
}
Step 하위 도메인인 Tasklet 에서는 아래처럼 조회도 할 수 있습니다.
@Bean
public Step simpleStep() {
return new StepBuilder("simpleStep", jobRepository)
.tasklet(simpleTasklet(null), transactionManager)
.build();
}
@Bean
@StepScope // 이게 중요합니다!
public Tasklet simpleTasklet(@Value("#{jobParameters['taskSummary']}") String taskSummary) {
return (contribution, chunkContext) -> {
System.out.println("taskSummary = " + taskSummary);
return RepeatStatus.FINISHED;
};
}
@Bean
public Step simpleStep() {
return new StepBuilder("simpleStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
StepContext stepContext = chunkContext.getStepContext();
for (Map.Entry<String, Object> entry : stepContext.getJobParameters().entrySet()) {
System.out.println("entry = " + entry);
}
return RepeatStatus.FINISHED;
}, transactionManager)
.build();
}
입력한 파라미터에 대한 유효성 검증을 위해서는
JobParametersValidator
인터페이스를 사용하면 됩니다.
spring batch 에서 기본으로 제공하는 구현체와 저희만의 커스텀한 검증기를
만들 수도 있습니다. 심지어는 이 2가지를 모두 종합해서 사용도 가능합니다!
지금부터 그 방법을 알아보죠.
간단하게 특정 파라미터가 전달됐는지만 체크하려면
Spring batch
에서 제공하는 DefaultJobParametersValidator
를 사용하면 됩니다.
@Bean
public Job simpleJob() {
return new JobBuilder("simpleJob", jobRepository)
.start(simpleStep())
.validator(jobParametersValidator()) // 적용!
.build();
}
@Bean
public DefaultJobParametersValidator jobParametersValidator() {
return new DefaultJobParametersValidator(
new String[]{"taskVersion"}, // 필수인 파라미터
new String[]{"taskSummary"} // 필수 아닌 파라미터
);
}
다만 위에서 설정한 그외의 파라미터에 대해서는 Warning log 가 아래처럼 찍힙니다.
좀 거슬리지만 실행은 정상적으로 됩니다.![]()
이런 동작 방식 때문에 사실상
필수 아닌 파라미터
설정은 유명무실합니다.
그래서 생성자의 두번째 인자값은new String[]{}
처럼 주는 게 좋습니다.
DefaultJobParametersValidator
은 필수 파라미터 체크용으로만 사용하세요!
@Bean
public Job simpleJob() {
return new JobBuilder("simpleJob", jobRepository)
.start(simpleStep())
.validator(new MyCustomValidator())
.incrementer(new RunIdIncrementer())
.build();
}
public static class MyCustomValidator implements JobParametersValidator {
@Override
public void validate(JobParameters parameters)
throws JobParametersInvalidException {
if (parameters != null) {
Long taskVersion = parameters.getLong("taskVersion");
if (taskVersion == null || taskVersion <= 0) {
throw new JobParametersInvalidException(
"taskVersion must be higher than Zero");
}
}
}
}
저는 예시로 들어오는 taskVersion 파라미터의 값이 0보다 작거나 같으면
예외를 터뜨리도록 했습니다.
그런데 Validator 를 여러개 적용하고 싶을 때는 어쩔까요?
이럴 때는 spring batch 에서 제공하는 CompositeJobParametersValidator
클래스를 사용하면 좋습니다! (아래 코드 참고)
@Bean
public Job simpleJob() {
return new JobBuilder("simpleJob", jobRepository)
.start(simpleStep())
.validator(validator()) // 적용!!!
.incrementer(new RunIdIncrementer())
.build();
}
@Bean
public CompositeJobParametersValidator validator() {
CompositeJobParametersValidator validator = new CompositeJobParametersValidator();
DefaultJobParametersValidator defaultValidator = new DefaultJobParametersValidator(
new String[]{"taskVersion"}, // 필수값인 파라미터
new String[]{} // 필수값이 아닌 파라미터
);
validator.setValidators(
List.of(defaultValidator, new MyCustomValidator())
);
return validator;
}