@Value("#{jobParameters[파라미터명]}")
jobParameters 외에도 jobExecutionContext, stepExecutionContext 등도 SpEL로 사용할 수 있다. @JobScope에선 stepExecutionContext는 사용할 수 없고, jobParameters와 jobExecutionContext만 사용할 수 있다.@Value를 통해서 등록이 가능하다.@StepScope, @JobScope Bean을 생성할때만 JobParameters가 생성된다.@Configuration
@EnableBatchProcessing
class MemberWithdrawalConfiguration(
private val jobBuilderFactory: JobBuilderFactory,
private val memberWithdrawalJobListener: MemberWithdrawalJobListener,
private val skipListener: MemberWithdrawalSkipListener,
private val stepBuilderFactory: StepBuilderFactory,
private val transactionManager: JpaTransactionManager,
private val memberWithdrawalJobParameter: MemberWithdrawalJobParameter,
private val memberWithdrawalItemReader: ItemReader<DormantMemberDetails>,
private val memberWithdrawalCompositeItemWriter: CompositeItemWriter<DormantMemberDetails>
): Log {
@Bean
fun memberWithdrawalJob(): Job {
return jobBuilderFactory.get("memberWithdrawalJob")
.start(memberWithdrawalStep(null))
.listener(memberWithdrawalJobListener)
.build()
}
@Bean
@JobScope
fun memberWithdrawalStep(@Value("#{jobParameters[CHUNK]}") chunk: Long?): Step{
return stepBuilderFactory["memberWithdrawalStep"]
.chunk<DormantMemberDetails, DormantMemberDetails>( memberWithdrawalJobParameter.chunk ?: 100)
.reader(memberWithdrawalItemReader)
.processor(memberWithdrawalProcessor())
.writer(memberWithdrawalCompositeItemWriter)
.faultTolerant()
.listener(skipListener)
.skip(Exception::class.java)
.skipLimit(100)
.transactionManager(transactionManager)
.build()
}
@Bean
fun memberWithdrawalProcessor() = MemberWithdrawalProcessor()
}
Double, Long, Date, String이 있으면 LocalDate, LocalDateTime, Instant는 지원하지 않아 String으로 받은 다음에 다시 타입변환을 해서 사용해야 한다.@Bean과 @StepScope를 함께 쓰는 것은 @Scope (value = "step", proxyMode = TARGET_CLASS)로 표시하는 것과 같다.@StepScope를 사용하게 되면 Step 실행 시점에 해당 컴포넌트를 Bean으로 생성한다.@Configuration
class MemberWithdrawalReaderConfiguration(
private val memberWithdrawalJobParameter: MemberWithdrawalJobParameter,
@Qualifier("memberSlaveDataSource") private val memberSlaveDataSource: DataSource
): Log {
@Bean
@StepScope
fun memberWithdrawalItemReader(): ItemReader<DormantMemberDetails> {
val status = "dormant"
val date = ZonedDateTime.now().minusYears(5)
logger.info(date.toString())
return JdbcPagingItemReaderBuilder<DormantMemberDetails>()
.name("itemReader")
.pageSize(memberWithdrawalJobParameter.chunk ?: 100)
.fetchSize(memberWithdrawalJobParameter.chunk ?: 100)
.dataSource(memberSlaveDataSource)
.queryProvider(queryProvider())
.rowMapper{ rs, _ -> mappingResultSet(rs)}
.parameterValues(
mapOf(
"status" to status,
"dormantDate" to date
)
)
.build()
}
}
@StepScope 없이 Step을 병렬로 실행시키게 되면 서로 다른 Step에서 하나의 Tasklet을 두고 마구잡이로 상태를 변경하려고 할 것이다.@StepScope가 있다면 각각의 Step에서 별도의 Tasklet을 생성하고 관리하기 때문에 서로의 상태를 침범할 일이 없다.@Configuration
@EnableBatchProcessing
class MemberWithdrawalConfiguration(
private val jobBuilderFactory: JobBuilderFactory,
private val memberWithdrawalJobListener: MemberWithdrawalJobListener,
private val skipListener: MemberWithdrawalSkipListener,
private val stepBuilderFactory: StepBuilderFactory,
private val transactionManager: JpaTransactionManager,
private val memberWithdrawalJobParameter: MemberWithdrawalJobParameter,
private val memberWithdrawalItemReader: ItemReader<DormantMemberDetails>,
private val memberWithdrawalCompositeItemWriter: CompositeItemWriter<DormantMemberDetails>
): Log {
@Bean
fun memberWithdrawalJob(): Job {
return jobBuilderFactory.get("memberWithdrawalJob")
.start(memberWithdrawalStep(null))
.listener(memberWithdrawalJobListener)
.build()
}
@Bean
@JobScope
fun memberWithdrawalStep(@Value("#{jobParameters[CHUNK]}") chunk: Long?): Step{
return stepBuilderFactory["memberWithdrawalStep"]
.chunk<DormantMemberDetails, DormantMemberDetails>( memberWithdrawalJobParameter.chunk ?: 100)
.reader(memberWithdrawalItemReader)
.processor(memberWithdrawalProcessor())
.writer(memberWithdrawalCompositeItemWriter)
.faultTolerant()
.listener(skipListener)
.skip(Exception::class.java)
.skipLimit(100)
.transactionManager(transactionManager)
.build()
}
@Bean
fun memberWithdrawalProcessor() = MemberWithdrawalProcessor()
}
start(memberWithdrawalStep(null)): memberWithdrawalStep을 호출하기 위해 null이라는 임시 값을 강제로 넣어줘야 한다.open class MemberWithdrawalJobParameter {
@Value("#{jobParameters[CHUNK]}")
open val chunk: Int? = null
open var referenceDate: Instant? = null
@Value("#{jobParameters[REFERENCE_DATE]}")
fun setReferenceDate(referenceDate: String?) {
this.referenceDate = Instant.parse(referenceDate)
}
}
LocalDate, LocalDateTime, Instant 등 자동 형변환이 되지 않는 경우에는 Setter를 정의한 다음 @Value를 사용하여 문자열로 받은 후, 형 변환을 진행한다.@Configuration
@EnableBatchProcessing
class MemberWithdrawalConfiguration(
private val jobBuilderFactory: JobBuilderFactory,
private val memberWithdrawalJobListener: MemberWithdrawalJobListener,
private val skipListener: MemberWithdrawalSkipListener,
private val stepBuilderFactory: StepBuilderFactory,
private val transactionManager: JpaTransactionManager,
private val memberWithdrawalJobParameter: MemberWithdrawalJobParameter,
private val memberWithdrawalItemReader: ItemReader<DormantMemberDetails>,
private val memberWithdrawalCompositeItemWriter: CompositeItemWriter<DormantMemberDetails>
): Log {
@Bean
@JobScope
fun memberWithdrawalJobParameter(): MemberWithdrawalJobParameter {
return MemberWithdrawalJobParameter()
}
@Bean
fun memberWithdrawalJob(): Job {
return jobBuilderFactory.get("memberWithdrawalJob")
.start(memberWithdrawalStep())
.listener(memberWithdrawalJobListener)
.build()
}
@Bean
@JobScope
fun memberWithdrawalStep(): Step {
return stepBuilderFactory["memberWithdrawalStep"]
.chunk<DormantMemberDetails, DormantMemberDetails>( memberWithdrawalJobParameter.chunk ?: 100)
.reader(memberWithdrawalItemReader)
.processor(memberWithdrawalProcessor())
.writer(memberWithdrawalCompositeItemWriter)
.faultTolerant()
.listener(skipListener)
.skip(Exception::class.java)
.skipLimit(100)
.transactionManager(transactionManager)
.build()
}
@Bean
fun memberWithdrawalProcessor() = MemberWithdrawalProcessor()
}
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'memberWithdrawalJobParameter' defined in BeanDefinition defined in class path resource
[kr/co/mustit/batch/job/MemberWithdrawalConfiguration.class]: Initialization of bean failed; nested exception is
org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class kr.co.mustit.batch.dto.MemberWithdrawalJobParameter: Common causes
of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class
kr.co.mustit.batch.dto.MemberWithdrawalJobParameter
5. Spring Batch 가이드 - Spring Batch Scope & Job Parameter
JobParameter 활용 방법 (feat. LocalDate 파라미터 사용하기)