๋ ๋ฆฝ์ ์ผ๋ก ์คํํ ์ ์๋ ๊ณ ์ ํ๋ฉฐ ์์๊ฐ ์ง์ ๋ ์คํ ์ ๋ชฉ๋ก
์ก์ ์คํ์ Job runer์์ ์์๋๋ค.
Job -> JobInstance -> JobExecution
JobInstanace๋ ์ฑ๊ณต์ ์ผ๋ก ์ํ๋ JobExecution์ด ์๋ค๋ฉด ์๋ฃ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ. ํ๋ฒ ์๋ฃ๋๋ฉด ๋ค์ ์คํ ๋ถ๊ฐ
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/batch?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=
# spring batch์ ๊ด๋ จ๋ table๋ค์ด rdbms์ ์๋์ผ๋ก ์์ฑ
spring.batch.jdbc.initialize-schema=always
// spring batch์ ํ์ํ bean๋ค autowired ๊ฐ๋ฅ
@EnableBatchProcessing
@SpringBootApplication
public class SpringBatchApplication
{
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Step step1()
{
// step์ด FINISHED ์ํ๋ก ์๋ฃ๋์ด์ผ ์ฑ๊ณต์ผ๋ก ์ธ์
return this.stepBuilderFactory.get("step1")
.tasklet(
// stepContribution : ์์ง ์ปค๋ฐ๋์ง ์์ ํ์ฌ ํธ๋์ญ์
์ ๋ํ ์ ๋ณด
// chunckContext : ์คํ ์์ ์ job ์ํ ์ ๊ณต
((stepContribution, chunkContext) -> {
System.out.println("Hello World!");
return RepeatStatus.FINISHED;
})
).build();
}
// ์ดํ๋ฆฌ์ผ์ด์
๊ฐ๋ ์ job์ผ๋ก ์ธ์๋๋ bean๋ค ์๋ ์คํ
@Bean
public Job job()
{
// step1์ ํฌํจํ๋ job ์์ฑ
return this.jobBuilderFactory.get("job")
.start(step1())
.build();
}
public static void main(String[] args)
{
SpringApplication.run(SpringBatchApplication.class, args);
}
}
job ์คํ -> step1 ์คํ -> hello world ์ถ๋ ฅ -> job ์๋ฃ(status : COMPLETED)
COMPLETED๋ก job์ด ์๋ฃ๋์์ผ๋ฏ๋ก ๋๊ฐ์ job๊ณผ parameter๋ก ์คํํ ๊ฒฝ์ฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
RDBMS์๋ batch์ ๊ด๋ จ๋ ํ
์ด๋ธ๋ค์ด ์์ฑ
ํ
์ด๋ธ์ ๋ด์ฉ์๋ ์คํํ๋ job๊ณผ step์ ๋ํ ๋ด์ฉ๋ค์ด ์ ์ฅ๋๋ค.
Job์ parameter๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
@Bean
public Tasklet helloWorldTasklet()
{
return ((stepContribution, chunkContext) -> {
// chunckContext๋ฅผ ํตํด ์คํ ์์ ์ job ์ํ์์ parameter ๊ฐ์ ธ์ค๊ธฐ
// map<String, Object>์ด๋ฏ๋ก type casting ํ์
String name = (String) chunkContext.getStepContext()
.getJobParameters()
.get("name");
System.out.printf("Hello World! %s%n", name);
return RepeatStatus.FINISHED;
});
}
@StepScope
@Bean
public Tasklet helloWorldTasklet(@Value("#{jobParameters['name']}") String name)
{
return ((stepContribution, chunkContext) -> {
System.out.printf("Hello World! %s%n", name);
return RepeatStatus.FINISHED;
});
}
Late Binding ๋ฐฉ์์๋ Step์ด๋ Job Scope๋ฅผ ๊ฐ์ ธ์ผ ํ๋ค.
StepScope
,JobScope
๋?
bean ์์ฑ ์์ ์ Step, Job ์์ ์ผ๋ก ๋ฆ์ถ๋ค. ๋ง์ฝ ์๋ฒ๋ฅผ ์ฌ๋ฆด ๋ tasklet์ ํ๋ฒ์ ๋ชจ๋ ์์ฑ์ํค๋ฉด ํ tasklet์ ๋ํด์ ๋์์ ์ฌ๋ฌ step๋ค์ด ์คํ๋๋ฉด์ ์นจ๋ฒ๋นํ ์ ์๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์ ์ฌ์ฉํ๋ค.
@Bean
public CompositeJobParametersValidator validator()
{
CompositeJobParametersValidator validator = new CompositeJobParametersValidator();
DefaultJobParametersValidator defaultJobParametersValidator = new DefaultJobParametersValidator(
new String[]{"fileName"},
new String[]{"name"}
);
defaultJobParametersValidator.afterPropertiesSet();
// ์ฌ๋ฌ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฌ๊ฐ์ validator๋ฅผ CompositeJobParametersValidator์ ์ถ๊ฐ
validator.setValidators(
Arrays.asList(
// ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ParamerterValidator
new ParameterValidator(),
// ์์์ ๋ง๋ defaultJobParametersValidator
defaultJobParametersValidator
)
);
return validator;
}
incrementer ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
@Bean
public Job job()
{
return this.jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.start(step1())
.build();
}
job repository์์ BATCH_JOB_EXECUTION_PARAMS์ ์ดํด๋ณด๋ฉด run.id๊ฐ ์ฆ๊ฐํ๊ณ ์๋ ๊ฑธ ํ์ธํ ์ ์๋ค!
๊ทธ ์ธ์๋ DailyJobTimestamper๋ฅผ ์ด์ฉํด์ ํ์ฌ ์๊ฐ์ ์ด์ฉํด์ ์ก์ ๋ฐ๋ณต ์คํํ ์๋ ์๋ค.
ํ์ฌ ์ฐ๋ฆฌํ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ incrementer๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ํ์ฌ ์๊ฐ์ผ๋ก ์ก ํ๋ผ๋ฏธํฐ๋ฅผ ์์ฑํด์ ๋๊ฒจ์ฃผ๊ณ ์์๋ค.
public void runJob(Job targetJob) throws JobParametersInvalidException,
JobExecutionAlreadyRunningException,
JobRestartException, JobInstanceAlreadyCompleteException
{
try
{
// ํ์ฌ ์๊ฐ์ parameter์ ์ถ๊ฐํจ์ผ๋ก์จ ์ฌ๋ฌ๋ฒ ์คํ์ด ๊ฐ๋ฅํ๋๋ก ํจ
JobParameters params = new JobParametersBuilder()
.addString("JobID", String.valueOf(System.currentTimeMillis()))
.toJobParameters();
jobLauncher.run(targetJob, params);
}
catch (Exception e)
{
log.info("{} job ์ค์ผ์ฅด ๋ฑ๋ก์ ์คํจํ์ต๋๋ค.", targetJob.getName());
throw e;
}
}
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.printf((START_MESSAGE) + "%n", jobExecution.getJobInstance().getJobName());
}
@Override
public void afterJob(JobExecution jobExecution)
{
System.out.printf(
(END_MESSAGE) + "%n",
jobExecution.getJobInstance().getJobName(),
jobExecution.getStatus()
);
}
}
@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.printf((START_MESSAGE) + "%n", jobExecution.getJobInstance().getJobName());
}
@AfterJob
public void afterJob(JobExecution jobExecution)
{
System.out.printf(
(END_MESSAGE) + "%n",
jobExecution.getJobInstance().getJobName(),
jobExecution.getStatus()
);
}
}
ExecutionContext๋ ์คํ๋ง ๋ฐฐ์น์์ job๊ณผ step์ ๋ํ ์ํ๋ฅผ ์ ์ฅํ๊ณ ์๋ค. ๋ํ ํ๋์ job๊ณผ step์ ๋ํด์ ๊ฐ๊ฐ์ ExecutionContext๋ฅผ ๊ฐ์ง๋ค.
public class HelloWorld implements Tasklet
{
public static final String HELLO_WORLD = "Hello World, %s";
@Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception
{
String name = (String) chunkContext.getStepContext()
.getJobParameters()
.get("name");
// ExecutionContext ์ ๊ทผ
ExecutionContext jobContext = chunkContext.getStepContext()
.getStepExecution()
.getJobExecution()
.getExecutionContext();
// ExecutionContext ์ user.name ์ถ๊ฐ
jobContext.put("user.name", name);
System.out.printf(HELLO_WORLD, name);
return RepeatStatus.FINISHED;
}
}
// step์ ์ถ๊ฐํ listener
@Bean
public StepExecutionListener promotionListener()
{
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
// listener์์ name ํค ์น๊ฒฉ
listener.setKeys(new String[] {"name"});
return listener;
}
step์ job์ ๊ตฌ์ฑ ์์๋ก ์์ฒด์ ์ธ ์ ๋ ฅ, ์ถ๋ ฅ, ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ง๋ค. state machine์ผ๋ก ์๊ฐํ๋ฉด ๋๋ค. ํธ๋์ญ์ ์ step ๋ด์์ ์ด๋ค์ง๋ค.
Tasklet ๊ธฐ๋ฐ Step์ ๋ง๋๋๋ฐ๋ ๋๊ฐ์ง ์ ํ์ด ์๋ค.
1. MethodInvokingTaskletAdapter
์ฌ์ฉ์๊ฐ ์์ฑํ ์ฝ๋๋ฅผ Tasklet Step ์ฒ๋ผ ์คํํ๋ ๋ฐฉ์
์ผ๋ฐ POJO๋ฅผ Step์ผ๋ก ํ์ฉ ๊ฐ๋ฅ
2. Tasklet ์ธํฐํ์ด์ค ๊ตฌํ
์ง๊ธ๊น์ง ์์์ ์ฌ์ฉํ ๋ฐฉ์์ด๋ค. ์ด ๋ Tasklet ์ธํฐํ์ด์ค๋ ํจ์ํ ์ธํฐํ์ด์ค์ด๋ฏ๋ก ๋๋ค์์ผ๋ก ๊ตฌํํ ์๋ ์๋ค.
Tasklet ๊ตฌํ์ฒด์ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋๋ฉด RepeatStatus
๊ฐ์ฒด๋ฅผ ๋ฆฌํดํด์ผ ํ๋ค. (์ด๊ฒ ๋ฐ๋ก state machine์ผ๋ก ์๊ฐ๋๋ ์ง์ !)
public enum RepeatStatus {
CONTINUABLE(true), // ์ด๋ค ์กฐ๊ฑด์ด ์ถฉ์กฑ๋ ๋๊น์ง ๋ฐ๋ณต ์คํ
FINISHED(false); // ์ฑ๊ณต ์ฌ๋ถ ๊ด๊ณ ์์ด tasklet ์ฒ๋ฆฌ ์๋ฃ ํ ๋ค์ ์ฒ๋ฆฌ
}
1. CallableTaskletAdapter
Tasklet์ด Step์ด ์คํ๋๋ ์ค๋ ๋์ ๋ณ๊ฐ๋ก ์๋ก์ด ์ค๋ ๋์์ ์คํ๋๋ค. ํ์ง๋ง ๋ณ๋ ฌ๋ก ์คํ๋์ง๋ ์๋๋ค.
Callable ๊ฐ์ฒด๊ฐ RepeatStatus๋ฅผ ๋ฆฌํดํ๊ธฐ ์ ๊น์ง๋ ํด๋น Step์ด ์๋ฃ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋์ง ์๋๋ค. ๊ทธ๋์ ๋ค์ Step์ด ์คํ๋ ์ ์๋ค.
@Bean
public Callable<RepeatStatus> callableObject()
{
return () -> {
System.out.println("This was executed in another thread");
// Callable ๊ฐ์ฒด๊ฐ RepeatStatus๋ฅผ ๋ฆฌํดํด์ผ tasklet ์๋ฃ๋ก ๊ฐ์ฃผ
return RepeatStatus.FINISHED;
};
}
@Bean
public CallableTaskletAdapter tasklet()
{
CallableTaskletAdapter callableTaskletAdapter = new CallableTaskletAdapter();
callableTaskletAdapter.setCallable(callableObject());
return callableTaskletAdapter;
}
2. MethodInvokingTaskletAdapter
MethodInvokingTaskletAdapter
๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ์กด์ ๋ค๋ฅธ ํด๋์ค์ ๋ฉ์๋๋ฅผ Step ๋ด์ Tasklet์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
@StepScope
@Bean
public MethodInvokingTaskletAdapter methodInvokingTasklet(@Value("#{jobParameters['message']}") String message)
{
MethodInvokingTaskletAdapter methodInvokingTaskletAdapter = new MethodInvokingTaskletAdapter();
methodInvokingTaskletAdapter.setTargetObject(service());
methodInvokingTaskletAdapter.setTargetMethod("serviceMethod");
methodInvokingTaskletAdapter.setArguments(new String[] {message});
return methodInvokingTaskletAdapter;
}
// ๋ด๊ฐ ์์ฑํ ํด๋์ค(์ผ๋ฐ POJO)
@Bean
public CustomService service()
{
// ์ด ๋ CustomService ํด๋์ค์ serviceMethod๊ฐ message ํ๋ผ๋ฏธํฐ๋ฅผ ์ด์ฉํ์ง ์์ผ๋ฉด ์๋ฌ ๋ฐ์
return new CustomService();
}
3. SystemCommandTasklet
์์คํ
๋ช
๋ น์ ์ฌ์ฉํ ๋ ์ฌ์ฉํ๋ฉฐ ํด๋น ๋ช
๋ น์ ๋น๋๊ธฐ๋ก ์คํ๋๋ค.
@Bean
public SystemCommandTasklet systemCommandTasklet()
{
SystemCommandTasklet systemCommandTasklet = new SystemCommandTasklet();
systemCommandTasklet.setCommand("rm -rf /tmp.txt");
systemCommandTasklet.setTimeout(5000);
// ์์คํ
๋ช
๋ น์ด ๋น์ ์ ์ข
๋ฃ๋ ๋ ์ค๋ ๋๋ฅผ ๊ฐ์ ์ข
๋ฃํ ์ง ์ฌ๋ถ ์ค์
systemCommandTasklet.setInterruptOnCancel(true);
return systemCommandTasklet;
}
์ถ๊ฐ๋ก ๋ค์ํ ๊ธฐ๋ฅ์ ์๊ฐํ๋ค.
@Bean
public SystemCommandTasklet systemCommandTasklet()
{
SystemCommandTasklet systemCommandTasklet = new SystemCommandTasklet();
systemCommandTasklet.setCommand("rm -rf /tmp.txt");
systemCommandTasklet.setTimeout(5000);
systemCommandTasklet.setInterruptOnCancel(true);
// working directory ์ค์
systemCommandTasklet.setWorkingDirectory("/Users/we/spring-batch");
// ExitCode ์ค์
systemCommandTasklet.setSystemProcessExitCodeMapper(touchCodeMapper());
systemCommandTasklet.setTerminationCheckInterval(5000);
// Lock์ด ๊ฑธ๋ฆฌ์ง ์๋๋ก ๋น๋๊ธฐ executor ์ค์
systemCommandTasklet.setTaskExecutor(new SimpleAsyncTaskExecutor());
// ํ๊ฒฝ ๋ณ์ ์ค์
systemCommandTasklet.setEnvironmentParams(new String[] {
"JAVA_HOME=/java",
"BATCH_HOME=/Users/batch"
});
return systemCommandTasklet;
}
@Bean
public SimpleSystemProcessExitCodeMapper touchCodeMapper()
{
// ์ข
๋ฃ ์ํ์ ๋ฐ๋ผ ExitStatus.COMPLETED, ExitStatus.FAILED ๋ฆฌํด
return new SimpleSystemProcessExitCodeMapper();
}
@Bean
public Step step1()
{
return this.stepBuilderFactory.get("step1")
// chunk size ๋ 10
// 10๊ฐ์ ๋ ์ฝ๋๋ฅผ ์ฝ๊ณ ์ฒ๋ฆฌํ ๋๊น์ง ์ฐ๊ธฐ ์์
ํ์ง ์์
.<String, String>chunk(10)
.reader(itemReader(null))
.writer(itemWriter(null))
.build();
}
@Bean
@StepScope
public FlatFileItemReader<String> itemReader(@Value("#{jobParameters['inputFile']}") Resource inputFile)
{
return new FlatFileItemReaderBuilder<String>()
.name("itemReader")
.resource(inputFile)
.lineMapper(new PassThroughLineMapper())
.build();
}
@Bean
@StepScope
public FlatFileItemWriter<String> itemWriter(@Value("#{jobParameters['outputFile']}") Resource outputFile)
{
return new FlatFileItemWriterBuilder<String>()
.name("itemWriter")
.resource(outputFile)
.lineAggregator(new PassThroughLineAggregator<>())
.build();
}
์ด ๋๋ Chunk ํฌ๊ธฐ๋ฅผ ํ๋ ์ฝ๋ฉ์ ํ ์ ์๋ค. ์ด ๋ ์ฌ์ฉํ๋ ๊ฒ SimpleCompletionPolicy
, TimeoutTerminationPolicy
์ด๋ค.
@Bean
public Step chunkStep()
{
return this.stepBuilderFactory.get("chunkStep")
.<String, String>chunk(completionPolicy())
.reader(itemReader())
.writer(itemWriter())
.build();
}
@Bean
public ListItemReader<String> itemReader()
{
List<String> items = new ArrayList<>(10000);
for (int i = 0; i < 10000; i++)
{
items.add(UUID.randomUUID().toString());
}
return new ListItemReader<>(items);
}
@Bean
public ItemWriter<String> itemWriter()
{
return items -> {
for (String item: items)
{
System.out.println(">> current item = " + item);
}
};
}
@Bean
public CompletionPolicy completionPolicy()
{
CompositeCompletionPolicy policy = new CompositeCompletionPolicy();
policy.setPolicies(
new CompletionPolicy[]
{
// ์ฒญํฌ ์ฒ๋ฆฌ ์๊ฐ์ด ๋์ ๊ฒฝ์ฐ ์์ ํ๊ฒ ๋น ์ ธ๋์ค๊ฒ ํด์ค
// timeout ๋ฐ์์ ํด๋น ์ฒญํฌ ์๋ฃ๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ
new TimeoutTerminationPolicy(3),
// 100๊ฐ์ ๋ ์ฝ๋์ฉ ๋์ด์ ์์
new SimpleCompletionPolicy(100)
}
);
return policy;
}
TimeoutTerminationPolicy๋ก ์ธํด ์ปค๋ฐ ๊ฐ์๊ฐ 10000/100 = 100 ๊ฐ๊ฐ ์๋ 105๊ฐ ์ธ ๊ฒ์ ํ์ธํ ์ ์๋ค.
Job Listener์ ๋น์ทํ๊ฒ AfterStep, BeforeStep, AfterChunk, BeforeChunk๋ฅผ ์ด์ฉํด์ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ค.
on
๋ฉ์๋๋ฅผ ์ด์ฉํด์ step์ ExitStatus
์ ๋ฐ๋ผ ์ด๋ค ์ผ์ ์ํํ ์ง ๊ฒฐ์ ํ ์ ์๋ค.
@Bean
public Job job()
{
return this.jobBuilderFactory.get("conditionalJob")
.start(firstStep())
// ์คํจ์ failureStep
.on("FAILED").to(failureStep())
// FAILED ์ธ์ ๊ฒฝ์ฐ์ successStep
.from(firstStep()).on("*").to(sucessStep())
.end()
.build();
}
*
: 0๊ฐ ์ด์์ ๋ฌธ์์ ์ผ์น
?
: 1๊ฐ์ ๋ฌธ์์ ์ผ์น
decider๋ฅผ ๋ฐ๋ก ๋ฌ์ ์ด์ ๋ฐ๋ผ ๋ค์ step์ ๊ฒฐ์ ํ๊ฒ๋ ํ ์๋ ์๋ค.
public class RandomDecider implements JobExecutionDecider
{
private Random random = new Random();
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution)
{
if (random.nextBoolean())
return new FlowExecutionStatus(FlowExecutionStatus.COMPLETED.getName());
else
return new FlowExecutionStatus(FlowExecutionStatus.FAILED.getName());
}
}
@Bean
public Job job()
{
return this.jobBuilderFactory.get("conditionalJob")
.start(firstStep())
.on("FAILED").end() // ์คํ
์ด ๋ฆฌํดํ ์ํ์ ์๊ด์์ด COMPLETED ์ ์ฅ
.on("FAILED").fail() // ์คํจํ๋ฉด FAILED. ๋ค์ ์คํ ๊ฐ๋ฅ
.on("FAILED").stopAndRestart(sucessStep()) // FAILED๋ก ์ข
๋ฃ๋์ง๋ง ์ฌ์คํ ์ successStep๋ถํฐ ์คํ
.from(firstStep()).on("*").to(successStep())
.build();
}
Step ์ ์ ์๋ฅผ ์ถ์ถํด์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ก ๋ง๋ค ์ ์๋ค. ์ฌ๊ธฐ์ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
1. ์คํ ์ ์ํ์ค๋ฅผ ๋ ์์ ์ธ flow๋ก ๋ง๋๋ ๋ฐฉ๋ฒ
@Bean
Public Flow preprocessingFlow()
{
return new FlowBuilder<Flow>("preProcessingFlow").start(loadFileStep())
.next(loadCustomerStep())
.next(updateStartStep())
.build();
}
flow builder๋ฅผ ์ด์ฉํด์ flow๋ฅผ ์์ฑํ์ฌ jobBuilder์๊ฒ ๋๊ธด๋ค.
2. flow step์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
์์ ๋น์ทํ์ง๋ง flow๋ฅผ step์ wrappingํ๊ณ ํด๋น step์ job builder๋ก ์ ๋ฌํ๋ค.
@Bean
public Step initializeBatch()
{
return this.stepBuilderFactory.get("initializeBatch")
.flow(preprocessingFlow())
.build();
}
1๋ฒ ๋ฐฉ๋ฒ๊ณผ ๋ฌด์จ ์ฐจ์ด๊ฐ ์์๊น? ๋ฐ๋ก JobRepository์์ ์ฐจ์ด๊ฐ ๋๋ค. 1๋ฒ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ฉด job์ step์ ๊ตฌ์ฑํ๋ ๊ฒ๊ณผ ๊ฒฐ๊ณผ์ ์ผ๋ก ๋์ผํ๋ค. ํ์ง๋ง 2๋ฒ ๋ฐฉ๋ฒ์ flow๊ฐ ๋ด๊ธด step์ ํ๋์ step์ฒ๋ผ ๊ธฐ๋กํ๋ค. ์ด๋ฅผ ํตํด์ ๊ฐ๋ณ step์ ์ง๊ณํ์ง ์๊ณ ๋ flow์ ์ํฅ์ ์ ์ฒด์ ์ผ๋ก ๋ชจ๋ํฐ๋ง ๊ฐ๋ฅํ๋ค.
3. job ๋ด์์ ๋ค๋ฅธ job์ ํธ์ถํ๋ ๋ฐฉ๋ฒ
job์ step์ผ๋ก wrappingํ๊ณ ํด๋น step์ ๋ ๋ค๋ฅธ job์์ ํธ์ถ๋๋ค.
@Bean
public Step initializeBatch()
{
return this.stepBuilderFactory.get("initializeBatch")
.job(preprocessingJob())
.parameterExtractor(new DefaultJobParametersExtractor())
.build();
}
ํ์ง๋ง ์์ ๋ฐฉ๋ฒ์ job๊ณผ์ ์์กด์ฑ์ ๋์ฌ์ ๊ฐ๋ณ job์ ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๋ ๋ฌธ์ ์ ์ด ์๊ธธ ์ ์์ผ๋ฏ๋ก ํผํ๋ ๊ฒ์ด ์ข๋ค.