[spring batch] ์Šคํ”„๋ง ๋ฐฐ์น˜ ์ดํ•ดํ•˜๊ณ  ์ ์šฉํ•˜๊ธฐ

๊ฐ์ข… ๊ณต์—ฐ์„ ์˜ˆ๋งคํ•˜๊ณ  ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์—ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์˜ˆ๋งคํ•œ ๊ณต์—ฐ์˜ ์‹œ์ž‘ ํ•˜๋ฃจ ์ „ ํšŒ์›๋“ค์—๊ฒŒ ์ผ๊ด„์ ์œผ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. 

์นด์นด์˜คํ†ก ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋ ค ํ–ˆ์ง€๋งŒ ์‚ฌ์—…์ž ๋ฒˆํ˜ธ๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ์•„์‰ฌ์šด๋ฐ๋กœ sms ์ „์†ก์œผ๋กœ ๋Œ€์ฒดํ–ˆ๋‹ค.

 

sms push ์•Œ๋ฆผ์€ naver cloud์—์„œ ์ œ๊ณตํ•˜๋Š” sens๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. coolsms๊ฐ€ ์ œ์ผ ๋ณดํŽธ์ ์œผ๋กœ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ฒƒ ๊ฐ™๋˜๋ฐ ํ›จ์”ฌ ์ €๋ ดํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. (์ง€๊ธˆ์€ ์‚ฌ์—…์ž๋งŒ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋„ค์ด๋ฒ„์—์„œ ๋ง‰์Œ.. ์ŠคํŽจ๋ฌธ์ œ)

 

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

 

์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” ์–ด๋–ค ์ž‘์—…์— ์“ฐ์ผ๊นŒ?

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

 

์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ์™€ ํ˜ผ๋™ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ ์ „ํ˜€ ๋‹ค๋ฅธ ๊ฐœ๋…์ด๋‹ค. ๋‘˜์˜ ์ฐจ์ด๋Š” ๋‹ค๋ฅธ ๊ธ€์—์„œ ๋‹ค๋ฃจ๊ฒ ๋‹ค.

https://dgjinsu.tistory.com/20

 

 

 

Spring batch ์šฉ์–ด ์ •๋ฆฌ

 

  • Job
    • ๋ฐฐ์น˜์ฒ˜๋ฆฌ ๊ณผ์ •์„ ํ•˜๋‚˜์˜ ๋‹จ์œ„๋กœ ๋งŒ๋“ค์–ด ๋†“์€ ๊ฐ์ฒด.
    • ์ „์ฒด ๋ฐฐ์น˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์บก์Šํ™”ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ, ์‹คํ–‰์‹œํ‚ฌ ์ž‘์—….
  • JobInstance
    • ๋…ผ๋ฆฌ์ ์ธ Job ์˜ ์‹คํ–‰์˜ ๋‹จ์œ„.
    • Job ์„ ์‹คํ–‰์‹œํ‚ค๋ฉด ํ•˜๋‚˜์˜ JobInstance ๊ฐ€ ์ƒ์„ฑ.
      • ์˜ˆ๋ฅผ๋“ค์–ด, 1์›” 1์ผ ์‹คํ–‰, 1์›” 2์ผ ์‹คํ–‰์„ ํ•˜๊ฒŒ ๋˜๋ฉด ๊ฐ๊ฐ์˜ JobInstance ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๋งŒ์•ฝ 1์›” 1์ผ์— ์‹คํ–‰ํ•œ JobInstance ๊ฐ€ ์‹คํŒจํ•˜์—ฌ ์ดํ›„ ์žฌ์‹คํ–‰ ํ•˜๋”๋ผ๋„ ์ด JobInstance ๋Š” 1์›” 1์ผ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ฒ˜๋ฆฌ.
  • JobParameters
    • Job ์„ ์‹คํ–‰ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ง‘ํ•ฉ์œผ๋กœ, Job ์ด ์‹คํ–‰๋˜๋Š” ๋™์•ˆ์— JobInstance ์˜ ์‹๋ณ„ ์™ธ์—๋„ ๋งค๊ฐœ๋ณ€์ˆ˜ ์—ญํ• ๋กœ ์‚ฌ์šฉ.
  • JobExecution
    • JobInstance ์—์„œ ์‹คํ–‰ ์‹œ๋„์— ๋Œ€ํ•œ ๊ฐ์ฒด.
    • ์‹คํ–‰์— ๋Œ€ํ•œ JobExecution ์€ ๊ฐœ๋ณ„๋กœ ์ƒ์„ฑ.
    • JobInstance ์‹คํ–‰์— ๋Œ€ํ•œ ์ƒํƒœ, ์‹œ์ž‘์‹œ๊ฐ„, ์ข…๋ฃŒ์‹œ๊ฐ„, ์ƒ์„ฑ์‹œ๊ฐ„ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ง.
      • ์˜ˆ๋ฅผ๋“ค์–ด, 1์›” 1์ผ์— ์‹คํ–‰ํ•œ JobInstance ๊ฐ€ ์‹คํŒจํ•˜์—ฌ ์žฌ์‹คํ–‰์„ ํ•˜์—ฌ๋„ ๋™์ผํ•œ JobInstance ๋ฅผ ์‹คํ–‰์‹œํ‚ค์ง€๋งŒ ์ด 2๋ฒˆ์— ์‹คํ–‰์— ๋Œ€ํ•œ Job Execution ์€ ๊ฐœ๋ณ„๋กœ ์ƒ์„ฑ.
  • Step
    • Job ์˜ ๋ฐฐ์น˜์ฒ˜๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ  ์ˆœ์ฐจ์ ์ธ ๋‹จ๊ณ„๋ฅผ ์บก์Šํ™”.
    • Job ์€ ์ตœ์†Œ 1๊ฐœ ์ด์ƒ์˜ Step ์„ ๊ฐ€์ ธ์•ผ ํ•˜๊ณ , Job ์˜ ์‹ค์ œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ชจ๋“  ์ •๋ณด๋ฅผ ํฌํ•จ.
    • Step ์˜ ๋‚ด์šฉ์€ ๊ฐœ๋ฐœ์ž์˜ ์„ ํƒ์— ๋”ฐ๋ผ ๊ตฌ์„ฑ.
    • Tasklet ์ฒ˜๋ฆฌ ๋ฐฉ์‹๊ณผ Chunk ์ง€ํ–ฅ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์„ ์ง€์›
      • Tasklet
        • ๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ ํ”„๋กœ์„ธ์Šค ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•œ ๋ชจ๋ธ.
        • SQL 1ํšŒ ๋ช…๋ น ๋“ฑ ๋‹จ์ˆœํ•˜๊ฑฐ๋‚˜, ์ž‘์—… ํ”„๋กœ์„ธ์Šค์˜ ํ‘œ์ค€ํ™”๊ฐ€ ์–ด๋ ค์šด ๋ณต์žกํ•œ ๊ฒฝ์šฐ์— Custom ์ž‘์—… ์ƒ์„ฑ์„ ์œ„ํ•ด ์‚ฌ์šฉ.
      • Chunk
        • ๋ฉ”๋ชจ๋ฆฌ์— ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋„ˆ๋ฌด ๋งŽ๊ณ , ํฐ ๋ฐ์ดํ„ฐ ๋“ค์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์ ํ•ฉ.
        • ์ผ์ •์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ผ๊ด„์ ์œผ๋กœ read / process / write ํ”„๋กœ์„ธ์Šค ํ๋ฆ„์— ๋”ฐ๋ผ ํ‘œ์ค€ํ™”ํ•˜์—ฌ ์ž‘์—…์„ ๊ตฌํ˜„.
        • ํ•˜๋‚˜์˜ Transaction ์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•  Item ์˜ ๋ฉ์–ด๋ฆฌ.
        • chunk size ๊ฐ€ 10์ด๋ผ๋ฉด ํ•˜๋‚˜์˜ transaction ์•ˆ์—์„œ 10๊ฐœ์˜ item ์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌ ํ•˜๊ณ  commit.
  • StepExecution
    • JobExceution ๊ณผ ๋™์ผํ•˜๊ฒŒ Step ์‹คํ–‰ ์‹œ๋„์— ๋Œ€ํ•œ ๊ฐ์ฒด.
    • ์‹ค์ œ ์‹œ์ž‘์ด ๋  ๋•Œ ์ƒ์„ฑ.
    • ์‹œ์ž‘ ์‹œ๊ฐ„, ์ข…๋ฃŒ ์‹œ๊ฐ„, ์ƒํƒœ, ์ข…๋ฃŒ ์ƒํƒœ, Commit Count, ItemCount, Skip Count ๋“ฑ ์‹คํ–‰์— ๋Œ€ํ•œ ๋‹ค์–‘ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ง.
      • ์˜ˆ๋ฅผ๋“ค์–ด, Job ์ด ์—ฌ๋Ÿฌ ๋‹จ๊ณ„์˜ Step ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ ์ด์ „ ๋‹จ๊ณ„์˜ Step ์ด ์‹คํŒจํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์Œ์œผ๋กœ ์‹คํŒจ ์ดํ›„ StepExecution ์€ ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ.
  • ExecutionContext
    • Job ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ  ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ.
    • Spring Batch ์—์„œ๋Š” JobExecutionContext, StepExecutionContext ๋ฅผ ์ง€์›ํ•œ๋‹ค.
      • JobExecutionContext
        • Commit ์‹œ์ ์— ์ €์žฅ
      • StepExecutionContext
        • ์‹คํ–‰ ์‚ฌ์ด์— ์ €์žฅ ExecutionContext ๋ฅผ ํ†ตํ•ด Step ๊ฐ„ Data ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , Job ์‹คํŒจ์‹œ ExecutionContext ๋ฅผ ํ†ตํ•ด ๋งˆ์ง€๋ง‰ ์‹คํ–‰ ๊ฐ’์„ ์žฌ๊ตฌ์„ฑ ํ•  ์ˆ˜ ์žˆ์Œ.
  • JobRepository
    • ์ˆ˜ํ–‰๋˜๋Š” Job ์˜ ๋ชจ๋“  ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ์ €์žฅ์†Œ.
    • ์–ด๋– ํ•œ Job ์ด ์–ธ์ œ ์ˆ˜ํ–‰๋˜์—ˆ๊ณ , ์–ธ์ œ ์ข…๋ฃŒํ•˜๊ณ , ๋ช‡ ๋ฒˆ ์‹คํ–‰๋˜์—ˆ๊ณ , ์‹คํ–‰์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์–ด๋–ค์ง€ ๋“ฑ์˜ Batch ์ˆ˜ํ–‰๊ณผ ๊ณผ๋ จ๋œ ๋ชจ๋“  Meta Data ๊ฐ€ ์ €์žฅ.
    • Job ์ด ์‹คํ–‰๋˜๊ฒŒ ๋˜๋ฉด JobRepository ์— JobExecution ๊ณผ StepExecution ์„ ์ƒ์„ฑํ•˜๊ณ , JobRepository ์—์„œ Execution ์ •๋ณด๋“ค์„ ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋ฉฐ ์‚ฌ์šฉ.

 

 

Meta-Table Schema

- Spring Batch ์—์„œ๋Š” 6๊ฐœ์˜ Meta Table ๊ณผ 3๊ฐœ์˜ Sequence Table ์ด ์กด์žฌํ•œ๋‹ค.

- ์ž‘์—…์ด ์ˆ˜ํ–‰๋  ๋•Œ ๋งˆ๋‹ค ์‹คํ–‰๋œ Job ์— ๋Œ€ํ•œ ๋‹ค์–‘ํ•œ ์ •๋ณด๋“ค์„ ์ €์žฅํ•˜์—ฌ ๊ด€๋ฆฌํ•œ๋‹ค.

- ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” Meta Table ์ด ์—†์œผ๋ฉด Spring Batch ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์—†๋‹ค.

 

 

Spring Batch 5 ๋ณ€๊ฒฝ์ 

 

Spring Boot 3(=Spring Framework 6)๋ถ€ํ„ฐ Spring Batch 5 ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ ๋˜์—ˆ๋‹ค. Batch 5์— ๋ณ€๊ฒฝ์ ์ด ๋งŽ์ด ์ƒ๊ฒจ ๊ธฐ์กด์˜ 4๋ฒ„์ „๊ณผ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ๋งŽ์ด ์ƒ๊ฒผ๋‹ค. ์ฒ˜์Œ ๋ฐฐ์น˜๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๋Š”๋ฐ Spring Batch5 ๋ฒ„์ „์˜ ์ฐธ๊ณ ํ•  ์ž๋ฃŒ๊ฐ€ ๋งŽ์ด ์—†์–ด ๊ณ ์ƒํ–ˆ๋‹ค. 

 

๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ๋ฐœ๊ฒฌํ•œ ๋ณ€๊ฒฝ์ ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

1. JobBuilderFactory, StepBuilderFactory deprecated

// Sample with v4
@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Bean
    public Job myJob(Step step) {
        return this.jobBuilderFactory.get("myJob")
                .start(step)
                .build();
    }
}
// Sample with v5
@Configuration
@EnableBatchProcessing
public class MyJobConfig {

    @Bean
    public Job myJob(JobRepository jobRepository, Step step) {
        return new JobBuilder("myJob", jobRepository)
                .start(step)
                .build();
    }
}

 

 

2. JobRepository, TransactionManager ๋ช…์‹œ์ ์œผ๋กœ ๋ณ€๊ฒฝ

 

๋‚ด๋ถ€์ ์œผ๋กœ ํ•„์š”ํ•œ ๊ฐ์ฒด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œ์‹œํ•ด ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค

// Sample with v4
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step myStep() {
        return this.stepBuilderFactory.get("myStep")
                .tasklet(..) // or .chunk()
                .build();
    }
}
// Sample with v5
@Configuration
@EnableBatchProcessing
public class MyStepConfig {

    @Bean
    public Tasklet myTasklet() {
       return new MyTasklet();
    }

    @Bean
    public Step myStep(JobRepository jobRepository, Tasklet myTasklet, PlatformTransactionManager transactionManager) {
        return new StepBuilder("myStep", jobRepository)
                .tasklet(myTasklet, transactionManager) // or .chunk(chunkSize, transactionManager)
                .build();
    }
}

 

 

3. JobParameter ์ง€์› ๋ฒ”์œ„ ํ™•๋Œ€

 

v4 ์—์„œ์˜ ์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” Job parameter ๋กœ Long, String, Date, Double ๋งŒ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

v5 ์—์„œ๋Š” ์—ฌ๊ธฐ์— ๋”ํ•ด converter ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ชจ๋“  ์ข…๋ฅ˜์˜ ํƒ€์ž…์„ JobParameter ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„ ๋๋‹ค. 

 

 

 

 

๋ณธ๊ฒฉ์ ์œผ๋กœ ์•Œ๋ฆผ ์ผ๊ด„์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ๋ฐœํ•ด๋ณด์ž

 

SmsJobConfiguration

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SmsJobConfiguration {
    private final SmsService smsService;
    private final EntityManagerFactory entityManagerFactory;
    @Bean @Qualifier(value = "smsSendJob")
    public Job smsSendJob(JobRepository jobRepository, Step smsSendStep) {
        return new JobBuilder("smsSendJob", jobRepository)
                .start(smsSendStep)
                .build();
    }
    @Bean
    public Step smsSendStep(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager){
        return new StepBuilder("smsSendStep", jobRepository)
                .<Ticketing, Ticketing>chunk(100, platformTransactionManager)
                .reader(itemReader())
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

    @Bean
    public ItemReader<Ticketing> itemReader() {
        LocalDateTime tomorrow  = LocalDateTime.now().plusDays(1);

        // DateTimeFormatter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํฌ๋งท ์ง€์ •
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 00:00๋กœ ์„ค์ •
        LocalDateTime tomorrowStart = tomorrow.withHour(0).withMinute(0).withSecond(0).withNano(0);
        // 23:59๋กœ ์„ค์ •
        LocalDateTime tomorrowEnd = tomorrow.withHour(23).withMinute(59);

        return new JpaCursorItemReaderBuilder<Ticketing>()
                .name("itemReader")
                .entityManagerFactory(entityManagerFactory)
                .queryString("select t from Ticketing t join fetch t.member m where t.event.startEvent between :tomorrowStart and :tomorrowEnd")
                .parameterValues(Map.of("tomorrowStart", tomorrowStart, "tomorrowEnd", tomorrowEnd))
                .build();
    }

    @Bean
    public ItemProcessor<Ticketing, Ticketing> itemProcessor() {
        return ticketing -> {
            Member member = ticketing.getMember();
            String eventName = ticketing.getEvent().getName();
            LocalDateTime startEvent = ticketing.getEvent().getStartEvent();

            int month = startEvent.getMonthValue(); // ์›”(month) ์ถ”์ถœ
            int day = startEvent.getDayOfMonth();   // ์ผ(day) ์ถ”์ถœ
            int hour = startEvent.getHour();        // ์‹œ(hour) ์ถ”์ถœ
            int minute = startEvent.getMinute();    // ๋ถ„(minute) ์ถ”์ถœ
            String content = month + "์›”" + day + "์ผ " + hour + "์‹œ" + minute + "๋ถ„์— " + eventName + "์ด ์˜ˆ๋งค๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์— ๋งž์ถฐ ๋ฐฉ๋ฌธํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.";
            log.info(member.getPhone() + " ํ•ด๋‹น ์ „ํ™”๋ฒˆํ˜ธ๋กœ [" + content + "] ๋ฌธ์ž ๋ฐœ์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
            smsService.sendSms(member.getPhone(), content);
            return ticketing;
        };
    }

    @Bean
    public JpaItemWriter<Ticketing> itemWriter() {
        return new JpaItemWriterBuilder<Ticketing>()
                .entityManagerFactory(entityManagerFactory)
                .build();
    }
}

 

์ „์ฒด ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

 

 

ํ•˜๋‚˜์”ฉ ๋œฏ์–ด์„œ ๋ณด๋ฉด

 

- Job์„ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๋ฉฐ Step์„ Job์— ๋“ฑ๋ก

@Bean @Qualifier(value = "smsSendJob")
public Job smsSendJob(JobRepository jobRepository, Step smsSendStep) {
    return new JobBuilder("smsSendJob", jobRepository)
            .start(smsSendStep)
            .build();
}

 

 

- Step ์ •์˜ (reader, processor, writer ์„ธํŒ…)

@Bean
public Step smsSendStep(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager){
    return new StepBuilder("smsSendStep", jobRepository)
            .<Ticketing, Ticketing>chunk(5, platformTransactionManager)
            .reader(itemReader())
            .processor(itemProcessor())
            .writer(itemWriter())
            .build();
}

 

 

- reader

- JpaCursorItemReaderBuilder๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์กฐ๊ฑด์— ๋งž๋Š” item ์กฐํšŒ

@Bean
public ItemReader<Ticketing> itemReader() {
    LocalDateTime tomorrow  = LocalDateTime.now().plusDays(1);

    // DateTimeFormatter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํฌ๋งท ์ง€์ •
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // 00:00๋กœ ์„ค์ •
    LocalDateTime tomorrowStart = tomorrow.withHour(0).withMinute(0).withSecond(0).withNano(0);
    // 23:59๋กœ ์„ค์ •
    LocalDateTime tomorrowEnd = tomorrow.withHour(23).withMinute(59);

    return new JpaCursorItemReaderBuilder<Ticketing>()
            .name("itemReader")
            .entityManagerFactory(entityManagerFactory)
            .queryString("select t from Ticketing t join fetch t.member m where t.event.startEvent between :tomorrowStart and :tomorrowEnd")
            .parameterValues(Map.of("tomorrowStart", tomorrowStart, "tomorrowEnd", tomorrowEnd))
            .build();
}

 

 

- processor

- reader์—์„œ ์ฝ์–ด๋“œ๋ฆฐ item์„ ํ™œ์šฉํ•ด ํ•„์š”ํ•œ ๋กœ์ง ์ˆ˜ํ–‰.

- ๋ฌธ์ž๋ฅผ ๋ณด๋‚ด๋Š” ๋กœ์ง์ด ์—ฌ๊ธฐ์— ๋“ค์–ด๊ฐ„๋‹ค

@Bean
    public ItemProcessor<Ticketing, Ticketing> itemProcessor() {
        return ticketing -> {
            Member member = ticketing.getMember();
            String eventName = ticketing.getEvent().getName();
            LocalDateTime startEvent = ticketing.getEvent().getStartEvent();

            int month = startEvent.getMonthValue(); // ์›”(month) ์ถ”์ถœ
            int day = startEvent.getDayOfMonth();   // ์ผ(day) ์ถ”์ถœ
            int hour = startEvent.getHour();        // ์‹œ(hour) ์ถ”์ถœ
            int minute = startEvent.getMinute();    // ๋ถ„(minute) ์ถ”์ถœ
            String content = month + "์›”" + day + "์ผ " + hour + "์‹œ" + minute + "๋ถ„์— " + eventName + "์ด ์˜ˆ๋งค๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์— ๋งž์ถฐ ๋ฐฉ๋ฌธํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.";
            log.info(member.getPhone() + " ํ•ด๋‹น ์ „ํ™”๋ฒˆํ˜ธ๋กœ [" + content + "] ๋ฌธ์ž ๋ฐœ์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
            smsService.sendSms(member.getPhone(), content);
            return ticketing;
        };
    }

 

 

 

- writer

- ๋ณ€๊ฒฝ๋œ item๋“ค์„ ๋‹ค์‹œ ์ €์žฅํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. 

- ๋‚˜๋Š” item๋“ค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋”ฐ๋กœ ์ˆ˜์ •ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ €์žฅํ•  ๊ฑด ์—†๋‹ค.

@Bean
public JpaItemWriter<Ticketing> itemWriter() {
    return new JpaItemWriterBuilder<Ticketing>()
            .entityManagerFactory(entityManagerFactory)
            .build();
}

 


 

 

์ด๋ ‡๊ฒŒ ์™„์„ฑํ•œ Batch ๋กœ์ง์„ Sheduler์— ๋“ฑ๋กํ•˜์—ฌ ์›ํ•˜๋Š” ์‹œ๊ฐ„๋Œ€์— ์‹คํ–‰ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. 

 

ScheduledJob

    @Scheduled(cron = "0 0 20 * * *", zone = "Asia/Seoul") // ์ดˆ ๋ถ„ ์‹œ ์ผ ์›” ์š”์ผ
    public void sendReminderMessages() {
        Map<String, JobParameter<?>> confMap = new HashMap<>();
        confMap.put("time", new JobParameter(System.currentTimeMillis(), Long.class));
        JobParameters jobParameters = new JobParameters(confMap);

        try {
            jobLauncher.run(SmsSendJob, jobParameters);
            jobLauncher.run(alarmJob, jobParameters);
        } catch (JobExecutionAlreadyRunningException e) {
            throw new RuntimeException(e);
        } catch (JobRestartException e) {
            throw new RuntimeException(e);
        } catch (JobInstanceAlreadyCompleteException e) {
            throw new RuntimeException(e);
        } catch (JobParametersInvalidException e) {
            throw new RuntimeException(e);
        }
        log.info("์Šค์ผ€์ค„๋Ÿฌ ์‹คํ–‰");
    }

 

JobParameter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ  ์ค‘ ํ•˜๋‚˜๋Š” Spring Batch๊ฐ€ ๊ฐ ์ž‘์—…์„ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ด๋‹ค.

Spring Batch๋Š” ๊ฐ ์ž‘์—… ์‹คํ–‰์— ๋Œ€ํ•œ ์œ ์ผํ•œ ํ‚ค๋ฅผ ๊ฐ€์ ธ์•ผ ํ•˜๋ฉฐ, ์ด๋Ÿฌํ•œ ํ‚ค๋Š” JobParameters์— ์˜ํ•ด ์ œ๊ณต๋œ๋‹ค.