[Spring Batch 5] JobParameter 전달 및 유효성 검증

식빵·2024년 10월 12일
0

spring-batch-5

목록 보기
3/4

테스트 소스는 깃헙에 올렸으니 참고하시기 바랍니다.
https://github.com/CodingToastBread/spring-batch-5/tree/job-parameter-study


🍞 JobParameter 전달 방법

spring boot 3 + spring batch 5 기반으로 생성된 runnable jar
실행할 때 spring batch 에서 사용할 파라미터(= job parameter) 를 전달할 수 있습니다.

이러한 파라미터는 Job 의 논리적인 실행 단위인
고유한 JobInstance 생성에 기여를 함으로 매우 중요합니다.

그러니 java -jar batch.jar <파라미터> 처럼 실행할 때,
<파라미터> 를 전달하는 방법을 아는 것도 중요합니다.



1. 파라미터 전달 패턴

Spring Batch 에서 사용하기 위한 파라미터는 전달 시에 특별한 패턴을 지켜야 합니다.

이러한 패턴은 Spring Batch 가 외부에서 전달된 파라미터를 Java 코드에서
사용하기 위한 변환기인 DefaultJobParametersConverterJava Doc 에서 확인 가능합니다. 그 내용은 다음과 같습니다.


  • 파라미터는 key=value,type,identifying 처럼 부여하면 됩니다.
  • value,type,identifying ?
    • value (필수 O) : 사용할 값입니다.
    • type (필수 X) :
      • 값에 대한 java 타입을 지정합니다.
      • 기본은 String 입니다.
    • identifying (필수 X) :
      • JobInstance 의 고유성 판별에 사용되는지 여부를 결정합니다.
        true 면 사용되고, false 면 사용되지 않습니다.
      • 기본은 true (=사용) 입니다.

이해가 잘 안 가면 실제로 사용하는 예시를 통해서 이해를 해봅시다.



2. 파라미터 패턴 사용 예시

간단한 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



3. identifying=false 설정

그런데 앞서 사용한 파라미터 설정에서는 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 테이블에 정상적으로 파라미터 정보가 들어갔습니다.

(다만 이전과 다르게 taskSummaryidentifying 컬럼 값이 N 입니다)



4. 코드에서 생성하고 사용하기

앞서 봤던 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 설정법은 어느정도 감이 잡혔을 거라 생각됩니다.
이제 다음으로 넘어가죠 👏



🍞 JobParameter 조회 방법

파라미터 전달을 했을 때, 실제 우리가 작성한 Spring Batch 코드에서는
이러한 파라미터들을 어떻게 조회할 수 있을까요? 그 방법들을 알아봅시다.


1. @JobScope + @Value

전달한 파라미터를 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();
}

2. @StepScope + @Value

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;
	};
}

3. chunkContext 로 접근

@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가지를 모두 종합해서 사용도 가능합니다!

지금부터 그 방법을 알아보죠.


1. 필수 여부만 체크하기

간단하게 특정 파라미터가 전달됐는지만 체크하려면
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 은 필수 파라미터 체크용으로만 사용하세요!



2. 커스텀 Validator 생성

@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보다 작거나 같으면
예외를 터뜨리도록 했습니다.



3. 여러 Validator 종합 사용

그런데 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;
}
profile
백엔드 개발자로 일하고 있는 식빵(🍞)입니다.

0개의 댓글