Job

JobLauncher
Job
, JobParameters
์ ํจ๊ป ๋ฐฐ์น๋ฅผ ์คํํ๋ ์ธํฐํ์ด์ค๋ก, ๋ฉ์๋๋ run
ํ๊ฐ์ด๋ค.
Job์ ์คํํ๋ ์ญํ
Job.execute()
ํธ์ถJob์ฌ์คํ ๊ฐ๋ฅ ์ฌ๋ถ ๊ฒ์ฆ
Job ์คํ ๋ฐฉ๋ฒ(ํ์ฌ ์ค๋ ๋์์ ์ํํ ์ง, ์ค๋ ๋ ํ์ ํตํด ์คํํ ์ง ๋ฑ)
ํ๋ผ๋ฏธํฐ ์ ํจ์ฑ ๊ฒ์ฆ
์คํ๋ง๋ถํธ๋ฅผ ์ฌ์ฉํ๋ฉด ์คํ๋ง ๋ถํธ๊ฐ ์ฆ์ Job๋ฅผ ์์ํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํด, ์ผ๋ฐ์ ์ผ๋ก๋ ์ง์ ๋ค๋ฃฐ ํ์ ์์
public interface JobLauncher {
// Job๊ณผ JobParameters๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ JobExecution ๋ฐํ
// ๋งค๊ฐ๋ณ์๊ฐ ๋์ผํ๋ฉฐ, ์ด์ JobExecution๊ฐ ์ค๋จ๋ ์ ์ด ์์ผ๋ฉด ๋์ผํ JobExecution ๋ฐํ
JobExecution run(Job var1, JobParameters var2) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}
JobExplorer
org.springframework.batch.core.explore.JobExplorer
JobRepository
์ ์๋ ์ด๋ ฅ ๋ฐ์ดํฐ๋ ์ต์ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ ์์์ ์ด๋ค.JobExplorer
๋ ์ก ์ ๋ณด๋ฅผ ์ป๊ธฐ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ ๊ทผํ๋ค.
Set<JobExecution> findRunningJobExecutions(@Nullable String jobName)
์ข
๋ฃ ์๊ฐ์ด ์กด์ฌํ์ง ์๋ ๋ชจ๋ JobExecution
๋ฐํ
List<JobInstance> findJobInstancesByJobName(String jobName, int start, int count)
์ ๋ฌ๋ฐ์ ์ด๋ฆ์ ๊ฐ์ง JobInstance
๋ชฉ๋ก ๋ฐํ(ํ์ด์ง ์ฒ๋ฆฌ)
JobExecution getJobExecution(@Nullable Long executionId)
์ ๋ฌ๋ฐ์ ID๋ฅผ ๊ฐ์ง JobExecution
๋ฐํ
์กด์ฌํ์ง ์๋๋ค๋ฉด null ๋ฐํ
List<JobExecution> getJobExecutions(JobInstance jobInstance)
์ ๋ฌ๋ฐ์ JobInstance
์ ์ฐ๊ด๋ ๋ชจ๋ JobExecution
๋ชฉ๋ก ๋ฐํ
JobInstance getJobInstance(@Nullable Long instanceId)
์ ๋ฌ๋ฐ์ ID๋ฅผ ๊ฐ์ง JobInstance
๋ฐํ
์กด์ฌํ์ง ์์ผ๋ฉด null ๋ฐํ
List<JobInstance> getJobInstances(String jobName, int start, int count)
์ ๋ฌ๋ฐ์ ์ด๋ฆ์ ๊ฐ์ง JobInstance
๋ชฉ๋ก ๋ฐํ(ํ์ด์ง ์ฒ๋ฆฌ)
int getJobInstanceCount(@Nullable String jobName)
์ ๋ฌ๋ฐ์ ์ก ์ด๋ฆ์ผ๋ก ์์ฑ๋ JobInstance
์
List<String> getJobNames()
JobRepository
์ ์ ์ฅ๋ผ ์๋ ๊ณ ์ ํ ๋ชจ๋ ์ก ์ด๋ฆ ์ํ๋ฒณ ์ ๋ฆฌ์คํธ
StepExecution getStepExecution(@Nullable Long jobExecutionId, @Nullable Long stepExecutionId)
์ ๋ฌ๋ฐ์ jobExecutionId์ stepExecutionId๋ฅผ ๊ฐ์ง StepExecution
๋ฐํ
@AllArgsConstructor
public class ExploringTasklet implements Tasklet {
private JobExplorer jobExplorer;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
// ํ์ฌ Job์ด๋ฆ ์กฐํ
String jobName = chunkContext.getStepContext().getJobName();
// ์ด๋๊ฐ์ง ์คํ๋ ๋ชจ๋ JobInstance ์กฐํ
List<JobInstance> instaces = jobExplorer.getJobInstances(jobName, 0, Integer.MAX_VALUE);
System.out.println(String.format("%d job instances for the job %s", instaces.size(), jobName));
System.out.println("===========================");
for (JobInstance instance : instaces) {
// JobInstance์ ๊ด๋ จ๋ JobExecution
List<JobExecution> jobExecutions = this.jobExplorer.getJobExecutions(instance);
System.out.println(String.format("Instance %d had %d executions", instance.getInstanceId(), jobExecutions.size()));
for (JobExecution jobExecution : jobExecutions) {
System.out.println(String.format("Execution %d resulted in ExitStatus %s", jobExecution.getId(), jobExecution.getExitStatus()));
}
}
return RepeatStatus.FINISHED;
}
}
@EnableBatchProcessing
@Configuration
public class DemoTaskletConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Autowired
private JobExplorer jobExplorer;
@Bean
public Tasklet explorerTasklet() {
return new ExploringTasklet(this.jobExplorer);
}
@Bean
public Step explorerStep() {
return this.stepBuilderFactory.get("explorerStep")
.tasklet(explorerTasklet())
.build();
}
@Bean
public Job explorerJob() {
return this.jobBuilderFactory.get("explorerJob")
.start(explorerStep())
.build();
}
}
Job

๋ฐฐ์น ์ฒ๋ฆฌ ๊ณผ์ ์ ํ๋์ ๋จ์๋ก ํํํ ๊ฐ์ฒด๋ก ์ ์ฒด ๋ฐฐ์น ์ฒ๋ฆฌ์ ์์ด ์ต์๋จ ๊ณ์ธต
Job์ ์ค๋จ์ด๋ ์ํธ์์ฉ ์์ด ์ฒ์๋ถํฐ ๋๊น์ง ์คํ๋๋ ์ฒ๋ฆฌ
Spring Batch์์ Job ๊ฐ์ฒด๋ ์ฌ๋ฌ step ์ธ์คํด์ค๋ฅผ ํฌํจํ๋ ์ปจํ ์ด๋
Job Runner
Job์ ์คํ์ job runner์์ ์์๋๋ค. job runner๋ ์ก ์ด๋ฆ๊ณผ ํ๋ผ๋ฏธํฐ๋ก ํด๋น ์ก์ ์คํ์ํค๋ ์ญํ ์ ํ๋ค.
CommandLineJobRunner
: ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํ๊ฑฐ๋ ๋ช ๋ นํ์์ ์ง์ Job์ ์ํํ ๋ ์ฌ์ฉ์คํ๋ง ๋ฐฐ์น ์ ๊ณต
์คํ๋ง์ ๋ถํธ์คํธ๋ฉํ๋ฉฐ ์ ๋ฌ๋ฐ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํด ์์ฒญ๋ ์ก ์คํ
org.springframework.batch.core.launch.support.CommandLineJobRunner
JobRegistryBackgroundJobRunner
: ์คํ๋ง์ ๋ถํธ์คํธ๋ฉ์์ ๊ธฐ๋ํ ์๋ฐ ํ๋ก์ธ์ค ๋ด์์ Quartz๋ Jmx ํํฌ์ ๊ฐ์ ์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํด ์ก์ ์คํํ๋ฉดJobReistry
๋ฅผ ์์ฑํ๊ฒ ๋๋ค.JobRegistryBackgroundJobRunner
๋JobRegistry
๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ์คํ๋ง ๋ฐฐ์น ์ ๊ณต
org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner
JobLauncherCommandLineRunner
์คํ๋ง ๋ถํธ ์ ๊ณต
๋ณ๋์ ๊ตฌ์ฑ์ด ์๋ค๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก
ApplicationContext
์ ์ ์๋ Job ํ์ ์ ๋ชจ๋ ๋น์ ๊ธฐ๋์ ์คํ
Job Runner๋ ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ํ์ค ๋ชจ๋์ด ์๋๋ฉฐ, ๊ฐ ์๋๋ฆฌ์ค๋ง๋ค ๋ค๋ฅธ ๊ตฌํ์ฒด๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์ ํ๋ ์์ํฌ๊ฐ JobRunner
์ธํฐํ์ด์ค๋ฅผ ๋ณ๋๋ก ์ ๊ณตํ์ง ์๋๋ค.
์ค์ ๋ก ์ก ๋ฌ๋๊ฐ ์๋ org.springframework.batch.core.launch.JobLauncher
์ธํฐํ์ด์ค๊ฐ ํ๋ ์์ํฌ ์คํ์์์ ์ด๋ฉฐ, ์คํ๋ง ๋ฐฐ์น๋ SimpleJobLauncher
๋ง ์ ๊ณตํ๋ค.
JobBuilderFactory
Job ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ๋น๋๋ ์ฌ๋ฌ๊ฐ๊ฐ ์๋ค. JobBuilderFactory
๋ ์ฌ๋ฌ ๋น๋๋ฅผ ํตํฉ ์ฒ๋ฆฌํ๋ ๊ณต์ฅ์ด๋ฉฐ, ์ํ๋ Job์ ์์ฝ๊ฒ ๋ง๋ค ์ ์๋ค.
package org.springframework.batch.core.configuration.annotation;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
public class JobBuilderFactory {
private JobRepository jobRepository;
public JobBuilderFactory(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
// JobBuilder ์์ฑ ๋ฐํ
public JobBuilder get(String name) {
JobBuilder builder = (JobBuilder)(new JobBuilder(name)).repository(this.jobRepository);
return builder;
}
}
JobBuilderFactory
์ get
๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ์๋ก์ด JobBuilder
๋ฅผ ์์ฑ๋๋ฉฐ, ์๋ก์ด JobBuilder
๋ฅผ ์์ฑํ ๋๋ง๋ค JobBuilderFactory
๊ฐ ์์ฑ๋ ๋ ์ฃผ์
๋ฐ์ JobRepository
๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ํด๋น JobBuilderFactory
์์ ์์ฑ๋๋ ๋ชจ๋ JobBuilder
๊ฐ ๋์ผํ JobRepository
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
์ฆ, JobBuilderFactory
๋ JobBuilder
๋ฅผ ์์ฑํ๋ ์ญํ ๋ง ์ํํ๋ค.
JobBuilder
public class JobBuilder extends JobBuilderHelper<JobBuilder> {
public JobBuilder(String name) {
super(name);
}
// 1. step์ ์ถ๊ฐํ์ฌ ๊ฐ์ฅ ๊ธฐ๋ณธ์ด ๋๋ SimpleJobBuilder ์์ฑ
public SimpleJobBuilder start(Step step) {
return (new SimpleJobBuilder(this)).start(step);
}
// 2. Flow๋ฅผ ์คํํ JobFlowBuilder ์์ฑ
public JobFlowBuilder start(Flow flow) {
return (new FlowJobBuilder(this)).start(flow);
}
// 3. Step์ ์คํํ JobFlowBuilder ์์ฑ
public JobFlowBuilder flow(Step step) {
return (new FlowJobBuilder(this)).start(step);
}
}
JobBuilder
๋ ์ง์ ์ ์ผ๋ก Job
์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ ๋ณ๋์ ๊ตฌ์ฒด์ ์ธ ๋น๋๋ฅผ ์์ฑํด ๋ฐํํ๋ค. ์๋ํ๋ฉด ๊ฒฝ์ฐ์ ๋ฐ๋ผ Job
์์ฑ ๋ฐฉ๋ฒ์ด ๋ชจ๋ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ๊ตฌ์ฒด์ ์ธ ๋น๋๋ฅผ ๊ตฌํํ๊ณ , ์ด๋ฅผ ํตํด Job
์์ฑ์ด ์ด๋ฃจ์ด์ง๊ฒ ํ๋ค.
์ค๊ฐ์ ๋น๋๋ฅผ ํ๋ฒ ๋ ๋ฐํํ์ฌ ์ฌ์ฉํด์ผํ์ง๋ง, ๋ฉ์๋ ์ฒด์ธ ๋ฐฉ์์ ํ์ฉํ๋ฉด ์์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค. Job
์ Step
/ Flow
์ธ์คํด์ค์ ์ปจํ
์ด๋ ์ญํ ์ ํ๊ธฐ ๋๋ฌธ์ ์์ฑ ์ด์ ์ ์ธ์คํด์ค๋ฅผ ์ ๋ฌ ๋ฐ๋๋ค.
SimpleJobBuilder
๋กJob
์์ฑํ๊ธฐ
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Bean
public Job simpleJob(){
return jobBuilderFactory.get("simpleJob") // "simpleJob" ์ด๋ฆ์ ๊ฐ์ง JobBuilder instance ๋ฐํ
.start(simpleStep()) // step์ ์์ฑํ๋ ๋ฉ์๋๋ก ์์ฑ๋๋ SimpleJobBuilder
.build(); // build ๋ฉ์๋ ํธ์ถ๋ก Job ๋ฐํ
}
JobInstance
๋ฐฐ์น ์ฒ๋ฆฌ์์ Job
์ด ์คํ๋ ๋ ํ๋์ Job
์คํ๋จ์์ด๋ค. ์๋ฅผ๋ค์ด ํ๋ฃจ์ ํ๋ฒ ๋ฐฐ์น Job
์ด ์คํ๋๋ค๋ฉด, ์ด์ ์ค๋ ๊ฐ๊ฐ ์คํ๋ Job
์ JobInstance
๋ผ ๋ถ๋ฅธ๋ค.
JobInstance๋ ์ด๋ฆ๊ณผ ๋ ผ๋ฆฌ์ ์คํ์ ์ํด ์ ๊ณต๋๋ ๊ณ ์ ํ ์๋ณ ํ๋ผ๋ฏธํฐ ๋ชจ์์ผ๋ก ์ ์ผํ๊ฒ ์กด์ฌํ๋ค.
ExampleGenerator
์ด๋ฆ์ Job์ด ๋ค๋ฅธ ํ๋ผ๋ฏธํฐ๋ก ์คํ๋ ๋๋ง๋ค ์๋ก์ดJobInstace
๊ฐ ์์ฑ๋๋ค.BATCH_JOB_INSTANCE
ํ ์ด๋ธ๋ก ๊ด๋ฆฌBATCH_JOB_EXECUTION_PARAMS
์์ ์ค์ ์๋ณ ํ๋ผ๋ฏธํฐ ๊ด๋ฆฌ
Job์ ์ฒ์ ์คํํ๋ฉด ์๋ก์ด JobInstance๋ฅผ ์ป๋๋ค. ํ์ง๋ง ์คํ์ ์คํจํ ์ดํ ๋ค์ ์คํํ๋ฉด, ์ฌ์ ํ ๋์ผํ ๋
ผ๋ฆฌ์ ์คํ(ํ๋ผ๋ฏธํฐ ๋์ผ)์ด๋ฏ๋ก ์๋ก์ด JobInstance๋ฅผ ์ป์ง ๋ชปํ๋ฉฐ, ์ค์ ์คํ์ ์ถ์ ํ๊ธฐ ์ํ ์๋ก์ด JobExecution
์ ์ป์ ๊ฒ์ด๋ค.
์ฆ, JobInstance
๋ ์คํจํ JobExecution
๊ณผ ์๋ก ์ํํ JobExecution
๊ณผ ๊ฐ์ด JobExecution
์ ์ฌ๋ฌ ๊ฐ ๊ฐ์ง ์ ์๋ค.
JobExecution
JobInstance
์ ๋ํ ํ ๋ฒ์ ์คํ(์ค์ ์๋)์ ๋ํ๋ด๋ ๊ฐ์ฒด์ด๋ค. JobExecution
์ Job
์คํ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋๋ฉ์ธ ๊ฐ์ฒด์ด๋ฉฐ, JobInstance
, ๋ฐฐ์น ์คํ ์ํ, ์์ ์๊ฐ, ๋๋ ์๊ฐ, ์ค๋ฅ ๋ฉ์ธ์ง ๋ฑ์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ค.
JobExecution
์ Job์ด ๊ตฌ๋๋ ๋ ๋ง๋ค ๋งค๋ฒ ์๋ก์ดJobExecution
์ ์ป๊ฒ๋๋ค.BATCH_JOB_EXECUTION
ํ ์ด๋ธ์ ๊ฐ ๋ ์ฝ๋๋ก ์ ์ฅBATCH_JOB_EXECUTION_CONTEXT
ํ ์ด๋ธ์ ์ํ ๊ฐ ์ ์ฅ
public class JobExecution extends Entity {
private final JobParameters jobParameters; //Job ์คํ์ ํ์ํ ๋งค๊ฐ ๋ณ์ ๋ฐ์ดํฐ
private JobInstance jobInstance; // Job ์คํ์ ๋จ์๊ฐ ๋๋ ๊ฐ์ฒด
private volatile Collection<StepExecution> stepExecutions; // StepExecution์ ์ฌ๋ฌ๊ฐ ๊ฐ์ง ์ ์๋ Collection ํ์
private volatile BatchStatus status; // Job์ ์คํ ์ํ(COMPLETED, STARTING, STARTED ...)
private volatile Date startTime; // Job์ด ์คํ๋ ์๊ฐ(null์ ์์ํ์ง ์์ ๊ฒ)
private volatile Date createTime; // JobExecution์ด ์์ฑ๋ ์๊ฐ
private volatile Date endTime; // JobExecution ์ข
๋ฃ ์๊ฐ
private volatile Date lastUpdated; // ๋ง์ง๋ง ์์ ์๊ฐ
private volatile ExitStatus exitStatus; // Job ์คํ ๊ฒฐ๊ณผ์ ๋ํ ์ํ๊ฐ(UNKOWN, EXECUTING, COMPLETE, ...)
private volatile ExecutionContext executionContext;// Job ์คํ ์ฌ์ด์ ์ ์งํด์ผํ๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ
private transient volatile List<Throwable> failureExceptions; // Job ์คํ ์ค ๋ฐ์ํ ์์ธ
private final String jobConfigurationName; // Job ์ค์ ์ด๋ฆ
...
}
JobParameters
Job
์ด ์คํ๋ ๋ ํ์ํ ํ๋ผ๋ฏธํฐ๋ค์ Map
ํ์
์ผ๋ก ์ง์ ํ๋ ๊ฐ์ฒด๋ก JobInstance
(1:1 ๊ด๊ณ)๋ฅผ ๊ตฌ๋ถํ๋ ๊ธฐ์ค์ด ๋๊ธฐ๋ ํ๋ค.
ํ๋์ Job
์ ์์ฑํ ๋ ์์ ์๊ฐ ๋ฑ์ ์ ๋ณด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ํด์ ํ๋์ JobInstance
๋ฅผ ์์ฑํ๋ค. ์ฆ, 1:1 ๊ด๊ณ์ด๋ค.
ํ๋ผ๋ฏธํฐ๋
key=value
๋ก ์ด๋ฃจ์ด์ ธ์๋ค.JobParameters๋
Map<String,JobParameter>
์ wrapper์ ๋ถ๊ณผํ๋ค.public class JobParameters implements Serializable { private final Map<String,JobParameter> parameters; ... }
ํ๋ผ๋ฏธํฐ ํ์ ์ String, Double, Date ํ์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ค.
ํ๋ผ๋ฏธํฐ ์ด๋ฆ ๋ค์ ๊ดํธ๋ฅผ ์ฐ๊ณ ํด๋น ํ๋ผ๋ฏธํฐ ํ์ ์ ๋ช ์ํด ์ง์ ํ ์ ์๋ค.
์ด๋, ํ๋ผ๋ฏธํฐ ํ์ ์ ์ด๋ฆ์ ๋ชจ๋ ์๋ฌธ์์ฌ์ผํ๋ค.
executionDate(date)=2021/11/27
BATCH_JOB_EXECUTION_PARAMS
์๋ณ์ ์ฌ์ฉํ์ง ์๋ ํ๋ผ๋ฏธํฐ๋ ์์ ์ ์๋ค. ์๋ณ์ ์ฌ์ฉํ๊ณ ์ถ์ง ์๋ ํ๋ผ๋ฏธํฐ๋ ์์
-
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.executionDate(date)=2021/11/27 -filename=test
Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={executionDate=1637938800000, filename=test}. If you want to run this job again, change the parameters. at org.springframework.batch.core.repository.support.SimpleJobRepository.createJobExecution(SimpleJobRepository.java:139) ~[spring-batch-core-4.3.3.jar:4.3.3] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.12.jar:5.3.12] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.12.jar:5.3.12] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
๋ค์๊ณผ ๊ฐ์ด
-
๋ฅผ ๋ถ์ธ ํ๋ผ๋ฏธํฐ๋ ์๋ณ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํ์ง ์์, ๊ธฐ์กด์ ์ด๋ฏธ ์ํ๋ Job์ผ๋ก ์คํจํ ๊ฒ์ ์ ์ ์๋ค.
ํ๋ผ๋ฏธํฐ์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ
ChunkContext
์ธ์คํด์ค์คํ ์์ ์ Job ์ํ ์ ๊ณต
tasklet ๋ด์์ ์ฒ๋ฆฌ์ค์ธ chunk์ ๊ด๋ จ๋ ์ ๋ณด(์คํญ ๋ฐ ์ก๊ณผ ๊ด๋ จ๋ ์ ๋ณด ํฌํจ) ์ ๊ณต
JobParametes
๊ฐ ํฌํจ๋StepContext
์ฐธ์กฐ๊ฐ ์์public class ChunkContext extends AttributeAccessorSupport { private final StepContext stepContext; ... }
public class StepContext extends SynchronizedAttributeAccessor { private StepExecution stepExecution; private Map<String, Set<Runnable>> callbacks = new HashMap<>(); private BatchPropertyContext propertyContext = null; ... /** * @return a map containing the items from the {@link JobParameters} */ public Map<String, Object> getJobParameters() { Map<String, Object> result = new HashMap<>(); for (Entry<String, JobParameter> entry : stepExecution.getJobParameters().getParameters().entrySet()) { result.put(entry.getKey(), entry.getValue().getValue()); } return Collections.unmodifiableMap(result); } ... }
์์
@Bean Step step2(){ return this.stepBuilderFactory.get("step2") .tasklet(helloWorldTasklet()) .build(); } @Bean public Tasklet helloWorldTasklet() { return ((contribution, chunkContext) -> { String name = (String) chunkContext.getStepContext() .getJobParameters() // Map<String, Object>์ด๋ฏ๋ก ํ์ ์บ์คํ ํ์ .get("name"); System.out.println(String.format("Hello, %s", name)); return RepeatStatus.FINISHED; }); }
Late Binding : ์คํ๋ง ๊ตฌ์ฑ์ ์ฌ์ฉํด ์ฃผ์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก,
JobParameters
๋ ๋ณ๊ฒฝํ ์ ์์ผ๋ฏ๋ก ๋ถํธ์คํธ๋ฉ์ ๋ฐ์ธ๋ฉํ๋ ๊ฒ์ด ์ข๋ค.@StepScope @Bean public Tasklet lateBindingParamTasklet(@Value("#{jobParameters['name']}") String name) { return ((contribution, chunkContext) -> { System.out.println(String.format("Hello, %s", name)); return RepeatStatus.FINISHED; }); }
ํ๋ผ๋ฏธํฐ ์ ํจ์ฑ ๊ฒ์ฆ
org.springframework.batch.core.JobParametersValidator
public interface JobParametersValidator { /** * Check the parameters meet whatever requirements are appropriate, and * throw an exception if not. * * @param parameters some {@link JobParameters} (can be {@code null}) * @throws JobParametersInvalidException if the parameters are invalid */ void validate(@Nullable JobParameters parameters) throws JobParametersInvalidException; }
JobParametersValidator
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ , ํด๋น ๊ตฌํ์ฒด๋ฅผ ์ก ๋ด์ ๊ตฌ์ฑํด ํ๋ผ๋ฏธํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ ์ ์๋ค.
์คํ๋ง์ ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋๋ฝ์์ด ์ ๋ฌ๋๋์ง ํ์ธํ๋ DefaultJobParametersValidator
๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด์ค๋ค.
DefaultJobParametersValidator
์ฌ์ฉํ ์ ํจ์ฑ ๊ฒ์ฆ : ํ๋ผ๋ฏธํฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ์ ์ธํ ๋ค๋ฅธ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ํํ์ง ์์.@Bean public JobParametersValidator validator() { DefaultJobParametersValidator validator = new DefaultJobParametersValidator(); validator.setRequiredKeys(new String[] {"executionDate", "fileName"}); // ํ์ ํ๋ผ๋ฏธํฐ ํ์ธ validator.setOptionalKeys(new String[] {"name"}); // ์ ํ ํ๋ผ๋ฏธํฐ validator.afterPropertiesSet(); // ์ ํ ํ๋ผ๋ฏธํฐ์ ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋์ง ์์๋์ง ํ์ธ return validator; }
ํ์ ํ๋ผ๋ฏธํฐ ๋ฏธํฌํจ์ ์ค๋ฅ
Caused by: org.springframework.batch.core.JobParametersInvalidException: The JobParameters do not contain required keys: [fileName] at org.springframework.batch.core.job.DefaultJobParametersValidator.validate(DefaultJobParametersValidator.java:120) ~[spring-batch-core-4.3.3.jar:4.3.3]
์ ํ ํ๋ผ๋ฏธํฐ์ ํ์ ํ๋ผ๋ฏธํฐ ๊ฒน์น๋ ๊ฒฝ์ฐ ์ค๋ฅ
Caused by: java.lang.IllegalStateException: Optional keys cannot be required: fileName at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.12.jar:5.3.12] at org.springframework.batch.core.job.DefaultJobParametersValidator.afterPropertiesSet(DefaultJobParametersValidator.java:73) ~[spring-batch-core-4.3.3.jar:4.3.3]
์ ํ ํ๋ผ๋ฏธํฐ์ ํ์ ํ๋ผ๋ฏธํฐ์ ํฌํจ๋์ง ์์ ํ๋ผ๋ฏธํฐ ์ ์ก์ ์ค๋ฅ
Caused by: org.springframework.batch.core.JobParametersInvalidException: The JobParameters contains keys that are not explicitly optional or required: [displyYn] at org.springframework.batch.core.job.DefaultJobParametersValidator.validate(DefaultJobParametersValidator.java:107) ~[spring-batch-core-4.3.3.jar:4.3.3]
์ปค์คํ ์ ํจ์ฑ ๊ฒ์ฆ : ํ๋ผ๋ฏธํฐ ์กด์ฌ ์ฌ๋ถ ์ธ์ ์ถ๊ฐ ์ ํจ์ฑ ๊ฒ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ ์ปค์คํ
JobParametersValidator
๊ตฌํ ํ์public class ParameterValidator implements JobParametersValidator { @Override public void validate(JobParameters parameters) throws JobParametersInvalidException { String fileName = parameters.getString("fileName"); if (!StringUtils.hasText(fileName)) { throw new JobParametersInvalidException("fileName ํ๋ผ๋ฏธํฐ๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค."); } else if (!StringUtils.endsWithIgnoreCase(fileName, ".csv")) { throw new JobParametersInvalidException("csv ํ์ผ์ด ์๋๋๋ค."); } } }
CompositeJobParametersValidator
: ์ฌ๋ฌ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฆ@Bean public CompositeJobParametersValidator validator() { CompositeJobParametersValidator validator = new CompositeJobParametersValidator(); // ํ๋ผ๋ฏธํฐ ์ ๋ฌด ๊ฒ์ฆ DefaultJobParametersValidator defaultJobParametersValidator = new DefaultJobParametersValidator(); defaultJobParametersValidator.setRequiredKeys(new String[] {"executionDate", "fileName"}); // ํ์ ํ๋ผ๋ฏธํฐ ํ์ธ defaultJobParametersValidator.setOptionalKeys(new String[] {"name"}); // ์ ํ ํ๋ผ๋ฏธํฐ defaultJobParametersValidator.afterPropertiesSet(); // ์ ํ ํ๋ผ๋ฏธํฐ์ ํ์ ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋์ง ์์๋์ง ํ์ธ // custom validator์ defaultJobParametersValidator List๋ก ์ ์ก validator.setValidators(Arrays.asList(new ParameterValidator(), defaultJobParametersValidator)); return validator; }
์ก ํ๋ผ๋ฏธํฐ ์ฆ๊ฐ์ํค๊ธฐ
org.springframework.batch.core.JobParametersIncrementer
public interface JobParametersIncrementer { /** * Increment the provided parameters. If the input is empty, then this * should return a bootstrap or initial value to be used on the first * instance of a job. * * @param parameters the last value used * @return the next value to use (never {@code null}) */ JobParameters getNext(@Nullable JobParameters parameters); }
JobParametersIncrementer
๋ ์ฌ์ฉํ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ณ ์ ํ๊ฒ ์์ฑํ ์ ์๊ฒ ์คํ๋ง ๋ฐฐ์น๊ฐ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค๋ก ๋งค ์คํ ์ timestamp๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฆ๊ฐ์์ผ์ผํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ๊ธฐ ์ ํฉํ๋ค.
RunIdIncrementer
: ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ดrun.id
(long)์ ๊ฐ์ ์ฆ๊ฐCustom Incrementer
public class DailyJobTimestamper implements JobParametersIncrementer { @Override public JobParameters getNext(JobParameters parameters) { return new JobParametersBuilder(parameters) .addDate("executionDate", new Date()) .toJobParameters(); } }
jobBuilderFactory์์ .incrementer()
๋ก ์ํํ ์ ์๋ค.
@Bean
public Job job() {
// jobBuilderFactory.get("์ก์ด๋ฆ")
return this.jobBuilderFactory.get("basicJob")
.start(step1())
.validator(validator())
.incrementer(new RunIdIncrementer())
.next(step2())
.build(); // ์ค์ job ์์ฑ
}
JobListener
๋ชจ๋ Job์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์คํ๋ง ๋ฐฐ์น๋ ์๋ช ์ฃผ๊ธฐ์ ํน์ ์์ ์์ ๋ก์ง์ ์ถ๊ฐํ ์ ์๊ฒ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ค๋ค.
JobExecutionListener
: Job ์คํ๊ณผ ๊ด๋ จ๋ ๋ฆฌ์ค๋ ์ธํฐํ์ด์คpublic interface JobExecutionListener { /** * Callback before a job executes. * * @param jobExecution the current {@link JobExecution} */ void beforeJob(JobExecution jobExecution); /** * Callback after completion of a job. Called after both both successful and * failed executions. To perform logic on a particular status, use * "if (jobExecution.getStatus() == BatchStatus.X)". * * @param jobExecution the current {@link JobExecution} */ void afterJob(JobExecution jobExecution); }
beforeJob
: Job ์ํ ์ด์ ์ ์ํafterJob
: Job ์ํ์๋ฃ ํ ์ํํ๋ฉฐ Job์ ์๋ฃ ์ํ์ ์๊ด ์์ด ํธ์ถ๋๋ค.
Job Listener๋ฅผ ์์ฑํ๋๋ฐ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
JobExecutionListener
์ธํฐํ์ด์ค ๊ตฌํpublic class JobLoggerListener implements JobExecutionListener { private static String START_MESSAGE = "%s is beginning execution"; private static String END_MESSAGE = "%s has completed with the status %s"; @Override public void beforeJob(JobExecution jobExecution) { System.out.println(String.format(START_MESSAGE, jobExecution.getJobInstance().getJobName())); } @Override public void afterJob(JobExecution jobExecution) { System.out.println(String.format(END_MESSAGE, jobExecution.getJobInstance().getJobName() , jobExecution.getStatus())); } }
JobBuilder
์listener
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด Job์ํ ์ ํ๋ก ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.return this.jobBuilderFactory.get("basicJob") .start(step1()) .validator(validator()) .incrementer(new DailyJobTimestamper()) .listener(new JobLoggerListener()) .next(step2()) .build(); // ์ค์ job ์์ฑ
2021-11-16 23:35:57.266 INFO 80890 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=basicJob]] launched with the following parameters: [{name=faker, executionDate=1637073357082, fileName=test4.csv, run.id=3}] basicJob is beginning execution ... 2021-11-16 23:35:57.656 INFO 80890 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 129ms basicJob has completed with the status COMPLETED
์ด๋ ธํ ์ด์ ์ฌ์ฉ(
@BeforeJob
,@AfterJob
) : ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํ์ ์์ด ์ด๋ ธํ ์ด์ ๋ง์ผ๋ก ๊ตฌํํ๋ฉด ๋๋ค.public class JobLoggerListener { private static String START_MESSAGE = "%s is beginning execution"; private static String END_MESSAGE = "%s has completed with the status %s"; @BeforeJob public void beforeJob(JobExecution jobExecution) { System.out.println(String.format(START_MESSAGE, jobExecution.getJobInstance().getJobName())); } @AfterJob public void afterJob(JobExecution jobExecution) { System.out.println(String.format(END_MESSAGE, jobExecution.getJobInstance().getJobName() , jobExecution.getStatus())); } }
์ด๋ ธํ ์ด์ ์ผ๋ก ๊ตฌํํ๋ ๊ฒฝ์ฐ
JobListenerFactoryBean
์ผ๋ก ๋ฆฌ์ค๋๋ฅผ ์ฃผ์ ํ ์ ์๋ค.return this.jobBuilderFactory.get("basicJob") .start(step1()) .validator(validator()) .incrementer(new DailyJobTimestamper()) .listener(JobListenerFactoryBean.getListener(new JobLoggerListener())) .next(step2()) .build(); // ์ค์ job ์์ฑ
ExecutionContext

ExecutionContext
๋ ๋ฐฐ์น ์ก์ ์ธ์
์ผ๋ก, ๊ฐ๋จํ ํค-๊ฐ์ ๋ณด๊ดํ๋ค. ์ด๋ Job์ ์ํ๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ ์ ์๊ฒ ์ ๊ณตํด์ค๋ค. Job์ ์ํํ๋ ๊ณผ์ ์์ ์ฌ๋ฌ๊ฐ์ ExecutionContext
๊ฐ ์กด์ฌํ ์ ์๋ค.
Job์ ๋ํ ์ํ :
JobExecution
์ExecutionContext
์ ์ ์ฅStep์ ๋ํ ์ํ :
StepExecution
์ExecutionContext
์ด๋ ๊ฒ ๊ฐ Step์ฉ ๋ฐ์ดํฐ์ Job ์ ์ฒด์ฉ ๋ฐ์ดํฐ์ ๊ฐ์ด ๋ฐ์ดํฐ ์ฌ์ฉ ๋ฒ์๋ฅผ ์ง์ ํ ์ ์๋ค.
ExecutionContext
๊ฐ ๋ด๊ณ ์๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ JopRepository
์ ์ ์ฅ๋๋ค.
ExecutionContext ๋ค๋ฃจ๊ธฐ
Job์ ExecutionContext๋ฅผ ๊ฐ์ ธ์ค๊ธฐ
@Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { String name = (String) chunkContext.getStepContext() .getJobParameters() .get("name"); ExecutionContext jobContext = chunkContext.getStepContext() .getStepExecution() .getJobExecution() .getExecutionContext(); jobContext.put("user.name", name); System.out.println(String.format(HELLO_WORLD, name)); return RepeatStatus.FINISHED; }
Step ExecutionContext
@Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { String name = (String) chunkContext.getStepContext() .getJobParameters() .get("name"); // 2. Step ExecutionContext ExecutionContext stepContext = chunkContext.getStepContext() .getStepExecution() .getExecutionContext(); jobContext.put("user.name", name); System.out.println(String.format(HELLO_WORLD, name)); return RepeatStatus.FINISHED; }
ExecutionContextPromotionListener
: Step์ExecutionContext
์ ์๋ ํค๋ฅผJobExecution
์ExecutionContext
๋ก ์น๊ฒฉํ ์ ์๋ค.@EnableBatchProcessing // ๋ฐฐ์น ์์ ์ ํ์ํ ๋น์ ๋ฏธ๋ฆฌ ๋ฑ๋กํ์ฌ ์ฌ์ฉ, ์ ํ๋ฆฌ์ผ์ด์ ๋ด ํ๋ฒ๋ง ์ ์ฉํ๋ฉด ๋จ. @SpringBootApplication public class JavaGradleApplication { @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; /** * ์ค์ ์คํ๋ง ๋ฐฐ์น Job ์์ฑ */ @Bean public Job job() { // jobBuilderFactory.get("์ก์ด๋ฆ") return this.jobBuilderFactory.get("basicJob") .start(step1()) .next(step2()) .build(); // ์ค์ job ์์ฑ } /** * ์ค์ ์คํ๋ง ๋ฐฐ์น step ์์ฑ * * @return */ @Bean public Step step1() { // stepBuilderFactory.get("์คํญ ์ด๋ฆ") // tasklet ๊ตฌํ์ฒด return this.stepBuilderFactory.get("step1") .tasklet((contribution, chunkContext) -> { System.out.println("Hello, world!"); return RepeatStatus.FINISHED; }) .listener(promotionListener()) // step์ด ์๋ฃ ์ํ๋ก ์ข ๋ฃ๋ ์ดํ ์ํ .build(); } @Bean Step step2() { return this.stepBuilderFactory.get("step2") .tasklet(lateBindingParamTasklet("test")) .build(); } /** * step์ด ์๋ฃ ์ํ๋ก ์ข ๋ฃ๋ ์ดํ ์ํ * "name" ํค๋ฅผ ์ฐพ์ Job์ ExecutionContext์ ๋ณต์ฌ * * @return */ @Bean public StepExecutionListener promotionListener() { ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); listener.setKeys(new String[]{"name"}); return listener; } public static void main(String[] args) { SpringApplication.run(JavaGradleApplication.class, args); } }
์ ์์ ์ ๊ฐ์ด ์คํ ๊ฐ์ ๊ณต์ ํ ๋ฐ์ดํฐ๊ฐ ์์ง๋ง ์ฒซ๋ฒ์งธ step์ด ์ฑ๊ณตํ์๋๋ง ๊ณต์ ํ ๋ ์ ์ฉํ๋ค.
ExecutionContext ์ ์ฅ
Job์ด ์ฒ๋ฆฌ๋๋ ๋์ ์คํ๋ง ๋ฐฐ์น๋ ๊ฐ ์ฒญํฌ๋ฅผ ์ปค๋ฐํ๋ฉฐ Job๊ณผ Step์ ์ํ๋ฅผ ์ ์ฅํ๋ค. (ExecutionContext
๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ)
์์ Step ExecutionContext ์์ ๋ฅผ ์ํํ๋ฉด BATCH_STEP_EXECUTION_CONTEXT
์ ๋ค์๊ณผ ๊ฐ์ด "user.name":"dahyelele"
์ง์ ํ ๋ฐ์ดํฐ๊ฐ ํค-๊ฐ์ผ๋ก ๋ค์ด๊ฐ ๊ฒ์ ์ ์ ์๋ค.
{"@class":"java.util.HashMap","batch.taskletType":"io.spring.batch.javagradle.tasklet.HelloWorld","user.name":"dahyelele","batch.stepType":"org.springframework.batch.core.step.tasklet.TaskletStep"}
Last updated
Was this helpful?