Skip to content

Latest commit

ย 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
ย 
ย 
ย 
ย 
ย 
ย 

README.md

4. Step์˜ ๊ธฐ๋ณธ ๊ตฌ์„ฑ๊ณผ ์ƒ์„ฑ์›๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰์›๋ฆฌ - StepBuilder & Step

์ง€๋‚œ ๊ธ€๋“ค์—์„œ Job์˜ ๊ฐœ๋…๊ณผ ๊ธฐ๋ณธ ๊ตฌ์„ฑ, Job์˜ ์ƒ์„ฑ์›๋ฆฌ์™€ ๋™์ž‘์›๋ฆฌ์— ๋Œ€ํ•ด ํŒŒ์•…ํ–ˆ๋‹ค.

๊ฐ™์€ ์ˆœ์„œ๋กœ, ์ด๋ฒˆ์—” Step์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ ์ž ํ•œ๋‹ค. Step์ด ๋ฌด์—‡์ด๊ณ , ๊ด€๋ จ๋œ ๊ฐœ๋…์—๋Š” ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณธ๋‹ค.
๋” ๋‚˜์•„๊ฐ€ Step์˜ ์ข…๋ฅ˜์™€ ์ƒ์„ฑ์›๋ฆฌ, ๋™์ž‘์›๋ฆฌ์— ๋Œ€ํ•ด ๊นŠ๊ฒŒ ํŒŒ์•…ํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

4.1 Step์ด๋ž€?

Job์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ์„ค๋ช…์„ ํ•˜๋ฉด์„œ, Job์€ "์ „์ฒด ๋ฐฐ์น˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์บก์Аํ™”ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ"๋ผ๊ณ  ํ‘œํ˜„ํ–ˆ์—ˆ๋‹ค.
์ด์™€ ๋น„์Šทํ•˜๊ฒŒ Step์€ "Job์˜ ๋…๋ฆฝ์ ์ธ ์ˆœ์ฐจ์  ๋‹จ๊ณ„๋ฅผ ์บก์Аํ™”ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ"๋ผ๊ณ  ๊ณต์‹๋ฌธ์„œ์—์„œ ์ •์˜ํ•œ๋‹ค. ์‚ฌ์‹ค, ๊ฐœ์ธ์ ์œผ๋กœ๋Š” Step์„ "Job์˜ ์„ธ๋ถ€ ์ž‘์—… ๋‹จ์œ„๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ์„ธ๋ถ€์ ์œผ๋กœ ๋‚˜๋‰œ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ๋…ผ๋ฆฌ์  ๋‹จ๊ณ„"๋ผ๊ณ  ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•œ๋‹ค.
์ฆ‰, Job์€ ํ•˜๋‚˜ ์ด์ƒ์˜ Step์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, ์ด Step์—๋Š” ๋ฐฐ์น˜์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ฐ€์ง„๋‹ค.

์œ„ ๋‚ด์šฉ์„ ํ† ๋Œ€๋กœ ํ•˜์—ฌ Job๊ณผ Step์˜ ๊ด€๊ณ„๋ฅผ ๊ทธ๋ฆผ์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

img.png

Step์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์—ญํ• ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งŒ์•ฝ Step์ด ์—ญํ• ๋ณ„(๋ฐ์ดํ„ฐ ์กฐํšŒ ์ˆ˜ํ–‰, ์‹ค์ œ ๋ฐฐ์น˜ ๋กœ์ง ์ˆ˜ํ–‰, ๋ฐ์ดํ„ฐ ์ €์žฅ ์ˆ˜ํ–‰ ๋“ฑ)๋กœ ๋ถ„๋ฆฌ๋œ๋‹ค๋ฉด, ๊ฐ ์—ญํ• ๋ณ„ Step์ด ํ•˜๋‚˜์˜ Job ์•ˆ์— ๋ฌถ์ด๋Š” ์…ˆ์ด๋‹ค.

๊ฒฐ๊ตญ, Step์€ ๋ฐฐ์น˜์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ตœ์†Œํ•œ์˜ ๋‹จ๊ณ„์ด๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ ์‹ค์ œ ๋ฐฐ์น˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฃผ์ฒด์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด Step์˜ ์‹คํ–‰ ์ƒํƒœ๋„ ๊ด€๋ฆฌ๋˜์–ด์•ผ ํ•˜์ง€ ์•Š์„๊นŒ?(Job์˜ ์‹คํ–‰์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” JobExecution์ฒ˜๋Ÿผ)
๊ทธ๋ž˜์„œ, JobExecution๊ณผ ๊ฐ™์€ ๊ฐœ๋…์œผ๋กœ Step์—๋„ StepExecution์ด ์กด์žฌํ•œ๋‹ค. ์ฆ‰, Step์˜ ์‹คํ–‰์„ ์˜๋ฏธํ•˜๋Š” StepExecution ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค.

4.2 StepExecution?

๊ฒฐ๊ตญ, Step์—๋„ ์‹คํ–‰์„ ์˜๋ฏธํ•˜๋Š” ๊ฐ์ฒด์ธ StepExecution์ด ์žˆ๊ณ , ์ด ๋…€์„์ด Step์˜ ์‹คํ–‰์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค. JobExecution์ด BATCH_JOB_EXECUTION ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, StepExecution๋„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์ด ์ง€์›๋œ๋‹ค.
BATCH_STEP_EXECUTION๋ผ๋Š” ํ…Œ์ด๋ธ”๋กœ ๊ด€๋ฆฌ๋œ๋‹ค. ์Šคํ‚ค๋งˆ๋ฅผ ํ†ตํ•ด ์ค‘์š”ํ•œ ํฌ์ธํŠธ ๋ช‡ ๊ฐ€์ง€๋งŒ ํ™•์ธํ•ด๋ณด์ž

img_1.png

  • STEP_NAME: Step์„ ์ •์˜ํ•  ๋•Œ, ์ง€์ •ํ•œ Step ์ด๋ฆ„์ด๋‹ค.
  • JOB_EXECUTION_ID: JobExecution๊ณผ StepExecution์€ 1:N ๊ด€๊ณ„๋‹ค.
    StepExecution์ด ๋ชจ๋‘ ์„ฑ๊ณตํ•ด์•ผ๋งŒ JobExecution๋„ ์„ฑ๊ณตํ•œ๋‹ค. ์ฆ‰, StepExecution๋“ค ์ค‘์— ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด JobExecution์€ ์‹คํŒจ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค.
    ๋˜ํ•œ, Job์ด ์žฌ์‹œ์ž‘ ๋˜์–ด๋„ ์‹คํŒจํ•œ Step์— ํ•œํ•ด์„œ๋งŒ ์žฌ์‹คํ–‰๋œ๋‹ค.
  • COMMIT_COUNT, READ_COUNT, WRITE_COUNT ๋“ฑ: Step์˜ ์‹คํ–‰ ๊ณผ์ •์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ๊ฐ๊ฐ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํšŸ์ˆ˜, ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ /์“ด ํšŸ์ˆ˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ FILTER_COUNT๋‚˜ READ_SKIP_COUNT ๋“ฑ ๋‹ค์–‘ํ•œ countํ•„๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋“ค ๋ชจ๋‘ step์ด ์‹คํ–‰๋˜๋ฉด์„œ, ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ค ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์ถ”์ ํ•˜๋Š”๋ฐ ์šฉ์ดํ•œ ํ•„๋“œ๋กœ ํ™œ์šฉ๋œ๋‹ค.

๊ณต์‹๋ฌธ์„œ์—์„œ ์ œ๊ณตํ•˜๋Š” Job-Step์˜ ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ๋์œผ๋กœ ๊ธฐ๋ณธ๊ฐœ๋…์— ๋Œ€ํ•œ ์„ค๋ช…์„ ๋งˆ์นœ๋‹ค.
img_2.png

4.3 Step์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ์™€ ์ƒ์„ฑ ์›๋ฆฌ

Job๊ณผ Step์˜ ๊ด€๊ณ„์— ๋Œ€ํ•ด ์œ„์—์„œ ์•Œ์•„๋ดค๋‹ค. ์ด๋ฒˆ์—๋Š” Job ํ•˜์œ„ ๊ฐœ๋…์œผ๋กœ ์†ํ•˜๋Š” Step์„ ์–ด๋–ค ์‹์œผ๋กœ ๊ตฌ์„ฑ๋Š”์ง€ ์•Œ์•„๋ณด๊ณ , ๊ตฌ์กฐ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.
์šฐ์„ , ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด Step ๊ตฌ์„ฑ์„ ํŒŒ์•…ํ•ด๋ณด์ž.

4.3.1 ์˜ˆ์‹œ ์ฝ”๋“œ

@Slf4j
@Configuration
@RequiredArgsConstructor
public class TransferNewUserJobConfiguration {

    private final JobRepository jobRepository;
    private final PlatformTransactionManager platformTransactionManager;
    private static final String JOB_NAME = "TRANSFER_NEW_USER_JOB";
    private static final String STEP_1_NAME = "TRANSFER_NEW_USER_STEP";

    private final UserService userService;

    @Bean
    public Job transferNewUserJob() {
        return new JobBuilder(JOB_NAME, jobRepository)
            .start(transferNewUserStep(null))
            .build();
    }


    @Bean
    @JobScope
    public Step transferNewUserStep(
        @Value("#{jobParameters['targetDate']}") LocalDate targetDate
    ) {
        return new StepBuilder(STEP_1_NAME, jobRepository) // StepBuilder ํ˜ธ์ถœ
            .tasklet((contribution, chunkContext) -> { // TaskletStep์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ˜ธ์ถœ
                final List<User> users = userService.findByRegisteredDate(targetDate);
                log.info("{} ๋ช…์˜ ์œ ์ € ์ •๋ณด๋ฅผ AML ๋“ฑ์˜ ์„œ๋น„์Šค๋กœ ์ „์†ก", users.size());
                return RepeatStatus.FINISHED;
            }, platformTransactionManager)
            .build(); // TaskletStep ์ƒ์„ฑ
    }
}

์œ„ ์ฝ”๋“œ์—์„œ ํŒŒ์•…ํ•ด๋ณผ ๋ถ€๋ถ„์€ transferNewUserStep()๋ฉ”์„œ๋“œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ํ•˜๋‚˜์˜ Step์„ ์ •์˜ํ•ด์„œ, returnํ•˜๊ณ  ์žˆ๋‹ค.
์–ด๋–ป๊ฒŒ Step์„ ๊ตฌ์„ฑํ•˜๋Š”์ง€ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋งŒ ์‚ดํŽด๋ณด์ž.

4.3.2 Step์˜ ๊ตฌ์กฐ

img_3.png

Step๋„ Job๊ณผ ๊ต‰์žฅํžˆ ๋น„์Šทํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค. ๊ณตํ†ต ๋ถ€๋ถ„์€ AbstractStep์œผ๋กœ ๋ฌถ์—ฌ ์žˆ๊ณ , ๊ฐ ์ข…๋ฅ˜๋ณ„๋กœ ์„œ๋กœ๋‹ค๋ฅธ ์‹คํ–‰๋ฐฉ์‹์„ ๊ฐ€์ง„ ๊ฐ Step๋“ค์€ ์ด๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” ํ˜•ํƒœ๋‹ค.

๊ฐ Step์˜ ๊ฐ„๋žตํ•œ ํŠน์ง•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ์ž์„ธํ•œ ์„ค๋ช…์€ ๋ณ„๋„์˜ ์žฅ์—์„œ ์„ค๋ช…ํ•œ๋‹ค.

  • TaskletStep: Tasklet์„ ํ˜ธ์ถœํ•ด, ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์˜ Step(Tasklet์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…์€ 4.3.4 ์ฐธ๊ณ )
  • FlowStep: Flow์—๊ฒŒ ์ž‘์—…์„ ์œ„์ž„ํ•˜๋Š” ๋ฐฉ์‹์˜ Step
  • JobStep: ๋ณ„๋„์˜ Job์—๊ฒŒ ์ž‘์—…์„ ์œ„์ž„ํ•˜๋Š” ๋ฐฉ์‹์˜ Step
  • PartitionStep: ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ ๋ฐฉ์‹์œผ๋กœ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์˜ Step

Step์˜ ์ข…๋ฅ˜๋Š” ์œ„์™€ ๊ฐ™์ด ๋„ค ์ข…๋ฅ˜๊ฐ€ ์žˆ๊ณ , ์šฐ์„  ๊ฐ Step์˜ ํŠน์ง•์€ ์œ„ ์ •๋„๋งŒ ์ดํ•ดํ•ด๋‘์ž. Tasklet์ด ๋ญ๊ณ , Flow๊ฐ€ ๋ฌด์—‡์ธ์ง€๋Š” ๋’ค์ด์–ด์„œ ์ •๋ฆฌํ•œ๋‹ค.

๊ฐ Step์€ ์–ด๋–ค ๊ตฌ์กฐ๋กœ ์‹คํ–‰๋˜๊ณ , ์œ„์—์„œ ์ž‘์„ฑํ•œ ์˜ˆ์‹œ์—์„œ์˜ transferNewUserStep()์€ ์–ด๋–ค Step์ด ์‹คํ–‰๋˜๋Š”์ง€ ๋“ฑ์˜ ์‹คํ–‰ ์›๋ฆฌ๋ฅผ ์•Œ์•„๋ด์•ผ ํ•œ๋‹ค.
ํ•˜์ง€๋งŒ, Job์˜ ์ƒ์„ฑ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์‹คํ–‰ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ์—ญ์‹œ๋‚˜ Step๋„ ์–ด๋–ป๊ฒŒ Step์ด ์ƒ์„ฑ๋˜๋Š”์ง€ ์ƒ์„ฑ ์›๋ฆฌ๋ฅผ ๋จผ์ € ์‚ดํŽด๋ณด์ž.

4.3.3 Step์˜ ์ƒ์„ฑ ์›๋ฆฌ

์•„๋ž˜ Step ๊ตฌ์„ฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๋ช…ํ•œ๋‹ค.

@Bean
@JobScope
public Step transferNewUserStep(
    @Value("#{jobParameters['targetDate']}") LocalDate targetDate
) {
    return new StepBuilder(STEP_1_NAME, jobRepository) // StepBuilder ํ˜ธ์ถœ
        .tasklet((contribution, chunkContext) -> { // TaskletStep์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ˜ธ์ถœ
            final List<User> users = userService.findByRegisteredDate(targetDate);
            log.info("{} ๋ช…์˜ ์œ ์ € ์ •๋ณด๋ฅผ AML ๋“ฑ์˜ ์„œ๋น„์Šค๋กœ ์ „์†ก", users.size());
            return RepeatStatus.FINISHED;
        }, platformTransactionManager)
        .build(); // TaskletStep ์ƒ์„ฑ
}

4.3.3.1 StepBuilder() ํ˜ธ์ถœ

img_4.png

new StepBuilder()๋ฅผ ํ†ตํ•ด StepBuilder ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœํ–ˆ๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ StepBuilderHelper๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๋‹ค.


4.3.3.2 ์ƒ์œ„ ํด๋ž˜์Šค์ธ StepBuilderHelper() ํ˜ธ์ถœ: Step ์†์„ฑ๊ฐ’ ์ €์žฅ

img_5.png

CommonJobProperties๋Š” StepBuilderHelper ํด๋ž˜์Šค์˜ ๋‚ด๋ถ€ ํด๋ž˜์Šค๋กœ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ, Step ์‹คํ–‰์— ํ•„์š”ํ•œ ๋‹ค์–‘ํ•œ ์†์„ฑ ๊ฐ’๋“ค์„ ๋‚ด๋ถ€ ํ•„๋“œ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.


4.3.3.3 StepBuilder ๊ฐ์ฒด ์ƒ์„ฑ ์™„๋ฃŒ & ๋‹ค์Œ์œผ๋กœ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ๋Š”?

์œ„ 1,2 ๊ณผ์ •(new StepBuilder())์„ ํ†ตํ•ด์„œ StepBuilder ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค. ๊ทธ ๋‹ค์Œ์œผ๋กœ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ์— ๋”ฐ๋ผ ์–ด๋–ค ์ข…๋ฅ˜์˜ step์ด ์ƒ์„ฑ๋ ์ง€ ๋‹ฌ๋ผ์ง€๋Š”๋ฐ, ์–ด๋–ค ์˜๋ฏธ์ธ์ง€ StepBuilder ํด๋ž˜์Šค๋ฅผ ์‚ดํŽด๋ณด์ž.

ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ถ€๋ถ„ ๋ฐœ์ทŒํ–ˆ๋‹ค.

public class StepBuilder extends StepBuilderHelper<StepBuilder> {

    public TaskletStepBuilder tasklet(Tasklet tasklet, PlatformTransactionManager transactionManager) {
        return (new TaskletStepBuilder(this)).tasklet(tasklet, transactionManager);
    }

    public <I, O> SimpleStepBuilder<I, O> chunk(int chunkSize, PlatformTransactionManager transactionManager) {
        return ((SimpleStepBuilder)(new SimpleStepBuilder(this)).transactionManager(transactionManager)).chunk(chunkSize);
    }

    public PartitionStepBuilder partitioner(String stepName, Partitioner partitioner) {
        return (new PartitionStepBuilder(this)).partitioner(stepName, partitioner);
    }

    public JobStepBuilder job(Job job) {
        return (new JobStepBuilder(this)).job(job);
    }

    public FlowStepBuilder flow(Flow flow) {
        return (new FlowStepBuilder(this)).flow(flow);
    }
    
    // ...
}

4.3.2์—์„œ ์‚ดํŽด๋ดค๋˜ Step์˜ ๊ตฌํ˜„์ฒด๋“ค์ด ์žˆ์—ˆ๋‹ค. ๊ฐ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๋นŒ๋” ํด๋ž˜์Šค๋“ค์ด ์กด์žฌํ•œ๋‹ค. ์ฆ‰, StepBuilder๋Š” Step์˜ ๊ฐ ๊ตฌํ˜„์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ StepBuilder์—๊ฒŒ ๊ทธ ์—ญํ• ์„ ์œ„์ž„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, StepBuilder๊ฐ€ TaskletStep์„ ์ƒ์„ฑํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, TaskletStepBuilder์—๊ฒŒ ๊ทธ ํ–‰์œ„๋ฅผ ๋„˜๊ธด๋‹ค.


์—ฌ๊ธฐ๊นŒ์ง€ Step ๊ตฌํ˜„์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ๋ฆ„์„ ๋””๋ฒ„๊น…์„ ํ†ตํ•ด ์•Œ์•„๋ดค๋‹ค. ๊ฐ ๊ตฌํ˜„์ฒด์— ๋”ฐ๋ผ ๋ฐ›๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋‹ค๋ฅด๋ฏ€๋กœ, ๋ณ„๋„์˜ ์ฑ•ํ„ฐ์—์„œ ์„ค๋ช…ํ•  ์˜ˆ์ •์ด๋‹ค.
๋‹ค์‹œํ•œ๋ฒˆ ์ฒ˜์Œ ์˜ˆ์ œ ์ฝ”๋“œ๋กœ ๋Œ์•„๊ฐ€์„œ ๋‹ค์‹œํ•œ๋ฒˆ ์‚ดํŽด๋ณด์ž.

@Bean
@JobScope
public Step transferNewUserStep(
    @Value("#{jobParameters['targetDate']}") LocalDate targetDate
) {
    return new StepBuilder(STEP_1_NAME, jobRepository) // StepBuilder ํ˜ธ์ถœ
        .tasklet((contribution, chunkContext) -> { // TaskletStep์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ˜ธ์ถœ
            final List<User> users = userService.findByRegisteredDate(targetDate);
            log.info("{} ๋ช…์˜ ์œ ์ € ์ •๋ณด๋ฅผ AML ๋“ฑ์˜ ์„œ๋น„์Šค๋กœ ์ „์†ก", users.size());
            return RepeatStatus.FINISHED;
        }, platformTransactionManager)
        .build(); // TaskletStep ์ƒ์„ฑ
}
  1. new StepBuilder(): StepBuilderHelper๋ฅผ ํ˜ธ์ถœํ•ด, Step์— ํ•„์š”ํ•œ ๋‹ค์–‘ํ•œ ์†์„ฑ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค.
  2. tasklet(): ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ ์ค‘์— tasklet()์„ ํ˜ธ์ถœํ•˜์—ฌ, TaskletStepBuilder์—๊ฒŒ Step ์ƒ์„ฑ์„ ์œ„์ž„ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ Step์€ ๊ตฌ์ฒด์ ์œผ๋กœ TaskletStep์ด๋‹ค.
  3. build(): TaskletStepBuilder::build()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ, TaskletStep์„ ์ƒ์„ฑํ•œ๋‹ค. (์ •ํ™•ํžˆ๋Š” TaskletStepBuilder์—๋Š” build()๊ฐ€ ์—†๊ณ , ๊ทธ ์ƒ์œ„ ํด๋ž˜์Šค์ธ AbstractTaskletStepBuilder ํด๋ž˜์Šค์— ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค.)

Step์˜ ๊ตฌํ˜„์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์—”ํ‹ฐํ‹ฐ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๊ทธ๋ฆฌ๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

img_12.png


4.3์ ˆ์—์„œ๋Š” Step ๊ฐ์ฒด ์ƒ์„ฑ์„ ์œ„ํ•œ ๊ตฌ์กฐ์™€ ์›๋ฆฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ดค๋‹ค. ์ด์ œ๋Š” ์ƒ์„ฑ๋œ Step์ด ์–ด๋–ป๊ฒŒ ์‹คํ–‰๋˜๋Š”์ง€. ๊ทธ ์‹คํ–‰ ์›๋ฆฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค.

4.4 Step์˜ ์‹คํ–‰์›๋ฆฌ

Step์˜ ์‹คํ–‰์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ์— ์•ž์„œ, Step์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Job์ด ์‹คํ–‰ํ•˜๋Š” ์ฃผ์ฒด์ด๋ฏ€๋กœ Job์— ์ข…์†์ ์ธ ๊ด€๊ณ„์ผ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค. ๋•Œ๋ฌธ์—, Job์˜ ์‹คํ–‰์—์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ Step์˜ ์‹คํ–‰๊นŒ์ง€ ํ˜๋Ÿฌ๊ฐ€๋Š” ํ”Œ๋กœ์šฐ์ด๋‹ค. ๋ณธ ๊ธ€์—์„œ Job์˜ ์‹คํ–‰์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ์—” ์–‘์ด ๋ฐฉ๋Œ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด์ „ ๊ธ€์— ๋Œ€ํ•œ ์ปจํ…์ŠคํŠธ๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ํ•œ๋‹ค.

์ด์ „ ๊ธ€์˜ 3.1.2.7์ ˆ์—์„œ SimpleJob::doExecute() ์ฝ”๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ๋„˜์–ด๊ฐ”๋‹ค. ์ด์œ ๋Š” step์„ ์‹คํ–‰ํ•˜๋Š” ๋กœ์ง์ด ๋Œ€๋ถ€๋ถ„์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ, ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ ๋ณธ ๊ธ€์—์„œ ์„ค๋ช…ํ•˜๊ณ ์ž ํ•œ๋‹ค.


4.4.1 SimpleJob์˜ ์‹คํ–‰

์ด์ „ ๊ธ€์„ ์‚ด์ง ์š”์•ฝํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
๊ฐ™์€ ์˜ˆ์ œ๋กœ SimpleJob์ด ์ƒ์„ฑ๋˜๊ณ , ์‹คํ–‰๋˜๋Š” ๊ฒƒ๊นŒ์ง€ ๋””๋ฒ„๊น…์„ ํ†ตํ•ด ํ™•์ธํ–ˆ๋‹ค. ๊ทธ ๊ณผ์ •์—์„œ AbstractJob::execute()๊ฐ€ ์‹คํ–‰ ๋˜์—ˆ๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ SimpleJob::doExecute()๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋‹ค.

์ถ”๊ฐ€๋กœ, ์ด๊ฑด ์–ด๋””๊นŒ์ง€๋‚˜ SimpleJob์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๊ฐ Job๋งˆ๋‹ค doExecute() ๊ตฌํ˜„ ๋ฐฉ๋ฒ•๋„ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์—ผ๋‘์— ๋‘ฌ์•ผ ํ•œ๋‹ค.

4.4.1.1 SimpleJob::doExecute()

public class SimpleJob extends AbstractJob {

    private final List<Step> steps;

    protected void doExecute(JobExecution execution)
        throws JobInterruptedException, JobRestartException, StartLimitExceededException {
        StepExecution stepExecution = null; 
        Iterator var3 = this.steps.iterator(); // 1. steps์— ์ €์žฅ๋œ step๋“ค์„ ๋ฐ˜๋ณตํ•˜์—ฌ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•จ 

        while (var3.hasNext()) {  // 1. steps์— ์ €์žฅ๋œ step๋“ค์„ ๋ฐ˜๋ณตํ•˜์—ฌ ์‹คํ–‰ํ•œ๋‹ค.
            Step step = (Step) var3.next();
            stepExecution = this.handleStep(step, execution); // 2. ๊ฐ Step์„ ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ์‹คํ–‰ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.
            if (stepExecution.getStatus() != BatchStatus.COMPLETED) {
                break;
            }
        }

        if (stepExecution != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Upgrading JobExecution status: " + stepExecution);
            }
            
            execution.upgradeStatus(stepExecution.getStatus()); // 3. BatchStatus๋ฅผ JobExecution์— ๋ฐ˜์˜
            execution.setExitStatus(stepExecution.getExitStatus()); // 4. ExitStatus๋ฅผ JobExecution์— ๋ฐ˜์˜
        }
    }
}

SimpleJob์€ ๋ฉค๋ฒ„๋กœ List ํƒ€์ž…์˜ steps๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ด์ „ ๊ธ€์—์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด Job ์ƒ์„ฑ ์‹œ, SimpleJobBuilder::build()์—์„œ ์ •์˜๋œ step ๋ฆฌ์ŠคํŠธ๋ฅผ SimpleJob ๋ฉค๋ฒ„์— ์ €์žฅํ•œ๋‹ค.
๊ทธ๋ ‡๊ฒŒ ์•„๋ž˜์™€ ๊ฐ™์ด SimpleJob::doExecute()๋Š” ์•„๋ž˜ ์ˆœ์„œ์™€ ๊ฐ™์ด ์ˆ˜ํ–‰๋œ๋‹ค.

  1. ์ €์žฅ๋œ Step ๋ฆฌ์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์”ฉ ์ˆœํšŒํ•˜๋ฉด์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰
  2. ๊ฐ Step์„ ์ˆ˜ํ–‰ํ•˜๊ณ , stepExecution ๋ณ€์ˆ˜์— ์‹คํ–‰ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ , Step์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ COMPLETED๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์ž‘์—…์„ ์ค‘์ง€ํ•œ๋‹ค. Step ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” handleStep() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ ์ ˆ์—์„œ ์•Œ์•„๋ณด๊ฒ ๋‹ค.
  3. StepExecution์—๋Š” status์™€ exitStatus ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๊ฐ๊ฐ BatchStatus๋ผ๋Š” enum ํƒ€์ž…, ExitStatus๋ผ๋Š” ํด๋ž˜์Šค ํƒ€์ž…์ด๋‹ค. Job์˜ ํ˜„์žฌ ์‹คํ–‰ ์ƒํƒœ์™€ ์ข…๋ฃŒ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
    3๋ฒˆ์—์„œ๋Š”, ํ˜„์žฌ StepExecution์˜ ์ƒํƒœ๋ฅผ JobExecution ์ƒํƒœ์— ๋ฐ˜์˜ํ•œ๋‹ค.
  4. ํ˜„์žฌ StepExecution์˜ ์ข…๋ฃŒ ์ƒํƒœ๋ฅผ JobExecution์˜ ์ข…๋ฃŒ ์ƒํƒœ์— ๋ฐ˜์˜ํ•œ๋‹ค.

2๋ฒˆ์—์„œ handleStep()์„ ํ†ตํ•ด Step์„ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

4.4.1.2 AbstractJob::handleStep()

public abstract class AbstractJob implements Job, StepLocator, BeanNameAware, InitializingBean {

    private StepHandler stepHandler;
    // ...
    
    protected final StepExecution handleStep(Step step, JobExecution execution)
            throws JobInterruptedException, JobRestartException, StartLimitExceededException {
        return this.stepHandler.handleStep(step, execution);
    }
    // ...
}

handleStep() ๋ฉ”์„œ๋“œ๋Š” AbstractJobํด๋ž˜์Šค์— ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ StepHandler::handleStep()์„ ํ˜ธ์ถœํ•˜๋Š” ์—ญํ• ๋งŒ ํ•œ๋‹ค.
StepHandler๋Š” Step ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ SimpleStepHandler ๊ตฌํ˜„์ฒด๋งŒ ์กด์žฌํ•œ๋‹ค. (5.1 ๊ธฐ์ค€)

๊ทธ๋ž˜์„œ, ๋‹ค์Œ์œผ๋กœ๋Š” SimpleStepHandler์˜ handleStep() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค.

4.4.1.3 SimpleStepHandler::handleStep()

public StepExecution handleStep(Step step, JobExecution execution) throws JobInterruptedException, JobRestartException, StartLimitExceededException {
        if (execution.isStopping()) {
            throw new JobInterruptedException("JobExecution interrupted.");
        } else {
            JobInstance jobInstance = execution.getJobInstance();  // 1. ํ˜„์žฌ JobExecution์˜ JobInstance๋ฅผ ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค.
            StepExecution lastStepExecution = this.jobRepository.getLastStepExecution(jobInstance, step.getName()); // 2. JobInstance์˜ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ์‹คํ–‰ Step์„ ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค. 
            if (this.stepExecutionPartOfExistingJobExecution(execution, lastStepExecution)) {
                if (logger.isInfoEnabled()) {
                    logger.info("ํ˜„์žฌ Job์—์„œ ์ค‘๋ณต๋œ Step ์‹คํ–‰์ด ๊ฐ์ง€๋˜์—ˆ๋‹ค๋Š” ๋‚ด์šฉ");
                }
                lastStepExecution = null;
            }

            StepExecution currentStepExecution = lastStepExecution;
            if (this.shouldStart(lastStepExecution, execution, step)) { // 3. ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰์œผ๋กœ ๊ฐ€์ ธ์˜จ Step์ด ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”์ง€ ๊ฒ€์ฆ
                currentStepExecution = execution.createStepExecution(step.getName()); // 4. JobExecution์— ์ƒˆ๋กœ์šด StepExecution์„ ์ถ”๊ฐ€ํ•œ๋‹ค. 
                boolean isRestart = lastStepExecution != null && !lastStepExecution.getStatus().equals(BatchStatus.COMPLETED);
                if (isRestart) { // 5-1. ์žฌ์‹คํ–‰๋œ Step์ด๋ฉด, ExecutionContext๋ฅผ ๊ทธ๋Œ€๋กœ ์„ค์ •ํ•œ๋‹ค.
                    currentStepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
                    if (lastStepExecution.getExecutionContext().containsKey("batch.executed")) {
                        currentStepExecution.getExecutionContext().remove("batch.executed");
                    }
                } else { // 5-2. ์ƒˆ๋กœ ์‹คํ–‰๋œ Step์ด๋ผ๋ฉด, ์ƒˆ๋กœ์šด ExecutionContext๋ฅผ ์ƒ์„ฑํ•ด์„œ ์„ค์ •ํ•œ๋‹ค.
                    currentStepExecution.setExecutionContext(new ExecutionContext(this.executionContext));
                }

                this.jobRepository.add(currentStepExecution); // 6. StepExecution ์ €์žฅ

                try {
                    step.execute(currentStepExecution); // 7. Step ์‹คํ–‰
                    currentStepExecution.getExecutionContext().put("batch.executed", true);
                } catch (JobInterruptedException var8) {
                    JobInterruptedException e = var8;
                    execution.setStatus(BatchStatus.STOPPING);
                    throw e;
                }

                this.jobRepository.updateExecutionContext(execution); // 8. ์ƒˆ๋กœ์šด StepExecution์ด ์ถ”๊ฐ€๋œ JobExecution์„ ์ €์žฅํ•œ๋‹ค.
                if (currentStepExecution.getStatus() == BatchStatus.STOPPING || currentStepExecution.getStatus() == BatchStatus.STOPPED) {
                    execution.setStatus(BatchStatus.STOPPING);
                    throw new JobInterruptedException("Job interrupted by step execution");
                }
            }

            return currentStepExecution;
        }
    }

์œ„ ์ฝ”๋“œ ์ƒ์œผ๋กœ 18 ์ฃผ์„์„ ์ž‘์„ฑํ•ด๋†จ๋‹ค. ๋’ค์ด์–ด์„œ 4.3.1์—์„œ ์ž‘์„ฑํ•œ ์˜ˆ์‹œ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๊ทธ๋ฆฌ๋ฉฐ ์ดํ•ดํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.
๊ทธ ์ „์—, 1
8 ๊ณผ์ • ์ค‘์— ํ•˜๋‚˜ ์ธ์ง€ํ•ด์•ผํ•  ๋ถ€๋ถ„์ด ์žˆ๋‹ค. ๋ฐ”๋กœ 7๋ฒˆ ์ฆ‰, Step์„ ์‹คํ–‰ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. SimpleStepHandler์—์„œ Step::execute()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, AbstractStep::execute()๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

AbstractStep์˜ execute() ๋ฉ”์„œ๋“œ๋Š” ์•ž์„œ ๋‹ค๋ฃฌ AbstractJob์˜ execute() ๋ฉ”์„œ๋“œ์™€ ๋งค์šฐ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค.
์‹คํ–‰ ์ „/ํ›„๋กœ StepExecution์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , Step์„ ์‹คํ–‰ํ•  ๋•Œ๋Š” doExecute() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ์‹œ์ผœ ๊ตฌํ˜„ ํด๋ž˜์Šค์˜ ์ •์˜ํ•œ ์ž‘์—…์„ ํ˜ธ์ถœํ•œ๋‹ค.

์ด์ œ ์œ„ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ 4.3.1 ์˜ˆ์‹œ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, Step์ด ์–ด๋–ป๊ฒŒ ์ƒ์„ฑ๋˜๊ณ  ์‹คํ–‰๋˜๋Š”์ง€ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๋์œผ๋กœ Step์— ๋Œ€ํ•œ ์ •๋ฆฌ๋ฅผ ๋งˆ์นœ๋‹ค.

4.5 Step ์ƒ์„ฑ, ์‹คํ–‰ ๋‹ค์ด์–ด๊ทธ๋žจ

4.5.1 ์˜ˆ์‹œ ์ฝ”๋“œ

@Slf4j
@Configuration
@RequiredArgsConstructor
public class TransferNewUserJobConfiguration {

    private final JobRepository jobRepository;
    private final PlatformTransactionManager platformTransactionManager;
    private static final String JOB_NAME = "TRANSFER_NEW_USER_JOB";
    private static final String STEP_1_NAME = "TRANSFER_NEW_USER_STEP";

    private final UserService userService;

    @Bean
    public Job transferNewUserJob() {
        return new JobBuilder(JOB_NAME, jobRepository)
            .start(transferNewUserStep(null))
            .build();
    }


    @Bean
    @JobScope
    public Step transferNewUserStep(
        @Value("#{jobParameters['targetDate']}") LocalDate targetDate
    ) {
        return new StepBuilder(STEP_1_NAME, jobRepository) 
            .tasklet((contribution, chunkContext) -> {
                final List<User> users = userService.findByRegisteredDate(targetDate);
                log.info("{} ๋ช…์˜ ์œ ์ € ์ •๋ณด๋ฅผ AML ๋“ฑ์˜ ์„œ๋น„์Šค๋กœ ์ „์†ก", users.size());
                return RepeatStatus.FINISHED;
            }, platformTransactionManager)
            .build();
    }
}

4.5.2 Step ์ƒ์„ฑ ๋‹ค์ด์–ด๊ทธ๋žจ

img_11.png

4.5.3 Step ์‹คํ–‰ ๋‹ค์ด์–ด๊ทธ๋žจ

img_10.png