Batch Step
Step
์ ์ค์ Batch ์์
์ ์ํํ๋ ์ญํ ์ ํ๋ค. ์ฆ, ์ค์ ๋น์ง๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๊ธฐ๋ฅ์ Step
์ ๊ตฌํ๋์ด์๋ค.
์ด์ฒ๋ผ Step
์์๋ Batch์์ ์ฒ๋ฆฌํ๊ณ ์ ํ๋ ๊ธฐ๋ฅ๊ณผ ์ค์ ์ ๋ชจ๋ ํฌํจํ๋ ์ฅ์๋ผ ์๊ฐํ๋ฉด๋๋ค.
Next
.next()
๋ ์์ฐจ์ ์ผ๋ก Step
๋ค์ ์ฐ๊ฒฐ์ํฌ๋ ์ฌ์ฉ ํ๋ค.
Copy @ Slf4j
@ Configuration
@ RequiredArgsConstructor
public class StepNextJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@ Bean
public Job stepNextJob (){
return jobBuilderFactory . get ( "stepNextJob" )
. start ( step1() )
. next ( step2() )
. next ( step3() )
. build ();
}
@ Bean
public Step step1 (){
return stepBuilderFactory . get ( "step1" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> this is step1" );
return RepeatStatus . FINISHED ;
}) . build ();
}
@ Bean
public Step step2 (){
return stepBuilderFactory . get ( "step2" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> this is step2" );
return RepeatStatus . FINISHED ;
}) . build ();
}
@ Bean
public Step step3 (){
return stepBuilderFactory . get ( "step3" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> this is step3" );
return RepeatStatus . FINISHED ;
}) . build ();
}
}
Flow
Next๋ ์์ฐจ์ ์ผ๋ก Step์ ์์๋ฅผ ์ ์ดํ์ง๋ง, ์์ Step์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ฉด, ๋ค์ Step๋ค์ ์คํ๋์ง ๋ชปํ๊ฒ ๋๋ค. ํ์ง๋ง ์ํฉ์ ๋ฐ๋ผ ์ ์ ์ํ์ธ ๊ฒฝ์ฐ์ Step B๋ก, ์ค๋ฅ๊ฐ ๋ฐ์ํ์ ๋ Step C๋ก ์ํํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
์ด๋ฌํ ๊ฒฝ์ฐ์ ๋๋นํด ์กฐ๊ฑด๋ณ๋ก Step์ ์ฌ์ฉํ ์ ์๋ค.
Copy @ Bean
public Job stepNextConditionalJob() {
return jobBuilderFactory . get ( "tepNextConditionalJob" )
. start ( conditionalJobStep1() )
. on ( "FAILED" ) // step1 FAILED ์ธ ๊ฒฝ์ฐ
. to ( conditionalJobStep3() ) // step3์ผ๋ก ์ด๋
. on ( "*" ) // step3์ ๊ฒฐ๊ณผ์ ์๊ด์์ด
. end () // step3์ผ๋ก ์ด๋์ Flow ์ข
๋ฃ
. from ( conditionalJobStep1() ) // step1์์๋ถํฐ
. on ( "*" ) // FAILED๊ฐ ์๋ ๋ชจ๋ ๊ฒฝ์ฐ์
. to ( conditionalJobStep2() ) // step2๋ก ์ด๋
. next ( conditionalJobStep3() ) // step2๊ฐ ์ ์ ์ข
๋ฃ์ step3์ผ๋ก ์ด๋
. on ( "*" ) // step3์ ๊ฒฐ๊ณผ์ ์๊ด์์ด
. end () // step3์ผ๋ก ์ด๋์ flow ์ข
๋ฃ
. end () // job ์ข
. build ();
}
.on()
ExitStatus
๋ฅผ ์ง์ ํ๋ค.
*
์ ๊ฒฝ์ฐ ๋ชจ๋ ExitStatus
๊ฐ ์ง์ ๋๋ค.
.to()
๋ค์์ผ๋ก ์ด๋ํ Step์ ์ง์ ํ๋ค.
.from()
์ํ๊ฐ์ ๋ณด๊ณ ์ผ์นํ๋ ์ํ๋ผ๋ฉด to()
์ ํฌํจ๋ step
์ ํธ์ถํ๋ฉฐ, ์ผ์ข
์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ญํ ์ ํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
์ถ๊ฐ๋ก ์ด๋ฒคํธ๋ฅผ ์บ์นํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉ ํ๋ค.
.end()
FlowBuilder๋ฅผ ๋ฐํ
.on("*")
๋ค์ ์๋ end()
FlowBuilder๋ฅผ ๋ฐํํ๋ end()
์ ๊ฒฝ์ฐ ๊ณ์ํด์ from()
์ ์ด์ด๊ฐ ์ ์๋ค.
FlowBuilder ์ข
๋ฃ
build()
์์ ์๋ end()
Flow ์ํ ํด๋ณด๊ธฐ
Copy @ Bean
public Step conditionalJobStep1() {
return stepBuilderFactory . get ( "step1" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> this is step1" );
// ExitStatus.FAILED๋ก ์ง์
// ํด๋น Status ๋ก flow๊ฐ ์งํ๋๋ค.
stepContribution . setExitStatus ( ExitStatus . FAILED );
return RepeatStatus . FINISHED ;
}) . build ();
}
Copy Job: [FlowJob: [name=stepNextConditionalJob]] launched with the following parameters: [{version=3}]
o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
.p.j.StepNextConditionalJobConfiguration : >>> this is step1
o.s.batch.core.step.AbstractStep : Step: [step1] executed in 75ms
o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
.p.j.StepNextConditionalJobConfiguration : >>> this is step3
o.s.batch.core.step.AbstractStep : Step: [step3] executed in 19ms
o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=stepNextConditionalJob]] completed with the following parameters: [{version=3}] and the following status: [COMPLETED] in 252ms
ExitStatus.FAILED
๋ก ์ํํ๋ ๊ฒฝ์ฐ step1 -> step3์ด ์ํ๋๊ณ ์ข
๋ฃ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Copy @ Bean
public Step conditionalJobStep1() {
return stepBuilderFactory . get ( "step1" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> this is step1" );
// stepContribution.setExitStatus(ExitStatus.FAILED);
return RepeatStatus . FINISHED ;
}) . build ();
}
Copy o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=stepNextConditionalJob]] launched with the following parameters: [{version=4}]
o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
.p.j.StepNextConditionalJobConfiguration : >>> this is step1
o.s.batch.core.step.AbstractStep : Step: [step1] executed in 33ms
o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
.p.j.StepNextConditionalJobConfiguration : >>> this is step2
o.s.batch.core.step.AbstractStep : Step: [step2] executed in 28ms
o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
.p.j.StepNextConditionalJobConfiguration : >>> this is step3
o.s.batch.core.step.AbstractStep : Step: [step3] executed in 23ms
o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=stepNextConditionalJob]] completed with the following parameters: [{version=4}] and the following status: [COMPLETED] in 309ms
ExitStatus
์ค์ ํ ๋ถ๋ถ์ ์ฃผ์ ์ฒ๋ฆฌํ ํ ์ํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด step1 -> step2 -> step3 ์์๋๋ก ์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
ํ์ง๋ง ์ด๋ ๊ฒ Step์ ์ํํ๊ฒ ๋๋ฉด ๋ค์ ๋๊ฐ์ง ๋ฌธ์ ๊ฐ ์๋ค.
Step์ด ๋ด๋นํ๋ ์ญํ ์ด 2๊ฐ ์ด์์ด๋ค. ์ค์ ํด๋น Step์ด ์ฒ๋ฆฌํด์ผํ ๋ก์ง์ธ์๋ ๋ถ๊ธฐ์ฒ๋ฆฌ๋ฅผ ์ํ ExitStatus
์กฐ์์ด ํ์ํ๋ค.
๋ค์ํ ๋ถ๊ธฐ ๋ก์ง ์ฒ๋ฆฌ์ ์ด๋ ค์์ด ์๋ค. ExitStatus
๋ฅผ ์ปค์คํ
ํ๊ฒ ๊ณ ์น๋ ค๋ฉด Listener
๋ฅผ ์์ฑํ๊ณ , Job Flow์ ๋ฑ๋กํ๋ ๋ฑ ๋ฒ๊ฑฐ๋ก์์ด ์กด์ฌํ๋ค.
Decide
Spring Batch์์ JobExecutionDecider
๋ Step๋ค์ Flow์์์ ๋ถ๊ธฐ๋ง ๋ด๋นํ๋ ํ์
์ด๋ค.
Copy @ Slf4j
@ Configuration
@ RequiredArgsConstructor
public class DeciderJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@ Bean
public Job deciderJob (){
return jobBuilderFactory . get ( "deciderJob" )
. start ( startStep() )
. next ( decider() ) // ํ์ || ์ง์ ๊ตฌ๋ถ
. from ( decider() ) // decider์ ์ํ๊ฐ
. on ( "ODD" ) // ํ์๋ผ๋ฉด
. to ( oddStep() ) // oddStep ์คํ
. from ( decider() ) // decider์ ์ํ๊ฐ
. on ( "EVEN" ) // ์ง์๋ผ๋ฉด
. to ( evenStep() ) // evenStep() ์คํ
. end () // builder ์ข
๋ฃ
. build ();
}
@ Bean
public Step startStep (){
return stepBuilderFactory . get ( "startStep" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> Start step" );
return RepeatStatus . FINISHED ;
}) . build ();
}
@ Bean
public Step oddStep (){
return stepBuilderFactory . get ( "oddStep" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> ํ์" );
return RepeatStatus . FINISHED ;
}) . build ();
}
@ Bean
public Step evenStep (){
return stepBuilderFactory . get ( "evenStep" )
. tasklet ((stepContribution , chunkContext) -> {
log . info ( ">>> ์ง์ " );
return RepeatStatus . FINISHED ;
}) . build ();
}
@ Bean
public JobExecutionDecider decider (){
return new OddDecider() ;
}
public static class OddDecider implements JobExecutionDecider {
@ Override
public FlowExecutionStatus decide ( JobExecution jobExecution , StepExecution stepExecution) {
Random rand = new Random() ;
int randomNum = rand . nextInt ( 50 ) + 1 ;
log . info ( "random number : {}" , randomNum);
if (randomNum % 2 == 0 ){
// Step์ผ๋ก ์ฒ๋ฆฌ๊ฐ ํ๋ ๊ฒ์ด ์๋๋ฏ๋ก, FlowExecutionStatus๋ก ์ํ ๊ด๋ฆฌ
return new FlowExecutionStatus( "EVEN" ) ;
} else {
return new FlowExecutionStatus( "ODD" ) ;
}
}
}
}
Copy o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=deciderJob]] launched with the following parameters: [{version=1}]
o.s.batch.core.job.SimpleStepHandler : Executing step: [startStep]
s.b.p.jobs.DeciderJobConfiguration : >>> Start step
o.s.batch.core.step.AbstractStep : Step: [startStep] executed in 45ms
s.b.p.jobs.DeciderJobConfiguration : random number : 13
o.s.batch.core.job.SimpleStepHandler : Executing step: [oddStep]
s.b.p.jobs.DeciderJobConfiguration : >>> ํ์
o.s.batch.core.step.AbstractStep : Step: [oddStep] executed in 18ms
o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [version=3]
o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=deciderJob]] launched with the following parameters: [{version=3}]
o.s.batch.core.job.SimpleStepHandler : Executing step: [startStep]
s.b.p.jobs.DeciderJobConfiguration : >>> Start step
o.s.batch.core.step.AbstractStep : Step: [startStep] executed in 31ms
s.b.p.jobs.DeciderJobConfiguration : random number : 50
o.s.batch.core.job.SimpleStepHandler : Executing step: [evenStep]
s.b.p.jobs.DeciderJobConfiguration : >>> ์ง์
๊ณ์ํด์ ์ํํ๋ฉด, ํ์์ ์ง์๊ฐ ๋ฒ๊ฐ์๊ฐ๋ฉด์ ์ํ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
BatchStatus vs ExitStatus
BatchStatus
Copy public enum BatchStatus {
COMPLETED ,
STARTING ,
STARTED ,
STOPPING ,
STOPPED ,
FAILED ,
ABANDONED ,
UNKNOWN;
...
Job
ํน์ Step
์ ์คํ ๊ฒฐ๊ณผ๋ฅผ Spring์ ๊ธฐ๋กํ ๋ ์ฌ์ฉ ํ๋ Enum์ด๋ค.
ExitStatus
Copy public class ExitStatus implements Serializable , Comparable < ExitStatus > {
public static final ExitStatus UNKNOWN = new ExitStatus( "UNKNOWN" ) ;
public static final ExitStatus EXECUTING = new ExitStatus( "EXECUTING" ) ;
public static final ExitStatus COMPLETED = new ExitStatus( "COMPLETED" ) ;
public static final ExitStatus NOOP = new ExitStatus( "NOOP" ) ;
public static final ExitStatus FAILED = new ExitStatus( "FAILED" ) ;
public static final ExitStatus STOPPED = new ExitStatus( "STOPPED" ) ;
...
}
Step
์ ์คํ ํ ์ํ ๋ฅผ ๋งํ๋ฉฐ, ExitStatus
๋ Enum์ด ์๋๋ค.
Custom ExitStatus
๋ณธ์ธ๋ง์ custom ExitStatus๊ฐ ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋ค.
Copy . start ( step1() )
. on ( "FAILED" )
. end ()
. from ( step1() )
. on ( "COMPLETED WITH SKIPS" )
. to ( errorPrint1() )
. end ()
. from ( step1() )
. on ( "*" )
. to ( step2() )
. end ()
step1 FAILED -> job ์คํจ
step1 ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์ด, COMPLETED WITH SKIPS
๋ก ์ข
๋ฃ
step1 ์ฑ๊ณต -> step2 ์ฑ๊ณต
Copy public class SkipCheckingListener extends StepExecutionListenerSupport {
public ExitStatus afterStep ( StepExecution stepExecution) {
String exitCode = stepExecution . getExitStatus () . getExitCode ();
// step์ด ์ ์ํ๋๊ณ , skip ํ์๊ฐ 0๋ณด๋ค ํฐ ๊ฒฝ์ฐ์ COMPLETED WITH SKIPS return
if ( ! exitCode . equals ( ExitStatus . FAILED . getExitCode ()) &&
stepExecution . getSkipCount () > 0 ) {
return new ExitStatus( "COMPLETED WITH SKIPS" ) ;
}
else {
return null ;
}
}
}
์ฐธ๊ณ