spring batch를 spring boot를 이용해서 돌려보기

2021. 4. 9. 15:57spring

728x90
반응형

1. 개요

조금 복잡한 스프링배치 프레임워크의 개념 파악을 위해 일단 돌려보자.

개념 설명을 최소한으로 하고, 이론보다는 실전, 스프링배치를 어떻게 실행해야 하는지를 먼저 분위기를 느낄 수 있도록

할 예정임.

가. 목표

  • Spring boot 를 이용해 Spring Batch를 어떻게 작성해야 될지 이해
  • Spring batch의 전체 구조가 어느 정도 이해
  • 간단한 job, step을 만들수 있게 (Tasklet 모델 이용)
  • 필요한 최소한의 라이브러리, Java Config 작성 방법 이해

2. 스프링 배치 개념 및 아키텍처

가. 스프링 배치 개념

docs.spring.io/spring-batch/docs/current/reference/html/spring-batch-intro.html#spring-batch-intro

나. 스프링배치 아키텍처

Spring Batch Architecture

다. 스프링배치 구성요소

ㄱ. JobLauncher

public interface JobLauncher {

public JobExecution run(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException,
                   JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}

Job을 실행하는 EntryPoint 가 되는 인터페이스.

ㄴ. Job

배치 처리 전체를 나타내는 요소.

1 Job = 1 배치

ㄷ. JobParameter

Job 실행 시 전달하는 파라메터. 전달 된 파라메터를 Job 또는 Step 의 변수로써 이용 가능

ㄹ. Step

배치 처리를 구성하는 최소단위의 요소. Step은 크게 2종류의 모델로 분류할 수 있다.

  • Chunk Model
    • 읽기 (ItemReader), 가공(ItemProcessor), 쓰기 (ItemWriter) 로 구성되어 있는 Step Model.
    • 읽기 > 가공 > 쓰기 순으로 Step이 구성되어 반드시 각각의 처리가 실행 (예를 들어 가공만의 처리를 실행하는 step 구성은 불가능)
  • Tasklet Model
    • Chunk모델과 달리, 특별히 처리의 흐름이 결정되어 있지 않고, 자유롭게 처리를 구성할 수 있는 Step Model.
    • 어떤 단일 처리를 실행하는 경우는 이걸 사용.

ㅁ. JobRepository

Job 및 Step의 실행상황과 실행결과를 보존하는 곳. 일반적으로 RDB 등의 스토리지로 영속화 (Persistence) 함.

3. 샘플 소스 (Spring Batch + Spring Boot)

github.com/hyowong/sample-spring-batch

가. 전제 조건 (라이브러리 버전)

  • Spring Boot - 2.2.5.RELEASE
  • Java - 11 (java 8에서 돌릴수 있는지 확인해보자!!)
  • maven - 3.5.3
  • Spring Batch 설정은 xml에 하지 않고 Java Config에서 기술.

나. 실행

ㄱ. 라이브러리 도입

  • Spring-boot-starter-batch - Spring Batch를 Spring Boot로 이용할 수 있기 위한 starter 라이브러리.
  • h2 - Java DB, JobRepository를 격납하기 위해 필요.
pom.xml
...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        ...
    </dependencies>
...

ㄴ. Tasklet 구현

우선 Step의 실제처리를 이루는 Tasklet를 구현해보자.

Tasklet Interface를 구현해 execute method를 구현한다.

그 메소드는 결과값으로 RepeatStatus 형을 반환 할 필요가 있다. (Enum Type)

- RepeatStatus.FINISHED : 더 이상의 처리를 종료 처리. NULL을 반납하는 경우에도 같은 결과.

- RepeatStatus.CONTINUABLE : Tasklet이 계속해서 호출된다. FINISHED를 반환하지 않는 한 계속 호출되므로 주의가 필요.

HelloWorldTasklet.java
@Component
@StepScope
public class HelloWorldTasklet implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        System.out.println("Hello, World!");
        return RepeatStatus.FINISHED;
    }
}

ㄷ. Job, Step의 정의

Java Config 은 주로 Job 과 step 의 설정을 Bean 정의로 기술한다.

본래는 JobRepository 와 JobLauncher 등 Bean 정의도 필요하지만, Spring Batch 에는 @EnableBatchProcessing 이라는 어노테이션이 있고 그것을 부여해도 명시적으로 정의안해도 자동으로 설정이 된다.

BatchConfig.java

@EnableBatchProcessing
@Configuration
public class BatchConfig {

    private final JobBuilderFactory jobBuilderFactory;

    private final StepBuilderFactory stepBuilderFactory;

    private final HelloWorldTasklet helloWorldTasklet;

    public BatchConfig(JobBuilderFactory jobBuilderFactory,
                       StepBuilderFactory stepBuilderFactory,
                       HelloWorldTasklet helloWorldTasklet) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.helloWorldTasklet = helloWorldTasklet;
    }

    @Bean
    public Job helloWorldJob(Step helloWorldStep) {
        return jobBuilderFactory.get("helloWorldJob") //Job名を指定
                .flow(helloWorldStep) //実行するStepを指定
                .end()
                .build();
    }

    @Bean
    public Step helloWorldStep() {
        return stepBuilderFactory.get("helloWorldStep") //Step名を指定
                .tasklet(helloWorldTasklet) //実行するTaskletを指定
                .build();
    }
}

ㄹ. DataSource의 설정

JobRepository 의 보존처로써 H2을 사용하기 위해 DataSource의 설정을 기술한다.

application.yml

spring:
  datasource:
    url: "jdbc:h2:mem:test"
    username: sa
    password:
    driver-class-name: org.h2.Driver

ㅁ. SpringBootApplication의 구현
일반적인 Spring Boot의 기동 클라스

SampleSpringBatchApplication.java

@SpringBootApplication
public class SampleSpringBatchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SampleSpringBatchApplication.class, args);
    }
}

ㅁ. 기동

다음은 빌드해서 기동하면 배치가 실행된다.
Spring Boot로 Bean정의 된 전체 job가 디폴트로 실행 되는 구조로 되어 있어 특별히 설정 안하고 기동해도 Job이 실행된다.
이하는 기동로그이지만, 정의한 helloWorldJob이 실행된게 알 수 있다.

$ ./mvnw spring-boot:run

...(생략)...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.5.RELEASE)

...(생략)...

2020-03-24 23:12:36.494  INFO 76528 --- [           main] c.k.s.SampleSpringBatchApplication       : Started SampleSpringBatchApplication in 2.261 seconds (JVM running for 2.83)
2020-03-24 23:12:36.496  INFO 76528 --- [           main] o.s.b.a.b.JobLauncherCommandLineRunner   : Running default command line with: []
2020-03-24 23:12:36.568  INFO 76528 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=helloWorldJob]] launched with the following parameters: [{}]
2020-03-24 23:12:36.638  INFO 76528 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [helloWorldStep]
Hello, World!
2020-03-24 23:12:36.672  INFO 76528 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [helloWorldStep] executed in 34ms
2020-03-24 23:12:36.678  INFO 76528 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=helloWorldJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 63ms

...(생략)...

다. 정리

Spring Boot로 Spring Batch로 실행하기 위한 최저한 필요한 것은

  • spring-boot-starter-batch 추가
  • DB의 준비, DataSource의 설정
  • Job 과 Step 등의 설정 (Java Config)
  • Step처리의 기술 또는 Tasklet의 기술

4. StepScope 개념

가. @StepScope 에 대해서

샘플 소스에서 구현해 본 HelloWorldTasklet 클래스에 @StepScope 라는 어노테이션을 부여하고 있다.

그 어노테이션은 Bean 생성 스코프를 정의한 것으로 StepScope는 같은 Step에서는 같은 인스턴스를 이용하게 하는 스코프이다.

Step이 변해야 인스턴스가 재생성된다.

초기설정값으로 싱글톤 스코프여서 Tasklet이 어떤 상태를 갖고 있으면 다른 Step에 그 상태를 전달하게 되는 리스크가 있다.

그런 리스크가 문제가 없다면 괜찮으나, 문제가 있는 경우에는 Step Scope로 설정해두는게 좋을거라고 생각되어진다.

Step Scope는 Spring Batch에서만 이용 가능이다.

나. 복수 Job을 정의한 경우, 기동시에 실행하는 Job을 제한하는 방법

- Spring Boot로 Spring Batch를 기동하면 초기값으로 정의된 전체 Job이 실행되기 때문에, 실행하고자 하는 Job을 제한하고 싶은 경우가 있다.

그 경우, spring.batch.job.names에 Job명을 지정하는 것으로 실행하는 Job을 제한할수 있다.

복수 Job을 지정하고 싶은 경우는 콤마 구분으로 지정할 수 있다.

application.yml

spring:
  batch:
    job:
      names: helloWorldJob

기동 시 전체 Job의 실행을 off하고자 하는 경우, spring.batch.job.enabled = false 하는 것으로 구현 가능.
@SpringBootTest에 의해 테스트하는 경우 Job 실행을 하고 싶지 않은 경우 유효하다.
샘플소스의 테스트에서도 설정을 false로 설정하고 있다.

application.yml

spring:
  batch:
    job:
      enabled: false

다. JobLauncher는 언급이 안되어 있음에도 실행되는 이유

개념 설명에서 Job은 JobLauncher#run 메소드에서 호출되는 것으로 설명했는데,
샘플소스에서는 특별히 JobLauncher에 대해서 언급이 없었다.
JobLauncher는 어디서 호출되는 것일까?

그 대답은 Spring Boot의 JobLauncherCommandLineRunner 클래스에 있다.
그 클래스는 CommandLineRunner 인터페이스를 구현하고 있기 때문에 Spring Boot 기동시에 run 메소드가 호출되어진다.
그 run method를 살펴보면 execute 메소드에서 JobLauncher#run 메소드를 호출하고 있는 걸 알수 있다.

JobLauncherCommandLineRunner.java


public class JobLauncherCommandLineRunner implements CommandLineRunner, Ordered, ApplicationEventPublisherAware {

    @Override
    public void run(String... args) throws JobExecutionException {
        logger.info("Running default command line with: " + Arrays.asList(args));
        launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="));
    }

    protected void launchJobFromProperties(Properties properties) throws JobExecutionException {
        JobParameters jobParameters = this.converter.getJobParameters(properties);
        executeLocalJobs(jobParameters);
        executeRegisteredJobs(jobParameters);
    }

    //(略)

    protected void execute(Job job, JobParameters jobParameters)
            throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
        JobParametersInvalidException, JobParametersNotFoundException {
        JobParameters parameters = getNextJobParameters(job, jobParameters);
        JobExecution execution = this.jobLauncher.run(job, parameters); //Jobの実行を呼び出している
        if (this.publisher != null) {
            this.publisher.publishEvent(new JobExecutionEvent(execution));
        }
    }

그럼, 이 JobLauncher#run 메소드의 인수로 전달하는 Job은 어디에서 취득하고 있는 것일까?
역추적해보면 setJobs 메소드에서 Job의 컬렉션이 setter-injection 되어 있는 걸 알수 있다.
결국 Bean 정의한 Job을 DI하는 것으로 Job을 취득하고 있다.

더불어서 application.yml에서 spring.batch.job.names를 지정하고 있는 경우, 어떻게 Job을 취득하고 있는지 찾아보면
BatchAutoConfiguration클래스의 Bean 메소드로 있는 jobLauncherCommandLineRunner 에서 Job명의 취득이 이루어지고,
그것을 JobLauncherCommandLineRunner 클래스의 필드 (jobNames)에 셋트 되어 있고, 최종적으로
JobLauncherCommandLineRunner의 executeRegisteredJobs 메소드에서 Job명에서 Job의 취득이 이루어진다.

JobLauncherCommandLineRunner.java


    private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException {
        if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) {
            String[] jobsToRun = this.jobNames.split(",");
            for (String jobName : jobsToRun) {
                try {
                    Job job = this.jobRegistry.getJob(jobName); //Job名からJobを取得
                    if (this.jobs.contains(job)) {
                        continue;
                    }
                    execute(job, jobParameters);
                }
                catch (NoSuchJobException ex) {
                    logger.debug(LogMessage.format("No job found in registry for job name: %s", jobName));
                }
            }
        }
    }

라. JobLauncher 와 JobRepository를 커스터마이징하는 방법

  • BatchConfigurer 인터페이스를 구현하여 커스터마이징을 한 후, 그 클래스를 Bean 정의하면 된다.
    공식 레퍼런스 사이트를 참조 하라

마. 참조 사이트

728x90
반응형