[Spring] Spring Batch

๊น€์ •๋ฏผยท2024๋…„ 5์›” 8์ผ
3
post-thumbnail

๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์Šคํ”„๋ง ๋ฐฐ์น˜์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

์•„๋ž˜์˜ ์ฑ…์„ ์ฐธ๊ณ  ํ•˜์˜€๋‹ค.

๐Ÿ’ก ์Šคํ”„๋ง ๋ฐฐ์น˜ ์™„๋ฒฝ ๊ฐ€์ด๋“œ 2/e

๐Ÿ’ก Spring Batch ?

Spring Batch๋Š” ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฒฝ๋Ÿ‰ํ™”๋œ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ผ๊ด„ ์ฒ˜๋ฆฌ(Batch Processing) ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋‚˜ ์ฃผ๊ธฐ์ ์ธ ์—…๋ฌด ์ฒ˜๋ฆฌ ๋“ฑ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•œ ๋ถ„์‚ฐ ๋ฐฉ์‹์˜ ์ฒ˜๋ฆฌ๋ฅผ ์ง€์›ํ•œ๋‹ค.

Spring Batch์˜ ํŠน์ง•

Spring Batch๋Š” ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ, ์žฌ์‹œ๋„ ๊ธฐ๋Šฅ ๋“ฑ์ด ์ด์— ํ•ด๋‹น๋œ๋‹ค.

๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

Spring Batch๋Š” ๋ฐฉ๋Œ€ํ•œ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ž‘์—…์„ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์„œ, ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•˜๋‹ค.

ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ

Spring Batch๋Š” ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ๋ฅผ ์ง€์›ํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ค‘ ์‹คํŒจํ•œ ์ž‘์—…์€ ๋กค๋ฐฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์žฌ์‹œ๋„ ๊ธฐ๋Šฅ

Spring Batch๋Š” ์ž‘์—… ์ค‘ ์‹คํŒจํ•œ ๊ฒฝ์šฐ, ์ž‘์—…์„ ์žฌ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ๋˜ํ•œ, ์žฌ์‹œ๋„ ํšŸ์ˆ˜๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

Spring Batch๋Š” ๊ทธ ์ž์ฒด๋งŒ์œผ๋กœ Job์„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†๋‹ค.
Configuration Bean์„ ํ†ตํ•ด ํ•ด๋‹น Job์ด ์‹คํ–‰๋  ๊ฒฝ์šฐ์— ์–ด๋–ค ์ผ์„ ์ฒ˜๋ฆฌํ• ์ง€ ์„ธํŒ…ํ•  ๋ฟ ์ด๋ฅผ ์‹คํ–‰ ์‹œ์ผœ์ฃผ๊ธฐ ์œ„ํ•ด์„ ย Batch Scheduler๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

๐Ÿ’ก ์Šคํ”„๋ง ๋ฐฐ์น˜ ์šฉ์–ด

JobRepository

JobRepository๋Š” ๋ฐฐ์น˜๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์ „๋ฐ˜์ ์ธ Object๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค. (Job, JobLauncher, Step, ๋“ฑ)

๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ํ†ตํ•ดย ๋ฐฐ์น˜ ์ˆ˜ํ–‰๊ณผ ๊ด€๋ จ๋œ ์ˆ˜์น˜ ๋ฐ์ดํ„ฐ์™€ Job์˜ Status๋ฅผ ์œ ์ง€/๊ด€๋ฆฌํ•˜๋ฉฐย ๋ฐฐ์น˜์™€ ๊ด€๋ จ๋œ CRUD ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

Job

Job์€ย ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋งŒ๋“ค์–ด ๋†“์€ ๊ฐ์ฒด์ด๋‹ค.

๋ฐฐ์น˜์ฒ˜๋ฆฌ ๊ณผ์ •์— ์žˆ์–ด ์ตœ์ƒ๋‹จ Object๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

JobLauncher

JobLauncher๋Š” ๋ง๊ทธ๋Œ€๋กœย Job์„ ์‹คํ–‰์‹œํ‚ค๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค.

Job & JobParameters๋ฅผ param์œผ๋กœ ๋ฐ›๊ณ  ๋ฐฐ์น˜ ์ˆ˜ํ–‰ ํ›„, JobExecution์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Spring Batch์—์„œ๋Š” JobLauncherApplicationRunner ํด๋ž˜์Šค๊ฐ€ ์ž๋™์œผ๋กœ JobLauncher๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—ย ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์ปจํŠธ๋กคํ•  ์ผ์€ ์—†๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Step

Step์€ Job์„ ๊ตฌ์„ฑํ•˜๋Š” ๋…๋ฆฝ์ ์ธ ์ž‘์—… ๋‹จ์œ„์ด๋‹ค. ์‹ค์ œ ๋ฐฐ์น˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ  ์ œ์–ดํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Object๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Step์€ย Taskletย /ย Chunkย ๊ธฐ๋ฐ˜์œผ๋กœ ์ˆ˜ํ–‰๋˜๋ฉฐ ์ด๋Š” ์œ ์ €๊ฐ€ ์„ ํƒํ•ด์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

Tasklet vs Chunk

Tasklet: ํ•œ ๊ฐ€์ง€ ์ด์ƒ์˜ CRUD๊ฐ€ ๋ฐœ์ƒ(=๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)ํ•˜๋Š” task์— ๋Œ€ํ•ด ์ผ๊ด„์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ, ์ฑ„ํƒํ•œ๋‹ค. (๋ณต์žกํ•œ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” job์ผ ๊ฒฝ์šฐ, ์ฑ„ํƒ)

Chunk: chunk ๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌํ•  ๋ชจ๋“  record๋ฅผ ์ญ‰ ์ฝ์–ด๋“ค์ธ ํ›„, ๋ชจ๋‘ ์ฝ์–ด๋“ค์ด๋Š”๋ฐ ์„ฑ๊ณตํ•˜๋ฉด ํ•œ๋ฒˆ์— Writeํ•˜๋Š” ๋ฐฉ์‹ (๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ๋‹จ์ˆœ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ, ์ฑ„ํƒ)

ItemReader

ItemReader๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๋ฐฐ์น˜๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ์žˆ์–ด ๋Œ€์ƒ์ด ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด๋“ค์ด๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” Object์ด๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด๋“ค์ด๋Š” ๋ฐฉ๋ฒ•์—๋Š” DB Connection์„ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜๋„ ์žˆ์ง€๋งŒ File I/O๋ฅผ ํ†ตํ•ด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๋„ ํ•œ๋‹ค.

ItemProcessor

ItemProcessor๋Š” Read์™€ Write์˜ ์ค‘๊ฐ„ ๋‹จ๊ณ„์—์„œย ๊ฐ€๊ณต(์ฒ˜๋ฆฌ) ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ ย ์ด ๋‹จ๊ณ„๋Š” ํ•„์ˆ˜๊ฐ€ ์•„๋‹ˆ๊ธฐ๋„ ํ•˜๋‹ค.ย Read์™€ Write๋งŒ์œผ๋กœ ์›ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ItemProcessor๋Š” ๊ณผ๊ฐํžˆ Skipํ•ด๋„ ๋œ๋‹ค.

ItemWriter

ItemWriter๋Š” Batch์˜ ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„์ด๋‹ค. ๋ง ๊ทธ๋Œ€๋กœ ์šฐ๋ฆฌ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด ์ตœ์ข…์ ์œผ๋กœ ๊ฐ€๊ณต๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์šฐ๋ฆฌ๋Š” ItemWriter๋ฅผ ํ†ตํ•ด ๋‹ค์‹œย DB์— ํ•ด๋‹น ๋‚ด์šฉ์„ ์ €์žฅํ•  ์ˆ˜๋„ ์žˆ๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผย ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ๋กœ API ํ˜ธ์ถœ์‹œํ‚ฌ ์ˆ˜๋„,ย Kafka๋ฅผ ํ†ตํ•ด msg ๋ฐœํ–‰์‹œํ‚ฌ ์ˆ˜๋„ ์žˆ๋‹ค.

Spring Batch Job ๋งŒ๋“ค๊ธฐ

๊ทธ๋Ÿผ ๊ฐ„๋‹จํžˆ Spring Batch์˜ ์šฉ์–ด ์ •๋ฆฌ๋„ ํ•ด๋ดค๊ฒ ๋‹ค ์ด์ œ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ํ•˜์ž.

์šฐ์„ , ๋‚˜๋Š”ย Spring Initializr(https://start.spring.io)๋ฅผ ํ†ตํ•ด SpringBoot ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ์œผ๋ฉฐ ๊ธฐ๋ณธ Spec์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1. @EnbleBatchProcessing ์ถ”๊ฐ€

@SpringBootApplication
@EnableBatchProcessing
public class JobPracticeApplication {

	public static void main(String[] args) {
		SpringApplication.run(JobPracticeApplication.class, args);
	}

}

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์‹œ, ์ตœ์ดˆ ๋ฉ”์ธ Application ํด๋ž˜์Šค์— ์œ„์™€ ๊ฐ™์ดย @EnableBatchProcessingย ์• ๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

@EnableBatchProcessing

: ๋ฐฐ์น˜ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”

2. JobConfig Class ์ƒ์„ฑ

ํŠน์ • Job์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋„๋ก ํ•˜์ž.

@Slf4j
@Configuration
@RequiredArgsConstructor
public class MyFirstJobConfiguration {
					...
}

3. Job ์ƒ์„ฑ

Spring Batch 5.0 ๋ถ€ํ„ฐย JobBuilderFactory๋Š” deprecated๋˜์—ˆ๋‹ค.

https://docs.spring.io/spring-batch/docs/current/api/deprecated-list.html

์ด๋กœ ์ธํ•ด ๊ธฐ์กด์— ํ™œ์šฉํ•˜๋˜ ๋ฐฉ์‹์€ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ทธ๋ž˜์„œย JobRepository๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟ”์ค„ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

(AS-IS)

				...
    @Bean
    public Job myFirstJob(Step step) {
        return this.jobBuilderFactory.get("myFirstJob")
                .start(myFirstStep)
                .build();
    }
    			...

(TO-BE)

				...
    @Bean
    public Job myFirstJob(JobRepository jobRepository){
        return new JobBuilder("myFirstJob", jobRepository)
                .start(myFirstStep(jobRepository))
                .build();
    }
    			...

์˜ˆ์ œ ์†, job์€ย ๋‹จ์ผ Step์œผ๋กœ ๊ตฌ์„ฑํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

4. Step ์ƒ์„ฑ

์ด์ œ Job์— ํฌํ•จ๋˜๋Š” Step์„ ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค.

StepBuilderFactoryย ์—ญ์‹œ Spring Batch 5.0์ด์ƒ๋ถ€ํ„ฐ deprecated๋˜์–ด ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

(AS-IS)

				...
    public Step myFirstStep() {
        return stepBuilderFactory.get("myFirstStep")
                .<String, String>chunk(1000)
                .reader(itemReader())
                .writer(itemWriter())
                .build();
    }
				...

(TO-BE)

				...
    @Bean
    public Step myFirstStep(JobRepository jobRepository){
        return new StepBuilder("myFirstStep",jobRepository)
                .<String, String>chunk(1000,transactionManager)
                .reader(itemReader())
                .writer(itemWriter())
                .build();
    }
    			...

Step์€ chunk ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜์˜€๊ณ  ํ•œ๋ฒˆ์— ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š” chunk size๋Š” 1000์œผ๋กœ ์„ค์ •ํ–ˆ๋‹ค.

5. ItemReader ์ƒ์„ฑ

๋‹ค์Œ์œผ๋กœ Batch๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ Readํ•˜๋Š” ItemReader ๋ถ€๋ถ„์„ ๋งŒ๋“ค์–ด๋ณด์ž.

				...
    @Bean
    public ItemReader<String> itemReader(){
        return new ItemReader<String>() {
            @Override
            public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
                return "Read OK";
            }
        };
    }
    			...

์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ  ์‹ค์งˆ์ ์œผ๋กœย DB ํ†ต์‹ ย /ย File I/O๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ, ์ด ๋ถ€๋ถ„์— ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.

(์•„๋ž˜๋Š” MyBatis๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์˜ˆ์‹œ์ด๋‹ค.)

    			...
    public MyBatisCursorItemReader<Object> itemReader() {
        String strQueryId = "์ฟผ๋ฆฌ ๊ฒฝ๋กœ ๊ธฐ์žฌ";

        return new MyBatisCursorItemReaderBuilder<Object>()
                .sqlSessionFactory(sqlSessionFactory)
                .queryId(strQueryId)
                .parameterValues(parameterValues)
                .saveState(false)
                .build();
    }
     			...

6. ItemWriter ์ƒ์„ฑ

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐฐ์น˜๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ถœ๋ ฅ์„ ๋‹ด๋‹นํ•˜๋Š” ItemWriter ๋ถ€๋ถ„์ด๋‹ค.

     			...
    @Bean
    public ItemWriter<String> itemWriter(){
        return strList -> {
            strList.forEach(
                    str -> log.info("str: {}", str)
            );
        };
    }
     			...

ItemReader์—์„œ ์ฒ˜๋ฆฌํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ String Listํ˜•ํƒœ๋กœ ๋‹ด๊ฒจ ๋“ค์–ด์˜ค๋ฉด ํ•ด๋‹น str๋ฅผ ๋กœ๊ทธ ์ถœ๋ ฅํ•˜๋Š” ํ˜•ํƒœ๋กœ ๊ฐ„๋‹จํžˆ ๊ตฌ์„ฑํ•ด๋ณด์•˜๋‹ค.

์‹ค์งˆ์ ์œผ๋กœ ํ™œ์šฉํ•˜๊ฒŒ ๋˜๋‹ค๋ฉด Writer ๋ถ€๋ถ„์—์„œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์œผ๋กœ API ์ „์†ก์„ ํ•  ์ˆ˜๋„ ์žˆ๊ณ  DB์— ๊ฐ€๊ณต๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค.

๐Ÿ’กย JobInstance๋Š” ํ•œ ๋ฒˆ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜๋ฉด ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์—†๋‹ค. JobInstance๋Š” ์žก ์ด๋ฆ„๊ณผ ์ „๋‹ฌ๋œ ์‹๋ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‹๋ณ„๋˜๋ฏ€๋กœ, ๋™์ผํ•œ ์‹๋ณ„ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์žก์€ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

์†Œ์Šค

๋” ๋งŽ์€ ๋‚ด์šฉ์€ ๊นƒํ—ˆ๋ธŒ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

github : https://github.com/Jungmin-Dev/SpringBatch

๐Ÿ’ก๊ธฐ๋ณธ์ ์ธ ์ฒญํฌ ๊ตฌ์„ฑ
@Autowired
	private JobBuilderFactory jobBuilderFactory;

	@Autowired
	private StepBuilderFactory stepBuilderFactory;

	@Bean
	public Job chunkBasedJob() {
		return this.jobBuilderFactory.get("chunkBasedJob")
				.start(chunkStep())
				.build();
	}

	@Bean
	public Step chunkStep() {
		return this.stepBuilderFactory.get("chunkStep")
				.<String, String>chunk(1000)
				.reader(itemReader())
				.writer(itemWriter())
				.build();
	}

	@Bean
	public ListItemReader<String> itemReader() {
		List<String> items = new ArrayList<>(100000);

		for (int i = 0; i < 100000; 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);
			}
		};
	}

	public static void main(String[] args) {
		SpringApplication.run(ChunkJob.class, args);
	}
๐Ÿ’กTasklet, Chunk ๊ฐ„๋‹จ ๋น„๊ต

Tasklet

๐Ÿ’กย ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๊ณผ์ •์ด tasklet์•ˆ์—์„œ ํ•œ๋ฒˆ์— ์ด๋ค„์ง„๋‹ค.

๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๊ณผ์ •์ด ์‰ฌ์šด ๊ฒฝ์šฐ ์‰ฝ๊ฒŒ ์‚ฌ์šฉ๋˜๋ฉฐ, ๋Œ€๋Ÿ‰์ฒ˜๋ฆฌ ๊ฒฝ์šฐ ๋” ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

Chunksize

๐Ÿ’กย chunksize ๋‹จ์œ„๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ํŽ˜์ด์ง•์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌ๋œ๋‹ค.

๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ• ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, reader / processor / writer ๋กœ ๊ตฌ๋ถ„๋˜์–ด ์ฒ˜๋ฆฌ๋œ๋‹ค.

(reader์™€ writer๋Š” ํ•„์ˆ˜์ด๋ฉฐ, processor๋Š” ์‚ฌ์šฉ์•ˆํ•ด๋„ ๋œ๋‹ค.)

  • reader

: (ํŒŒ์ผ/DB) ๋ฐ์ดํ„ฐ(item)๋ฅผ ์ฝ์–ด์˜ค๋ฉฐ, reader์•ˆ์—์„œ๋„ ํŽ˜์ด์ง•์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ bean๋“ค์ด ์žˆ๋‹ค.

ItemReader, MybatisPagingItemReader, JpaPagingItemReader, ...

  • processor

: reader์—์„œ ์ฝ์–ด์˜จ ๋ฐ์ดํ„ฐ(item)๋ฅผ ํ•„ํ„ฐ/์ฒ˜๋ฆฌ ํ•˜๊ณ  write๋กœ ๋ณด๋‚ด๋Š” ์—ญํ• .

item์„ ํ•„ํ„ฐ ๋„์ค‘ null๋กœ ๋ฆฌํ„ดํ•˜๋ฉด, ๊ทธ item์€ write๋กœ ์ „๋‹ฌ๋˜์ง€ ๋ชปํ•œ๋‹ค.

ex) 10๊ฐœ read ํ›„ processor์—์„œ 4๊ฐœ ํ•„ํ„ฐ๋ง ํ•ด์„œ 6๊ฐœ๋งŒ ๋ฆฌํ„ดํ•˜๋ฉด write์—์„œ๋Š” 6๊ฐœ๋งŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • writer

: processor์—์„œ ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ๋“ค(items : List)์„ ํŒŒ์ผ์ด๋‚˜ DB์— ์ ์žฌํ•˜๋Š” ์—ญํ• .

writer๋Š” ๊ธฐ๋ณธ์œผ๋กœ List๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ, List๋Š” chunksize์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค.

Tasklet

private String JOBNAME = "testTaskChunkJob";

@Bean
public JobtestTaskChunkJob(){
return jobBuilderFactory.get(JOBNAME)
                .incrementer(new RunIdIncrementer())
                .start(this.taskStep1())
                .build();
    }

@Bean
public SteptaskStep1(){
return stepBuilderFactory.get("taskStep1")
                .tasklet(tasklet())
                .build();
    }

private Tasklettasklet(){//tasklet์œผ๋กœ ๋ชจ๋‘ ์ฒ˜๋ฆฌ
return (contribution, chunkContext) -> {
            List<String> items = getItems();

            log.info("items : "+ items.toString());

return RepeatStatus.FINISHED;
        };
    }
private List<String>getItems() {
        List<String> items =new ArrayList<>();

for (int i = 0; i < 100; i++) {
            items.add(i + " test!");
        }

return items;
    }

๋‹จ์ˆœํ•˜๊ฒŒ 100๊ฐœ์˜ ์ˆซ์ž๋ฅผ ๊ฐ€์ ธ์™€ log๋ฅผ ์ถœ๋ ฅํ•˜๋Š” tasklet์ด๋‹ค.

tasklet์€ ํ•จ์ˆ˜์•ˆ์—์„œ ๋ชจ๋‘ ์ฒ˜๋ฆฌ๋˜์„œ 1~100๊นŒ์ง€ ๋ชจ๋‘ ์ถœ๋ ฅ๋œ๋‹ค.

๊ฒฐ๊ณผ :

items : [0 test!, 1 test!, 2 test!, 3 test!, ... 99 test!]

chunksize

private String JOBNAME = "testTaskChunkJob";

@Bean
public JobtestTaskChunkJob(){
return jobBuilderFactory.get(JOBNAME)
                .incrementer(new RunIdIncrementer())
                .start(this.taskStep1())//tasklet ์ฒ˜๋ฆฌ
                .next(this.chunkStep1())//chunk ์ฒ˜๋ฆฌ
                .build();
    }

// ...
@Bean
public StepchunkStep1() {//chunksize๋กœ ์ฒ˜๋ฆฌ
return stepBuilderFactory.get("chunkStep1")
                .<String,String>chunk(10)
                .reader(itemReader())
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

private ItemReader<String>itemReader() {
return new ListItemReader<>(getItems());
    }

private ItemProcessor<String, String>itemProcessor() {
return item -> item + " now processor!!";
    }

private ItemWriter<String>itemWriter() {
return items -> log.info("### writer : " + items.toString());
    }

chunksize๋กœ ์ฒ˜๋ฆฌ ์‹œ, Step์— .chunksize(ํฌ๊ธฐ) ๋กœ ์ •์˜ํ•˜๋ฉฐ ์•ž์— ์ฒ˜๋ฆฌ๋  ํƒ€์ž…์„ ์ •์˜ํ•œ๋‹ค.

.<String,String>chunk(10)

<String,String>

์•ž์˜ย String์€ย reader์—์„œ ์ฝ์€ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์ด๊ณ 

๋’ค์˜ย String์€ย writer์—์„œ ๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์ด๋‹ค.

๋”ฐ๋ผ์„œ processor ์ฒ˜๋ฆฌ ํ•จ์ˆ˜์—์„œ๋Š” ์•ž/๋’ค ํƒ€์ž…์„ ๋ชจ๋‘ ์„ ์–ธํ•ด์„œ ํ•„ํ„ฐ๋‚˜ ๋ฐ์ดํ„ฐ ๋ณ€ํ˜• ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

  • reader
private ItemReader<String>itemReader() {
returnnew ListItemReader<>(getItems());
}

reader์—์„œ ๋ฆฌํ„ด์œผ๋กœ ์„ ์–ธํ•œ ListItemReader๋Š” spring batch์—์„œ ์ œ๊ณตํ•˜๋Š” item์ฒ˜๋ฆฌ ํด๋ž˜์Šค์ด๋‹ค.

getItems์—์„œ ๊ฐ€์ ธ์˜จ List์„ LIstItemReader์—์„œ ์•Œ์•„์„œ item๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

  • processor
private ItemProcessor<String, String>itemProcessor() {
return item -> item + " now processor!!";
}

processor์—์„œ๋Š” ์•ž์„œ ๋งํ•œ๊ฒƒ๊ณผ ๊ฐ™์ดย <reader์—์„œ ์ฝ์€ ํƒ€์ž…, writer์— ๋ณด๋‚ผ ํƒ€์ž…>ย ์œผ๋กœ ์ •์˜ํ•ด ์ฒ˜๋ฆฌํ•œ๋‹ค.

์ง€๊ธˆ์€ ๊ทธ๋ƒฅ ์ฝ์€ item์— ๋ฌธ์ž์—ด๋งŒ ๋”ํ•ด์„œ ์ฒ˜๋ฆฌ.

  • writer
private ItemWriter<String>itemWriter() {
return items -> log.info("### writer : " + items.toString());
}

writer์—์„œ ์ด์ง€๋งŒ,ย List๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ๋˜๋ฉฐ,

items๋ฅผ ๋ฐ›์•„ log๋ฅผ ์ถœ๋ ฅํ•˜๋ฉด chunksize=10๊ฐœ์”ฉ ์ฝ์€ String์ด List๋กœ ์ถœ๋ ฅ๋œ๋‹ค.

๊ฒฐ๊ณผ :

writer : [0 test! now processor!!, 1 test! now processor!!, ... 9 test! now processor!!]
writer : [10 test! now processor!!, 11 test! now processor!!, ... 19 test! now processor!!]
...
writer : [90 test! now processor!!, 91 test! now processor!!, ... 99 test! now processor!!]

log๋ฅผ ์ด 10๊ฐœ ์ถœ๋ ฅํ–ˆ์œผ๋ฉฐ, ๊ฐ ๋กœ๊ทธ๋Š” items๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

reader์—์„œ 10๊ฐœ์”ฉ ์ฝ๊ณ  List ์œผ๋กœ items ๊ฐ€ writer์—์„œ chunksize=10๋งŒํผ ์ฒ˜๋ฆฌ๋œ๋‹ค.

Tasklet์œผ๋กœ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ

@Bean
public JobtestTaskChunkJob(){
return jobBuilderFactory.get(JOBNAME)
                .incrementer(new RunIdIncrementer())
                .start(this.taskStep1())
                .next(this.chunkStep1())
                .next(this.taskPagingStep1())//tasklet์œผ๋กœ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ
                .build();
    }

//...

private TaskletpagingTasklet() {
        List<String> items = getItems();

return (contribution, chunkContext) -> {
//stepexecution : ์ฝ์€ item์„ ์ €์žฅ
            StepExecution stepExecution = contribution.getStepExecution();
int chunksize = 10;
int readCnt = stepExecution.getReadCount();
int idx = readCnt + chunksize;

if(idx > items.size()){
return RepeatStatus.FINISHED;
            }

//sublist : list ์ค‘๊ฐ„ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ
            List<String> sublist = items.subList(readCnt,idx);

            log.info("### sublist size : " + sublist.toString());
            stepExecution.setReadCount(idx);//read count ์ฝ์€๋งŒํผ ๋‹ค์‹œ ์ €์žฅreturn RepeatStatus.CONTINUABLE;
        };
    }

tasklet์˜ contributution์—์„œ StepExecution ์œผ๋กœ step์ด ์–ผ๋งˆ๋‚˜ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ๋Š”์ง€์˜ ์ •๋ณด๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

(chunksize๋ฅผ 10์œผ๋กœ ์ •์˜ํ•˜๊ณ  readCount๋กœ ํŽ˜์ด์ง•์„ ์ฒ˜๋ฆฌ)

tasklet์œผ๋กœ chunksize=10์œผ๋กœ ์ •ํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์†Œ์Šค๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๋ณต์žกํ•ด์ง์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๊ฒฐ๊ณผ :

sublist size : [0 test!, 1 test!, 2 test!, ... 9 test!]
sublist size : [10 test!, 11 test!, 12 test!, ... 19 test!]
...
sublist size : [90 test!, 91 test!, 92 test!, ... 99 test!]
๐Ÿ’กSpring Batch + Job ์‹คํ–‰ ์˜ˆ์ œ

REST ๋ฐฉ์‹์œผ๋กœ ์žก ์‹คํ–‰

Controller

Kotlin

@RestController
class RestJobController(
    @Autowired
    private val jobLauncher: JobLauncher,
    @Autowired
    private val jobExplorer: JobExplorer,
    @Qualifier("job1") @Autowired
    private val job: Job,  // Job ๋นˆ์„ ์ง์ ‘ ์ฃผ์ž…
) {
    @PostMapping(path = ["/run"])
    @Throws(Exception::class)
    fun runJob(@RequestBody request: RestJobDto): ExitStatus {
// JobParametersBuilder ์˜ getNextJobParameters ๋ฉ”์„œ๋“œ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ฆ๊ฐ€ ์‹œํ‚จ๋‹ค ex) run.id=1 ์ดํ›„ run.id=2 ..
        val jobParameters = JobParametersBuilder(
            request.getJobParameters(),
            jobExplorer
        )
            .getNextJobParameters(job)
            .toJobParameters()
        return jobLauncher.run(job, jobParameters).exitStatus
    }
}

Java

@RestController
public class RestJobController {
    private JobLauncher jobLauncher;
    private JobExplorer jobExplorer;
    private Job job;

    @Autowired
    public RestJobController(
        JobLauncher jobLauncher,
        JobExplorer jobExplorer,
        @Qualifier("job1") Job job
    ) {
        this.jobLauncher = jobLauncher;
        this.jobExplorer = jobExplorer;
        this.job = job;
    }

    @PostMapping(path = ["/run"])
    public ExitStatus runJob(@RequestBody RestJobDto request) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder(
            request.getJobParameters(),
            jobExplorer
        )
        .getNextJobParameters(job)
        .toJobParameters();

        return jobLauncher.run(job, jobParameters).getExitStatus();
    }
}

Dto

Kotlin

data class RestJobDto(
    @JsonProperty("name")
    var name: String? = null,
    @JsonProperty("jobParameters")
    var jobParamsProperties: Properties? = null
) {
    fun getJobParameters(): JobParameters {
        val jobParametersBuilder = JobParametersBuilder()
        // jobParamsProperties๊ฐ€ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์ถ”๊ฐ€
        if (jobParamsProperties != null) {
            for ((key, value) in jobParamsProperties!!) {
                jobParametersBuilder.addString(key.toString(), value.toString())
            }
        }
        return jobParametersBuilder.toJobParameters()
    }
}

Java

@Data
@NoArgsConstructor
public class RestJobDto {
    @JsonProperty("name")
    private String name;
    @JsonProperty("jobParameters")
    private Properties jobParamsProperties;

    public JobParameters getJobParameters() {
        JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
        
        // jobParamsProperties๊ฐ€ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์ถ”๊ฐ€
        if (jobParamsProperties != null) {
            for (Entry<Object, Object> entry : jobParamsProperties.entrySet()) {
                jobParametersBuilder.addString(entry.getKey().toString(), entry.getValue().toString());
            }
        }

        return jobParametersBuilder.toJobParameters();
    }
}

Job

์ „์†ก ๋ฐ์ดํ„ฐ :

*{ 
	"name" : "job",
	"jobParameters" : { 
			"foo": "bar",
		  "baz": "quix",
		  "requestDate":"20231108" 
			}
}*

Kotlin

@Configuration
class RestJob1 {
    private final val chunkSize = 5

    @Bean(name = ["job1"])
    fun textJob1_batchBuild(jobRepository: JobRepository, transactionManager: PlatformTransactionManager): Job {
        return JobBuilder("job", jobRepository)
            // "Job"์—๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ job parameters๋ฅผ ์ƒ์„ฑํ•˜๋Š” incrementer๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด incrementer๋Š” batch job์˜ ์‹คํ–‰์„ ์‹๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
            .incrementer(RunIdIncrementer())
            .start(textJob1_batchStep1(jobRepository, transactionManager))
            .build()
    }

    @Bean
    @Primary
    fun textJob1_batchStep1(jobRepository: JobRepository, transactionManager: PlatformTransactionManager): Step {
        return StepBuilder("textJob1", jobRepository)
            .allowStartIfComplete(true) // ๋ฐ˜๋ณต ์ž‘์—…์„ ์œ„ํ•œ ์Šคํ… ์„ค์ •
            .chunk<OneDto, OneDto>(chunkSize, transactionManager)
            .reader(textJob1_FileReader(null))
            .writer { oneDto ->
                oneDto.forEach { item ->
                    println(item.one)
                }
            }
            .build()
    }

    @Bean
    @StepScope
    fun textJob1_FileReader(@Value("#{jobParameters[requestDate]}") requestDate: String?): FlatFileItemReader<OneDto> {
        val flatFileItemReader: FlatFileItemReader<OneDto> = FlatFileItemReader<OneDto>()
        flatFileItemReader.setResource(ClassPathResource("sample/textJob1_input.txt"))
        flatFileItemReader.setLineMapper { line, lineNumber -> OneDto("$lineNumber == $line ${requestDate} ") }
        return flatFileItemReader
    }
}

Java

@Configuration
public class RestJob1 extends DefaultBatchConfigurer {

    private final int chunkSize = 5;

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean(name = "job1")
    @Primary
    public Job textJob1_batchBuild(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return jobBuilderFactory.get("job")
                .incrementer(new RunIdIncrementer())
                .start(textJob1_batchStep1(jobRepository, transactionManager))
                .build();
    }

    @Bean
    public Step textJob1_batchStep1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return stepBuilderFactory.get("textJob1")
                .allowStartIfComplete(true)
                .<OneDto, OneDto>chunk(chunkSize)
                .reader(textJob1_FileReader(null))
                .writer(oneDtoList -> oneDtoList.forEach(item -> System.out.println(item.getOne())))
                .build();
    }

    @Bean
    @StepScope
    public FlatFileItemReader<OneDto> textJob1_FileReader(@Value("#{jobParameters[requestDate]}") String requestDate) {
        FlatFileItemReader<OneDto> flatFileItemReader = new FlatFileItemReader<>();
        flatFileItemReader.setResource(new ClassPathResource("sample/textJob1_input.txt"));

        FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();
        tokenizer.setColumns(new Range[] { new Range(1, 4), new Range(5, 8) });
        tokenizer.setNames("one", "two");
        tokenizer.setStrict(false);

        flatFileItemReader.setLineMapper((line, lineNumber) -> {
            OneDto oneDto = new OneDto();
            oneDto.setOne(line.substring(0, 4));
            oneDto.setTwo(line.substring(4, 8));
            oneDto.setRequestDate(requestDate);
            return oneDto;
        });

        return flatFileItemReader;
    }
}

@RestController
class RestJobController {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private JobExplorer jobExplorer;

    @Qualifier("job1")
    @Autowired
    private Job job;

    @PostMapping(path = "/run")
    public ExitStatus runJob(@RequestBody RestJobDto request) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder(request.getJobParameters(), jobExplorer)
                .getNextJobParameters(job)
                .toJobParameters();
        return jobLauncher.run(job, jobParameters).getExitStatus();
    }
}
๐Ÿช“SQLSyntaxErrorException ์—๋Ÿฌ ํ•ด๊ฒฐ

๊ธฐ๋ณธ์ ์œผ๋กœ H2 DB๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—” Boot๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ์ง€๋งŒ,ย MySQL์ด๋‚˜ Oracle๊ณผ ๊ฐ™์€ DB๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.ย ๋‚˜๋Š” MySQL์„ ์‚ฌ์šฉํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฐ์น˜๊ฐ€ ์‹คํŒจํ•œ ๊ฒƒ์ด๋‹ค.

MySQL์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ฉ”ํƒ€ ํ…Œ์ด๋ธ”์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๋ฉด ๋œ๋‹ค.

์•„๊นŒ ์ถ”๊ฐ€ํ•œ Spring Batch ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— org.springframework.batch.core ํด๋”์— ๊ฐ€๋ณด๋ฉด schema-mysql.sql ํŒŒ์ผ์ด ์žˆ์„ ๊ฒƒ์ด๋‹ค.


์ด ํŒŒ์ผ์— ์ž‘์„ฑ๋œ SQL์„ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋ฉด ๋ฉ”ํƒ€ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

์ƒ์„ฑ ํ›„์— ๋‹ค์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•ด๋ณด๋ฉด!


๋งˆ๋ฌด๋ฆฌ

์Šคํ”„๋ง ๋ฐฐ์น˜๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š”๋ฐ deprecated ๋œ JobBuilderFactory์„ ์‚ฌ์šฉํ•œ ์‚ฌ๋ก€๊ฐ€ ๋งŽ์•˜๋‹ค. ๋งŽ์€ ๊ฒ€์ƒ‰์„ ํ†ตํ•ด JobRepository๊ณผ ๊ด€๋ จ๋œ ์ •๋ณด๋ฅผ ์ฐพ์•˜๊ณ , ๋‹ค์Œ์— ๋˜ ์ฐพ์œผ๋ ค๋ฉด ๋งŽ์€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์•„์„œ ๋ฒจ๋กœ๊ทธ์— ๊ธฐ๋กํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฝ”ํ‹€๋ฆฐ ๋ฒ„์ „์œผ๋กœ๋„ ์ž‘์„ฑํ•ด๋‘๋ฉด ๋‹ค์Œ์— ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์•„์„œ ๊นƒํ—ˆ๋ธŒ์— ์ž‘์„ฑํ•ด๋‘์—ˆ๋‹ค.

๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์„ ์Šคํ”„๋ง ๋ฐฐ์น˜๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„์ง€๋„ ๋ชจ๋ฅธ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ์ถ”ํ›„ ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”์ผ์ด๋‚˜ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์ด ์žˆ๋Š”์ผ์ด ์žˆ๋‹ค๋ฉด ์Šคํ”„๋ง ๋ฐฐ์น˜๋ฅผ ๋– ์˜ฌ๋ ค ํ•ด๊ฒฐํ•ด ๋‚˜๊ฐ€๋ฉด ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค. ์•„์ง ์ง์ ‘์ ์ธ ํ•ด๊ฒฐ์„ ํ•ด๋ณธ์ ์ด ๋งŽ์ด ์—†๋‹ค..


์ถœ์ฒ˜ : https://velog.io/@cho876/Spring-Batch-job-%EC%83%9D%EC%84%B1 - [Spring Batch] ๊ฐœ๋… ๋ฐ Job ์ƒ์„ฑ

์ถœ์ฒ˜ : https://choisblog.tistory.com/80 - Spring Batch - Tasklet, Chunk ๊ฐ„๋‹จ ๋น„๊ต

์ถœ์ฒ˜ : https://velog.io/@clevekim/Spring-Batch%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80 - Spring Batch๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2024๋…„ 6์›” 10์ผ

๋„์›€์ด ๋˜์—ˆ์–ด์š”!๐Ÿ˜

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ