SpringBatch에서 JobLauncher 동기/비동기 실행
- Coding/Spring Batch
- 2022. 1. 21.
jobLauncher
- 배치 Job을 실행시키는 역할을 한다.
- jobLauncher.run(job, jobParameter); 로직으로 배치를 수행한다. job, jobParameter 를 인자로 받아서 jobExecution을 결과로 반환한다.
- 스프링 배치가 실행되면 jobLauncher 빈을 생성하고, jobLauncherApplicationRunner가 자동적으로 jobLauncher을 실행시킨다.
예제코드
- JobLauncherConfiguration.java
package com.spring.batch.job;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
--job.name=launcherTestJob
*/
@Configuration
@RequiredArgsConstructor
public class JobLauncherConfiguration {
// job 생성
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job launcherTestJob() {
return this.jobBuilderFactory.get("launcherTestJob")
/* step start */
.start(launcherTestStep1())
.next(launcherTestStep2())
.build();
}
@Bean
public Step launcherTestStep1() {
return stepBuilderFactory.get("launcherTestStep1")
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("launcherTestStep1");
// 동기 : 잡이 모두 끝난 후 결과 리턴
// 비동기 : 일단 결과를 내려주고, 내부적으로 배치 실행
Thread.sleep(3000);
return RepeatStatus.FINISHED;
}
})
.build();
}
@Bean
public Step launcherTestStep2() {
return stepBuilderFactory.get("launcherTestStep2")
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("launcherTestStep2");
return RepeatStatus.FINISHED;
}
})
.build();
}
}
동기 실행
동기로 실행되면, 배치 Job이 실행이 완료된 후에 결과값을 클라이언트에서 확인할 수 있다.
- batch.http
POST http://localhost:8080/batch
Content-Type: application/json
{
"id" : "seohae"
}
- JobLauncherController.java
package com.spring.batch.web;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequiredArgsConstructor
public class JobLauncherController {
private final Job job;
private final JobLauncher jobLauncher; // proxy 객체
/**
* 동기 배치 수행
* @param member
* @return
* @throws JobInstanceAlreadyCompleteException
* @throws JobExecutionAlreadyRunningException
* @throws JobParametersInvalidException
* @throws JobRestartException
*/
@PostMapping("/batch")
public String launch(@RequestBody Member member) throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException {
JobParameters jobParameters = new JobParametersBuilder()
.addString("id", member.getId())
.addDate("date", new Date())
.toJobParameters();
/* batch 수행 */
jobLauncher.run(job, jobParameters);
// job 실행 시간이 3초일때, 3초가 지나야 아래 값이 리턴된다.
return "batch completed";
}
}
비동기 실행
- asyncBatch.http
POST http://localhost:8080/async/batch
Content-Type: application/json
{
"id" : "seohae"
}
- JobLauncherController.java
package com.spring.batch.web;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequiredArgsConstructor
public class JobLauncherController {
private final Job job;
private final JobLauncher jobLauncher; // proxy 객체
/* SimpleJobLauncher 는 빈 등록이 불가능
* BasicBatchConfigurer 를 주입해야한다.
*/
private final BasicBatchConfigurer basicBatchConfigurer;
/**
* 비동기는 직접 SimpleJobLauncher 의 setTaskExecutor 을 설정해줘야한다.
* SimpleJobLauncher 는 빈 등록이 불가능
* BasicBatchConfigurer 를 주입해야한다.
*/
/**
* 비동기 배치 수행
* @param member
* @return
* @throws JobInstanceAlreadyCompleteException
* @throws JobExecutionAlreadyRunningException
* @throws JobParametersInvalidException
* @throws JobRestartException
*/
@PostMapping("/async/batch")
public String asyncLaunch(@RequestBody Member member) throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException {
JobParameters jobParameters = new JobParametersBuilder()
.addString("id", member.getId())
.addDate("date", new Date())
.toJobParameters();
/* 비동기로 수행 */
SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
/*
만약, 위 주입에서 private final JobLauncher simpleJobLauncher; 로 할 경우
- JobLauncher 을 생성할 경우는 프록시 객체로 생성됨
- simpleJobLauncher : 실제 객체가 아니고, 타입을 상속해서 만들어진 proxy 객체이다.
*/
// SimpleJobLauncher jobLauncher1 = (SimpleJobLauncher) simpleJobLauncher;
/* batch 수행 */
jobLauncher.run(job, jobParameters);
// // job 실행 시간이 3초일때, 일단 결과가 보여지고, 실제로 내부적으로 3초동안 배치가 실행중이다.
return "batch completed";
}
}
주석에도 있듯이, 비동기 실행은 SimpleJobLauncher 인스턴스를 생성해서 setTaskExecutor()를 호출해줘야한다. SimpleJobLauncher 인스턴스 생성을 위해 아래와 같은 코드가 추가되었다.
- JobLauncherController.java
/* 비동기로 수행 */
SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
의문점
- JobLauncherController.java
@RestController
@RequiredArgsConstructor
public class JobLauncherController {
private final Job job;
private final JobLauncher jobLauncher;
.
.
.
}
- SimpleJobLauncher.java
public class SimpleJobLauncher implements JobLauncher, InitializingBean {
protected static final Log logger = LogFactory.getLog(SimpleJobLauncher.class);
private JobRepository jobRepository;
private TaskExecutor taskExecutor;
...
}
아래의 코드처럼 jobLauncher 을 바로 타입캐스팅해서 사용이 불가능한가?
SimpleJobLauncher jobLauncher1 = (SimpleJobLauncher) simpleJobLauncher;
불가능하다. 우선 스프링 배치에서 JobLauncherController.java 에서 주입받은 jobLauncher 객체는 프록시 객체다. 따라서 프록시 객체는 실제 객체가 아니기 때문에 SimpleJobLauncher 객체로 타입캐스팅을 할 수 없다.
해결방안
BasicBatchConfigurer을 주입한 후, SimpleJobLauncher 로 타입캐스팅 해줘야한다.
- JobLauncherController.java
package com.spring.batch.web;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.boot.autoconfigure.batch.BasicBatchConfigurer;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
@RequiredArgsConstructor
public class JobLauncherController {
private final Job job;
private final JobLauncher jobLauncher; // proxy 객체
/* SimpleJobLauncher 는 빈 등록이 불가능
* BasicBatchConfigurer 를 주입해야한다.
*/
private final BasicBatchConfigurer basicBatchConfigurer;
.
.
.
// 타입캐스팅
SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
.
.
정리
JobLauncherConfiguration 잡은 총 실행시간이 3초다. 동기 실행은 한 경우에는 클라이언트가 3초 후 배치 잡의 결과를 받을 수 있었다. 반면에 비동기 실행은 우선 결과를 받고, 내부적으로 배치가 3초동안 실행되었다.
'Coding > Spring Batch' 카테고리의 다른 글
[SpringBatch] Incrementer() Custom : 직접 별도 클래스 생성하기 (0) | 2022.02.08 |
---|---|
SpringBatch JobLauncherApplicationRunner Job Name 을 지정하여 실행시키기 (동시에 Job 여러개 지정하여 실행시키기) (0) | 2022.01.24 |
스프링배치 StepContribution (0) | 2022.01.19 |
SpringBatch 에서 StepExecution 알아보기 (0) | 2022.01.16 |
SpringBatch 에서 JobInstance, JobExecution 의 관계 (0) | 2022.01.14 |