Batch Scope & Job Parameter

Spring Batch์˜ ๊ฒฝ์šฐ ์™ธ๋ถ€, ๋‚ด๋ถ€์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ ์—ฌ๋Ÿฌ Batch ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ด๋ฅผ Job Parameter๋ผ ํ•œ๋‹ค.

Job Parameter๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ญ์ƒ Spring Batch ์ „์šฉ Scope๋ฅผ ์„ ์–ธํ•ด์•ผํ•˜๋Š”๋ฐ ์ข…๋ฅ˜๋Š” ํฌ๊ฒŒ @StepScope์™€ @JobScope๊ฐ€ ์žˆ๋‹ค.

@Value("#{jobParameters[ํŒŒ๋ผ๋ฏธํ„ฐ๋ช…]}")

Job Parameter๋Š” Double, Long, Date, String ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, LocalDate์™€ LocalDateTime์€ ์ œ๊ณตํ•˜์ง€ ์•Š์•„, ํƒ€์ž…์„ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

Job Parameter ์‚ฌ์šฉ์‹œ ๋งŽ์ด ์˜คํ•ดํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค. Job Parameter๋Š” Scope Bean์„ ์ƒ์„ฑํ• ๋•Œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ฆ‰, @StepScope, @JobScope Bean์„ ์ƒ์„ฑํ• ๋•Œ๋งŒ Job Parameters๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

Bean Scope

Spring Bean์˜ ๊ธฐ๋ณธ Scope๋Š” singleton์ด์ง€๋งŒ, @JobScope, @StepScope๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ๋˜๋ฉด Spring Batch๊ฐ€ ์ง€์ •๋œ Job/Step์˜ ์‹คํ–‰ ์‹œ์ ์— ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ Spring Bean์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค. ์ฆ‰, Bean์˜ ์ƒ์„ฑ ์‹œ์ ์„ ์ง€์ •๋œ Scope๊ฐ€ ์‹คํ–‰๋˜๋Š” ์‹œ์ ์œผ๋กœ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

JobScope, StepScope๋Š” Job/Step์ด ์‹คํ–‰๋˜๊ณ  ๋๋‚ ๋•Œ ๊ฐ๊ฐ ์ƒ์„ฑ/์‚ญ์ œ๊ฐ€ ์ด๋ฃจ์–ด์ง„๋‹ค๊ณ  ๋ณด๋ฉด๋œ๋‹ค.

์ด๋ ‡๊ฒŒ Bean ์ƒ์„ฑ์‹œ์ ์„ ์ง€์—ฐ์‹œํ‚ค๋ฉด ์–ป๋Š” ์žฅ์ ์€ ํฌ๊ฒŒ 2๊ฐœ๊ฐ€ ์žˆ๋‹ค.

1. JobParameter์˜ Late Binding

๊ผญ application์ด ์‹คํ–‰๋˜๋Š” ์‹œ์ ์ด ์•„๋‹ˆ๋”๋ผ๋„, Controller, Service์™€ ๊ฐ™์€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ๋‹จ๊ณ„์—์„œ Job Parameter๋ฅผ ํ• ๋‹น์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

2. ๋™์ผํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ‘๋ ฌ ํ˜น์€ ๋™์‹œ์— ์‚ฌ์šฉํ• ๋•Œ ์œ ์šฉ

์˜ˆ๋ฅผ ๋“ค์–ด, Step๋‚ด๋ถ€์— Tasklet์ด ์žˆ๊ณ , ์ด Tasklet์€ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

์ด ๊ฒฝ์šฐ @StepScope ์—†์ด Step์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๊ฒŒ๋˜๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ Step์—์„œ ํ•˜๋‚˜์˜ Tasklet์„ ๋‘๊ณ  ๋งˆ๊ตฌ์žก์ด๋กœ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๊ณ  ํ• ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ, @StepScope๋กœ Scope๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๊ฐ๊ฐ์˜ Step์—์„œ ๋ณ„๋„์˜ Tasklet์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋กœ์˜ ์ƒํƒœ๋ฅผ ์นจ๋ฒ”ํ•  ์ผ์ด ์—†๋‹ค.

์‚ฌ์šฉ ์˜ˆ์ œ

@JobScope

@JobScope๋Š” Step ์„ ์–ธ๋ฌธ์—์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅ

@Bean
public Job scopeJob() {
  	return jobBuilderFactory.get("scopeJob")
      			.start(scopeStep())
      			.build();
}

@Bean
@JobScope
public Step scopeStep(@Value("#{jobParameters[requestDate]}") String requestDate){
  
  	return stepBuilderFactory.get("scopeStep")
      				.tasklet((stepContribution, chunkContext) -> {
                    log.info(">>> Start step : {}", requestDate);
                    return RepeatStatus.FINISHED;
                }).build();
}

@StepScope

@StepScope๋Š” Tasklet, ItemReader, ItemWriter, ItemProcessor ๋“ฑ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

@Bean
public Step scopeStep2(){  
  	return stepBuilderFactory.get("scopeStep2")
      				.tasklet(scopeStepTasklet())
      				.build();
}


@Bean
@StepScope
public Tasklte scopeStepTasklet(@Value("#{jobParameters[requestDate]}") String requestDate){  
  	return (stepContribution, chunkContext) -> {
               log.info(">>> Start scopeStepTasklet : {}", requestDate);
               return RepeatStatus.FINISHED;
            };
}

Class ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋กœ ํ• ๋‹น๋ฐ›๊ธฐ

@Bean
public Job simpleJob() {
  	return jobBuilderFactory.get("simpleJob")
      			.start(simpleStep())
      			.build();
}

// ์ƒ์„ฑ์ž
private final SimpleJobTasklet tasklet;

public Step simpleStep(){
  	log.info("Start simpleStep");
  
  	return stepBuilderFactory.get("simpleStep")
      				.tasklet(tasklet1).build();
}
/**
	* StepScope๋กœ ์ƒ์„ฑํ–ˆ๊ธฐ๋•Œ๋ฌธ์— JobParameters๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
	*/

@Component
@StepScope
public class SimpleJobTasklet implements Tasklet{
  
  	// ํด๋ž˜์Šค ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋กœ ํ• ๋‹น
  	@Value("#{jobParameters[requestDate]}")
  	private String requestDate;
  
  	public SimpleJobTasklet(){ log.info("SimpleJobTasklet ์ƒ์„ฑ");}
  
  	@Override
  	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception{
      	log.info("SimpleJobTasklet execute");
      	log.info("requestDate : {}", requestDate);
      
      	return RepeatStatus.FINISHED;
    }
}

Job Parameter vs ์‹œ์Šคํ…œ ๋ณ€์ˆ˜

JobParameter

@Bean
@StepScope
public FlatFileItemReader<Partner> reader(
        @Value("#{jobParameters[pathToFile]}") String pathToFile){
    FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>();
    itemReader.setLineMapper(lineMapper());
    itemReader.setResource(new ClassPathResource(pathToFile));
    return itemReader;
}

์‹œ์Šคํ…œ ๋ณ€์ˆ˜

์‹œ์Šคํ…œ ๋ณ€์ˆ˜๋Š” application.properties์™€ -D ์˜ต์…˜์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ณ€์ˆ˜ ํฌํ•จ

@Bean
@ConfigurationProperties(prefix = "my.prefix")
protected class JobProperties {

    String pathToFile;

    ...getters/setters
}

@Autowired
private JobProperties jobProperties;

@Bean
public FlatFileItemReader<Partner> reader() {
    FlatFileItemReader<Partner> itemReader = new FlatFileItemReader<Partner>();
    itemReader.setLineMapper(lineMapper());
    String pathToFile = jobProperties.getPathToFile();
    itemReader.setResource(new ClassPathResource(pathToFile));
    return itemReader;
}

1. ์‹œ์Šคํ…œ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ Spring Batch์˜ Job Parameter ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.

Spring Batch๋Š” ๊ฐ™์€ JobParameter๋กœ ๊ฐ™์€ Job์„ ๋‘ ๋ฒˆ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ•˜์ง€๋งŒ ์‹œ์Šคํ…œ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ด ๊ธฐ๋Šฅ์ด ์ „ํ˜€ ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค. ๋˜ํ•œ Spring Batch์—์„œ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” Parameter ๊ด€๋ จ ๋ฉ”ํƒ€ ํ…Œ์ด๋ธ”์— ์ „ํ˜€ ๊ด€๋ฆฌ๊ฐ€ ๋˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

์ฆ‰, Job Parameter๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์€ Late Binding์„ ๋ชปํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

2. Command Line์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ Job์„ ์‹คํ–‰ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ

@Scope(
    value = "step",
    proxyMode = ScopedProxyMode.TARGET_CLASS
)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StepScope {
}

@StepScope ๋‚ด๋ถ€ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด @Scope(value = "step", proxyMode = ScopedProxyMode.TARGET_CLASS) ๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. ์ด๋•Œ proxyMode๋กœ ์ธํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด @StepScope๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ํ•ด๋‹น Bean์˜ return type์ด ์ธํ„ฐํŽ˜์ด์Šค์ธ ๊ฒฝ์šฐ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
@StepScope
public ItemReader<Person> reader(@Value("#{jobParameters[name]}") String name){
  	Map<String, Object> params = new HashMap<>();
  	params.put("name", name);
  
	  JpaPagingItemReader<Person> reader = new JpaPagingItemReader<>();
  	reader.setQueryString("select * from person where name = :name");
  	reader.setParameterValues(params);
  	reader.setEntityManagerFactory(entityManagerFactory);
  	reader.setPageSize(CHUNK_SIZE);
		return reader;
}
o.s.b.c.l.AbstractListenerFactoryBean    : org.springframework.batch.item.ItemReader is an interface.  The implementing class will not be queried for annotation based listener configurations.  If using @StepScope on a @Bean method, be sure to return the implementing class so listner annotations can be used.

๊ด€๋ จ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

@Bean ๋ฉ”์†Œ๋“œ์—์„œ @StepScope๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ listner ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ returnํ•ด์•ผํ•œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ returnํ•˜์—ฌ ํ•ด๋‹น ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
@StepScope
public JpaPagingItemReader<Person> reader(@Value("#{jobParameters[name]}") String name){
  	Map<String, Object> params = new HashMap<>();
  	params.put("name", name);
  
	  JpaPagingItemReader<Person> reader = new JpaPagingItemReader<>();
  	reader.setQueryString("select * from person where name = :name");
  	reader.setParameterValues(params);
  	reader.setEntityManagerFactory(entityManagerFactory);
  	reader.setPageSize(CHUNK_SIZE);
		return reader;
}

์ฐธ๊ณ 

Last updated