๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ฐœ๋ฐœ/Spring

Spring batch - JobInstanceAlreadyCompleteException ํ•ด๊ฒฐ

RunIdIncrementer - ์™„์ „ํžˆ uniqueํ•˜์ง€ ์•Š๋‹ค.

JobInstanceAlreadyCompleteException ํ˜น์€ JobExecutionAlreadyRunningException์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ

batch job์˜ run.id ์™€ job parameter๊ฐ€ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์ผ ํ™•๋ฅ ์ด ํฌ๋‹ค.

 

์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด spring batch์—์„œ run.id๋ฅผ ์ž๋™์œผ๋กœ 1์”ฉ ์ฆ๊ฐ€์‹œ์ผœ ๋™์ผํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•œ๋‹ค.

    @Bean
    public Job myBatchJob() {
        return jobBuilderFactory.get("myBatchJob")
                .incrementer(new RunIdIncrementer())  // RunIdIncrementer ์ ์šฉ
                .start(myStep())
                .build();
    }

 

ํ•˜์ง€๋งŒ RunIdIncrementer๋Š” ์™„๋ฒฝํ•œ ๊ณ ์œ ์„ฑ์ด ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. 

๋™์ผํ•œ ์‹œ๊ธฐ์— ์—ฌ๋Ÿฌ ๋ฐฐ์น˜๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ ๋™์ผํ•œ run id๊ฐ€ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค.

 

 

Batch ์‹คํ–‰ ์ •๋ณด ์‚ดํŽด๋ณด๊ธฐ

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์„ ์ฐพ๊ธฐ์ „ run id์™€ job ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ ๋ฐฐ์น˜ ์‹คํ–‰ ์ •๋ณด๋ฅผ ์–ด๋–ค์‹์œผ๋กœ ์–ด๋””์— ์ €์žฅ๋˜์–ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ค.

 

๊ธฐ๋ณธ์ ์œผ๋กœ batch์˜ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋Š” ๋‚ด์žฅ H2 DB์— ์ €์žฅ๋œ๋‹ค. 

์ด DB๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ƒ์—์„œ ์šด์˜๋˜๋ฏ€๋กœ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ข…๋ฃŒ๋˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ์•„๊ฐ€๋ฒ„๋ฆฐ๋‹ค.

์ •๋ณด๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ spring batch application.yaml์— ๊ธฐ๋ณธ ์™ธ๋ถ€ DB๋ฅผ ์—ฐ๊ฒฐํ•˜๋ฉด ๋œ๋‹ค.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://your-mysql-host:3306/your-database
    username: your-username
    password: your-password

 

 

์ €์žฅ๋˜๋Š” ์ •๋ณด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

BATCH_JOB_INSTANCE : ๊ฐ ์ž‘์—… ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅ
BATCH_JOB_SEQ : 'BATCH_JOB_INSTANCE.JOB_INSTANCE_ID'์˜ ๊ณ ์œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ

BATCH_JOB_EXECUTION : ๊ฐ ๋ฐฐ์น˜ ์ž‘์—…์˜ ์‹คํ–‰์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅ, ์ž‘์—… ์‹คํ–‰์— ๋Œ€ํ•œ ์ƒํƒœ ๋ฐ ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ณดBATCH_JOB_EXECUTION_CONTEXT : ๊ฐ ์ž‘์—… ์‹คํ–‰์— ๋Œ€ํ•œ `ExecutionContext`์˜ ์ง๋ ฌํ™”๋œ ํ‘œํ˜„์„ ์ €์žฅBATCH_JOB_EXECUTION_PARAMS : ์ž‘์—… ์‹คํ–‰์— ์ „๋‹ฌ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ •๋ณด๋ฅผ ์ €์žฅ
BATCH_JOB_EXECUTION_SEQ : 'BATCH_JOB_EXECUTION.JOB_EXECUTION_ID'์˜ ๊ณ ์œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ

BATCH_STEP_EXECUTION : ๊ฐ ๋ฐฐ์น˜ ์Šคํ…์˜ ์‹คํ–‰์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅ, ์Šคํ… ์‹คํ–‰์— ๋Œ€ํ•œ ์ƒํƒœ ๋ฐ ํƒ€์ž„์Šคํƒฌํ”„ ์ •๋ณด
BATCH_STEP_EXECUTION_CONTEXT : ๊ฐ ์Šคํ… ์‹คํ–‰์— ๋Œ€ํ•œ `ExecutionContext`์˜ ์ง๋ ฌํ™”๋œ ํ‘œํ˜„์„ ์ €์žฅ
BATCH_STEP_EXECUTION_SEQ : 'BATCH_STEP_EXECUTION.STEP_EXECUTION_ID'์˜ ๊ณ ์œ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ

 

๊ฐ•์กฐํ•œ 3๊ฐ€์ง€ ํ…Œ์ด๋ธ”์˜ ๊ด€๊ณ„๋งŒ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด์ž๋ฉด

BATCH_JOB_INSTANCE์—๋Š” job์ด ์‹คํ–‰๋ ๋•Œ๋งˆ๋‹ค job instance id์™€ job name์ด ์‹ ๊ทœ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜์˜ job instance์—๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ job execution์ด ์žˆ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ (job์•ˆ์—์„œ execution์ด ์‹คํŒจ ํ›„ ์žฌ์‹œ๋„ ๋˜๊ฑฐ๋‚˜ chunk ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ ์ฒ˜๋Ÿผ ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋กœ ์ธํ•ด job execution์ด ์—ฌ๋Ÿฌ๊ฐœ ์ƒ์„ฑ๋˜๋Š” ๊ฒฝ์šฐ) job execution์— ๋Œ€ํ•œ ์ •๋ณด๋Š” BATCH_JOB_EXECUTION์— ์ €์žฅ๋œ๋‹ค.

๊ฐ execution์— ์‚ฌ์šฉ๋œ parameters๋Š” BATCH_JOB_EXECUTION_PARAMS์— ์ €์žฅ๋˜๋Š” ์‹์ด๋‹ค.

 

๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด DB์—์„œ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” run.id๋ฅผ ์กฐํšŒํ•ด๋ณด๊ณ  ์ด๋ฏธ ์‹คํ–‰๋œ ๊ธฐ๋ก์ด ์žˆ๋Š”์ง€ ์ฐพ์•„๋ณด์ž.

๋งŒ์•ฝ ์ด๋ฏธ ๊ธฐ๋ก์ด ์žˆ๋‹ค๋ฉด ์•„๋ž˜ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ€๋Šฅํ•  ๊ฒƒ์ด๊ณ  ์•„๋‹ˆ๋ผ๋ฉด ๋‹ค๋ฅธ ์ •๋ณด๋ฅผ ์ฐพ๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ด๋‹ค.

 

๊ณ ์œ ํ•œ parameter ์ž๋™ ์ƒ์„ฑ

๋ฌธ์ œ๋กœ ๋Œ์•„๊ฐ€์„œ JobInstanceAlreadyCompleteException ํ˜น์€ JobExecutionAlreadyRunningException์„ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ job ๋งˆ๋‹ค parameter๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

 

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์žˆ๋‹ค.

  • run.id๋ฅผ ์ปค์Šคํ…€ํ•˜๊ฒŒ ์„ค์ •
    : run.id๊ฐ€ ์ œ์—ญํ• ์„ ๋‹ค ํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•
    • JobLauncher๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ปค์Šคํ…€
      : ๋ชจ๋“  job์˜ job incremeter๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€์•Š๊ณ , ์ „์ฒด ์ ์šฉ์„ ์›ํ•˜๋Š” ๊ฒฝ์šฐ
    • JobParametersIncrementer๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ปค์Šคํ…€ 
      : ์—ฌ๋Ÿฌ๊ฐ€์ง€ job incremeter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
  • ์ปค์Šคํ…€ํ•œ parameter ๊ฐ’ ์ถ”๊ฐ€
    : ๋ณ„๋„์˜ parameter ์ถ”๊ฐ€๊ฐ€ ์ƒ๊ด€์—†์„ ๋•Œ
JobLauncher๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ปค์Šคํ…€
import org.springframework.batch.core.*;
import org.springframework.batch.core.launch.JobLauncher;
import java.util.Date;

public class CustomJobLauncher implements JobLauncher {

    private final JobLauncher jobLauncher;

    public CustomJobLauncher(JobLauncher jobLauncher) {
        this.jobLauncher = jobLauncher;
    }

    @Override
    public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
        JobParametersBuilder builder = new JobParametersBuilder(jobParameters)
                .addDate("currentTime", new Date());

        return jobLauncher.run(job, builder.toJobParameters());
    }
}
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class BatchConfiguration {

    private final JobLauncher jobLauncher;

    public BatchConfiguration(JobLauncher jobLauncher) {
        this.jobLauncher = jobLauncher;
    }

    @Bean
    @Primary
    public JobLauncher currentTimeJobLauncher() {
        return new CurrentTimeJobLauncher(jobLauncher);
    }
}

๊ธฐ๋ณธ๊ฐ’์œผ๋กœ customJobLauncher๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •

 

JobParametersIncrementer๋ฅผ ์ƒ์†ํ•˜์—ฌ ์ปค์Šคํ…€ 
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersIncrementer;

public class CurrentTimeMillisIncrementer implements JobParametersIncrementer {

    @Override
    public JobParameters getNext(JobParameters parameters) {
        return new JobParametersBuilder(parameters)
                .addLong("run.id", System.currentTimeMillis())
                .toJobParameters();
    }
}

 

์ปค์Šคํ…€ํ•œ parameter ๊ฐ’ ์ถ”๊ฐ€
mport org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
import java.util.Date;

public class CustomJobParameters {

    @Value("#{jobParameters[chunkSize]?:100}")
    private Integer chunkSize;

    @Value("#{jobParameters[currentTime]?:T(java.lang.System).currentTimeMillis()}")
    private Long currentTime; // ๊ฐ’์„ ๋„ฃ์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ์‹œ๊ฐ„์ด ์„ค์ •๋˜๋„๋ก ์„ธํŒ…
}

 

๋ฐ˜์‘ํ˜•