SpringBatch에서 JobLauncher 동기/비동기 실행

반응형
728x90
반응형

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초동안 실행되었다.

 

 

반응형

Designed by JB FACTORY