[spring-batch] job parameter와 관련된 수난기
환경: 자바 17, springboot3.1.5, springCloud 2022.0.4
목표: 배치가 하루에 한 번 돌아야 하고 (성공했어도) 종종 수동으로 한번 더 돌릴 수 있어야 함.
trial1: program argument로 date를 넘겨 중복 실행을 막아보자
step1. job parameter를 program argument로 넘겨야 한다.
시도한 방법
java -jar aaa.jar --job.name=sampleBatchJob dateParam=2022-09-09
에러 발생
Caused by: org.springframework.batch.core.converter.JobParametersConversionException: Unable to decode job parameter 2024-08-22
...
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)): Expected space separating root-level values
at [Source: (String)"2024-08-22"; line: 1, column: 6]
...
그 어떤 글을 봐도 job param을 넘길 때 그냥 넘기길래.. 계속 저 방식으로 시도했지만.. 실패가 났다..
'-'가 문제 되나 싶어 지우고 해 봐도, 숫자가 아닌 임의의 문자열을 줘도 비슷한 에러가 나길래, 타입의 문제는 아닌 것 같았다.
step2. 혹시 springboot2 와 3의 차이로 인해 발생?
구글링 한 자료들이 outdated 된 것일 수 있다고 판단하였다.
그 이유는 springboot3 로 오면서 크게 변한 것 중 하나가 javax -> jakarta로 패키지명이 변한 것인데
사실 그 때는 jackson이라고 착각했다. 어쨌건 라이브러리 변화가 있어서 추가 설정이나 파라미터 넘기는 방식이 변했을지도 모르겠다고 생각했다.
그러다 구글링하다 파라미터에 type을 주는 예시를 봤는데, 아래와 같이 시도해 보았지만 역시나 같은 에러가 발생하였다.
dateParam(String)=2024-08-22
더 파고들어보니 위 방식은 fade out 되었고 boot3으로 버전이 오르면서 아래와 같이 바뀌었다는 글을 보게 된다.
parameter=value,type,identifying
그래서 아래와 같이 시도해 보았지만 여전히 실패하였다.
dateParam=2024-08-22,String,true
step3. 파고들기
관련 글을 좀 더 보다 보니 위와 같은 형태로 파라미터를 전달하려면 아래의 잡 파라미터 컨버터를 사용해야 한다고 한다. 이름 그대로 설정이 없을 경우 "기본적"으로 사용하는 컨버터이다.
DefaultJobParametersConverter
해당 프로젝트의 컨버터 설정이 뭔지 찾아보니 맙소사.. 다른 것이었다.
@Bean
public JobParametersConverter jobParametersConverter() {
return new JsonJobParametersConverter();
}
해당 컨버터를 사용할 경우 잡 파라미터를 아래와 같은 형태로 넘겨야 한다고 한다.
parameterName='{"value": "parameterValue", "type":"parameterType", "identifying": "booleanValue"}'
그래서 비슷하게 만들고 실행해 본다.
--job.name=sampleBatchJob
dateParam='{"value":"2024-08-22","type":"java.lang.String","identifying":"true"}'
아래의 에러가 발생한다.
Caused by: org.springframework.batch.core.converter.JobParametersConversionException: Unable to decode job parameter '{value:2024-08-22,type:java.lang.String,identifying:true}'
...
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character (''' (code 39)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
...
json parsing 에러다. single quotaion, double quotation 문제인가 싶어서 여러 조합으로 수정해 봤는데도 비슷한 에러만 발생한다.
그러던 중 한 글을 보게 되는데, json 안의 quote에는 escape 처리를 해주어야 한다는 것! (https://github.com/spring-projects/spring-batch/issues/4299)
그래서 아래처럼 수정했더니 드디어 돌아간다!
--spring.profiles.active=local
--job.name=sampleBatchJob
dateParam="{\"value\":\"2024-08-22\",\"type\":\"java.lang.String\",\"identifying\":\"true\"}
복수개의 파라미터를 넘기게 된다면 아래와 같다. 필요없는 콤마, 따옴표.. 등등이 들어가면 뜬금없는 에러가 나며 인식이 되지 않는다(에러 상황을 알기 어려움).
--spring.profiles.active=local
--job.name=DailyRankingJob
date="{\"value\":\"2024-08-21\",\"type\":\"java.lang.String\",\"identifying\":\"true\"}"
version="{\"value\":\"1\",\"type\":\"java.lang.Integer\",\"identifying\":\"true\"}"
json job converter 예시: https://spring.io/blog/2022/11/24/spring-batch-5-0-goes-ga
배치 최신 문서: https://docs.spring.io/spring-batch/reference/job/running.html
trial2. 날짜는 넘겼는데, 잡에서 사용하게 해야 하네!
job parameter를 프로젝트에서 보이게 하려면 우선 빈으로 등록되어 있어야 한다.
아래와 같이 일반적인 string으로 받을 경우 아래의 에러를 만난다.
@Value("#{jobParameters[dateParam]}")
public String dateParam;
Caused by: org.springframework.expression.spel.SpelEvaluationException:
EL1008E: Property or field 'jobParameters' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?
빈만 등록되면 안 되고 scope에서 보이게끔 선언해야 한다.
Job 안에서 보이게 하려면 JobScope, Step 안에서 보이려면 StepScope 안에서 사용하게 끔 아래와 같이 빈 선언부에 등록한다.
@Bean(STEP_NAME)
@JobScope
public Step rankingStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
MyBatisCursorItemReader<Ranking> dailyRankingReader,
ItemWriter<Ranking> dailyRankingWriter,
@Value("#{jobParameters[dateParam]}") String dataParam) {
...
}
이때 Job Parameter의 타입으로 사용할 수 있는 것으로는 Double, Long, Date, String이 있다.(배치4 기준)
LocalDate나 LocalDateTime같은 타입은 String으로 받아서 타입 변환을 해야 한다. 반환하는 방법은 크게 세 가지가 있다(https://jojoldu.tistory.com/490). 여기서는 setter주입 방식으로 해본다.
@Getter
@NoArgsConstructor
@Component
@JobScope
public class DailyRankingJobParameter {
private LocalDate date;
@Value("#{jobParameters[dateParam]}")
public void setDate(String date) {
this.date = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
빈으로 등록되어야 JobScope이 먹기 때문에 굳이? 싶어도 Component 등록을 해줘야 한다.
사용하고자 하는 Job이나 Step에서는 argument로 전달할 필요 없이 클래스에서 생성자로 받아서 바로 사용하면 된다.
@Configuration
@RequiredArgsConstructor
public class DailyRankingJobConfig {
private final DailyRankingJobParameter jobParameter;
...
@Bean(STEP_NAME)
@JobScope
public Step sinyutnoriDailyRankingStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
MyBatisCursorItemReader<SinyutnoriRanking> dailyRankingReader,
ItemWriter<SinyutnoriRanking> dailyRankingWriter
// @Value("#{jobParameters[dateParam]}") String dataParam
) {
System.out.println(jobParameter);
return new StepBuilder(STEP_NAME, jobRepository)
.<SinyutnoriRanking, SinyutnoriRanking>chunk(CHUNK_SIZE, transactionManager)
.reader(dailyRankingReader)
.writer(dailyRankingWriter)
.build();
}
위 내용은 fade out된 내용이고(물론 위처럼 해도 작동은 됨) 실제로는 job parameter class를 만들 필요도 없이! argument에 아래와 같이 전달하면 된다.
--job.name=DailyRankingJob
date="{\"value\":\"2024-08-21\",\"type\":\"java.time.LocalDate\",\"identifying\":\"true\"}"
version="{\"value\":\"2\",\"type\":\"java.lang.Integer\",\"identifying\":\"true\"}"
사용하려는 job/step에서 바로 땡겨다 사용 가능. 클래스에 선언하면 scope이 정의되지 않아 에러가 난다.
@Bean(STEP1_NAME)
@JobScope
public Step DailyRankingMatchCntStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
@Value("#{jobParameters[date]}") LocalDate date) {
In Spring Batch 5, when job parameters are passed as strings, Spring Batch will automatically infer the correct type (
String, Long, Double, or Date) based on the format of the input.
trial3. 날짜는 같은데도 반복 실행이 된다?
위에서 날짜를 받아서 job parameter로 넘기는 것을 해봤다. 근데도 여전히 반복 실행이 된다. 왜 그런가 싶어 job execution에 사용된 파라미터를 확인해 보니 아래와 같이 run.id와 복합 키로 잡고 있어서 매번 다르게 인식하고 있었다.
해당 부분은 소스로 보면 아래와 같은데, RunIdIncrementer가 run.id의 키로 하나씩 키를 증가시키면서 실행하기 때문이다.
@Bean(JOB_NAME)
public Job rankingJob(JobRepository jobRepository, Step rankingStep) {
return new JobBuilder(JOB_NAME, jobRepository)
.incrementer(new RunIdIncrementer()) // <----
.start(rankingStep)
.build();
}
따라서 해당 부분을 주석하면 여러 번 실행되지 않는 것을 확인할 수 있다.
16:32:21.071 [main] ERROR o.s.boot.SpringApplication - Application run failed
java.lang.IllegalStateException: Failed to execute ApplicationRunner
...
Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={'dateParam':'{value=2024-08-21, type=class java.lang.String, identifying=true}'}. If you want to run this job again, change the parameters.
체크포인트
- RunIdIncrementer와 같이 매번 다른 키를 생성하는 job incrementer를 사용하지 않았는지 확인
- argument로 파라미터를 넘길 때 identifying 값을 true로 넘겼는지 확인
- 해당 의미는 고유 키값인지 의미로 true면 execution key로 인식한다.
dateParam="{\"value\":\"2024-08-21\",\"type\":\"java.lang.String\",\"identifying\":\"true\"}
- 이전 execution 결과, params 확인
- 이전 execution이 fail이면 execution id는 달라도 같은 job instance의 execution으로 묶여 재실행이 가능하다.
- 해당 내용을 확인할 수 있는 쿼리 참고..
select execution.JOB_EXECUTION_ID, execution.JOB_INSTANCE_ID, execution.CREATE_TIME, execution.STATUS, execution.EXIT_CODE, execution.EXIT_MESSAGE,
params.PARAMETER_NAME, params.PARAMETER_TYPE, params.PARAMETER_VALUE, params.IDENTIFYING
FROM BATCH_CASUAL_JOB_EXECUTION execution inner join BATCH_CASUAL_JOB_EXECUTION_PARAMS params on execution.JOB_EXECUTION_ID = params.JOB_EXECUTION_ID
order by JOB_EXECUTION_ID DESC
;
job instance 45의 경우 dateParam, identifying: true로 실패 -> 성공을 했고(execution 47, 48)
job instance 46번의 경우, 같은 dateParam이지만 identifying:false로 실행을 하니 다른 job parameter로 인식을 해서 실행을 되었고 실패 난 것을 알 수 있다.
결론
배치가 하루에 한 번 돌아야 하고 종종 수동으로 한번 더 돌릴 수 있으려면..
배치 당 unique 한 값(날짜 등)을 argument로 넘기고 job parameter로 받아서 적당한 scope의 빈에 등록해야 한다.
나의 경우, json의 형식으로 parameter를 넘기고 혹시 재실행이 필요할 경우 identifying: false로 재실행하려고 한다.
참고
argument로 파라미터 보내는 방법
Spring Boot Framework 버전 업그레이드 과정
new features of jdk 17 & spring boot 3major spring projectsJDK를 최소 17부터 19까지 지원함.Java 11과 비교하여 GC 등 성능 개선문자열, 리스트 등 다양한 API 지원타입 추론 키워드 추가switch 문 확장r
velog.io
파라미터를 bean으로 등록한다는 것의 의미
https://velog.io/@lxxjn0/Spring-Batch-Guide-05.-Spring-Batch-Scope-Job-Parameter
Spring Batch Guide - 05. Spring Batch Scope & Job Parameter
Spring Batch Guide 시리즈는 이동욱 개발자님의 Spring Batch 가이드를 보고 학습한 내용을 정리한 글입니다.많은 내용이 원 글과 유사할 수 있습니다. 이 점 양해바랍니다 🙏🏻 이번에는 Spring Batch의 S
velog.io
batch5에 추가된 내용
https://devfunny.tistory.com/931
[Kotlin + SpringBatch5] SpringBatch5의 다양한 파라미터 지원 - Job 생성해서 테스트 및 메타테이블 확인, i
SpringBatch5의 다양한 파라미터 지원 https://devfunny.tistory.com/930 SpringBatch5 변경사항 정리 (vs SpringBatch4) SpringBatch 5.0 이전 SpringBatch 공부할때 SpringBatch 4.0 버전이였다. 최근, SpringBatch 복습을 위해 새로
devfunny.tistory.com