0JUUU / spring-batch

Spring Boot 기반으로 개발하는 Spring Batch
1 stars 0 forks source link

섹션 3. 스프링 배치 도메인 이해 #2

Open 0JUUU opened 2 years ago

0JUUU commented 2 years ago
0JUUU commented 2 years ago

Job

기본 구현체

다양한 구현체가 있지만, 2개를 알아보자.

SimpleJob

FlowJob

image

실행 흐름 Job 설정 ➡️ Step 설정 ➡️ 설정한 JobJobLauncher가 실행 ➡️ 실행시켜 Job이 구동됨 ➡️ Job이 각각의 Step 실행 ➡️ Step이 자기가 갖고 있는 tasklet 실행

0JUUU commented 2 years ago

JobInstance

Job이나 Step이 실행되는 시점마다 생성되어 메타데이터를 DB에 저장하기 위한 용도

image
// 스프링부트가 자동실행하게 하지 않고, 수동으로 구성함
@Component
public class JobRunner implements ApplicationRunner {
    // ApplicationRunner : 스프링부트가 초기화되면 가장 먼저 호출됨

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder().addString("name", "user1").toJobParameters();
        jobLauncher.run(job, jobParameters);
    }
}
spring:
    batch:
        job:
            enabled: false

실행 후

BATCH_JOB_INSTANCE

image

BATCH_JOB_PARAMETERS

image

같은 JobParameter로 시도

image

JobParameter 변경

JobParameters jobParameters = new JobParametersBuilder().addString("name", "user2").toJobParameters();

BATCH_JOB_INSTANCE

image

BATCH_JOB_PARAMETERS

image
0JUUU commented 2 years ago

JobParameter

// 수동으로 구성
@Component
public class JobParameterTest implements ApplicationRunner {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobParameters jobParameters = new JobParametersBuilder()
            .addString("name", "user1")
            .addLong("seq", 2L)
            .addDate("date", new Date())
            .addDouble("age", 16.5)
            .toJobParameters();

        jobLauncher.run(job, jobParameters);
    }
}

application.yml 설정 동일

BATCH_INSTANCE_PARAMS

image

JobParameter 값을 확인하기 위한 2가지 방식

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {

    // Stepontribution을 이용한 방식
    // 실제로 우리가 전달한 파라미터값을 확인할 수 있는 방식
    JobParameters jobParameters = contribution.getStepExecution().getJobExecution().getJobParameters();
                                         jobParameters.getString("name");
                                         jobParameters.getLong("seq");
                                         jobParameters.getDate("date");
                                         jobParameters.getDouble("age");

                                         // chunkContext를 이용
                                         // 위와 동일한 값을 얻을 수 있음
                                         // 하지만, 이 방식은 단순히 Map에서 값을 꺼내오는 것 (Map으로 고정)
                                         // JobParameters 값을 바꾼다면, 여기는 반영 x => 단순히 값을 꺼내오는 것
                                         Map<String, Object> jobParameters1 = chunkContext.getStepContext().getJobParameters();

                                         System.out.println("step1 was executed");
                                         return RepeatStatus.FINISHED;
}

하나 더 -> jar 파일 이용

: jar를 실행시킬 때, 인자 값을 넘겨주면 JobParameters 설정 가능

  1. clean해서 target 폴더 지우기
  2. package 클릭 -> build 하기 (결과 : target 폴더 생성) image
  1. 생성된 jar 파일 실행 (JobParameter 값 함께)
    // 근데 얘 (long) 안된다고 나옴... 왜?
    java -jar spring-batch-0.0.1-SNAPSHOT.jar name=user1 seq=(long)2 date=2022/03/24 age=(double)16.5 
    image image
    • jar 파일을 실행시킬 때는 기본 ApplicationRunner가 실행되도록 하자

하나 더~

image
0JUUU commented 2 years ago

JobExecution

BATCH_JOB_INSTANCE

image

BATCH_JOB_EXECUTION

image

만약, 일부러 에러를 낸다면 (잡을 완료하지 못하게 한다면)

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                                 .tasklet((contribution, chunkContext) -> {
                                     System.out.println("step2 has executed");
                                     throw new RuntimeException("step2 has failed");
//                                     return RepeatStatus.FINISHED;
                                 })
                                 .build();
    }
image

BATCH_JOB_INSTANCE

image

BATCH_JOB_EXECUTION

image

여기서 또 실행하면?

image

BATCH_JOB_EXECUTION

image

이제 성공시키면?

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                                 .tasklet((contribution, chunkContext) -> {
                                     System.out.println("step2 has executed");
//                                     throw new RuntimeException("step2 has failed");
                                     return RepeatStatus.FINISHED;
                                 })
                                 .build();
    }
image

BATCH_JOB_EXECUTION

image

여기서 또 다시 실행

image

BATCH_JOB_EXECUTION

변함 없다❗️❗️

0JUUU commented 2 years ago

Step

기본 구현체

TaskletStep

PartitionStep

JobStep

FlowStep

package io.springbatch.springbatch;

import org.springframework.batch.core.StepContribution;
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.stereotype.Component;

@Component
public class CustomTasklet implements Tasklet {

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        // 비즈니스 로직
        System.out.println("step2 was executed");

        return RepeatStatus.FINISHED;
    }
}
0JUUU commented 2 years ago

StepExecution

package io.springbatch.springbatch;

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;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Configuration
public class StepExecutionConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job job() {
        return jobBuilderFactory.get("job")
                                .start(step1())
                                .next(step2())
                                .next(step3())
                                .build();
    }

    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                                 .tasklet(new Tasklet() {
                                     @Override
                                     public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                                         System.out.println(">> step1 has executed");
                                         return RepeatStatus.FINISHED;
                                     }
                                 })
                                 .build();
    }

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                                 .tasklet((contribution, chunkContext) -> {
                                     System.out.println(">> step2 has executed");
                                     return RepeatStatus.FINISHED;
                                 })
                                 .build();
    }

    @Bean
    public Step step3() {
        return stepBuilderFactory.get("step3")
                                 .tasklet((contribution, chunkContext) -> {
                                     System.out.println(">> step3 has executed");
                                     return RepeatStatus.FINISHED;
                                 })
                                 .build();
    }
}

BATCH_STEP_EXECUTION

image

BATCH_JOB_EXECUTION

image

step2에 에러 발생

    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2")
                                 .tasklet((contribution, chunkContext) -> {
                                     System.out.println(">> step2 has executed");
                                     throw new RuntimeException(">> step2 has failed");
//                                     return RepeatStatus.FINISHED;
                                 })
                                 .build();
    }

BATCH_JOB_EXECUTION

image

BATCH_STEP_EXECUTION

image

동일 조건으로 재실행할 경우

: 성공한 step은 다시 실행하지 않는다.

image

BATCH_JOB_EXECUTION

image

BATCH_STEP_EXECUTION

image
0JUUU commented 2 years ago

StepContribution

0JUUU commented 2 years ago

ExecutionContext

디버그🐞해보자

ExecutionContextTask1 진입

image

BATCH_JOB_EXECUTION_CONTEXT

image

BATCH_STEP_EXECUTION_CONTEXT

image

ExecutionContextTask2 진입

ExecutionContext jobExecutionContext = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext();
ExecutionContext stepExecutionContext = chunkContext.getStepContext().getStepExecution().getExecutionContext();
image

ExecutionContextTask3 진입

Object name = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().get("name");

if (name == null) {
    chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext().put("name", "user1");
    throw new RuntimeException("step3 was failed");
}

이 때, DB 확인 ㄲㅆ

BATCH_JOB_INSTANCE

image

BATCH_JOB_EXECUTION

image

BATCH_STEP_EXECUTION

image

BATCH_JOB_EXECUTION_CONTEXT

image

BATCH_STEP_EXECUTION_CONTEXT

image

다시 재시작 ❗️ ➡️ Job이 실패했기에 재시작이 가능하다. 

Step1, Step2는 이미 성공한 일이기 때문에 재시작할 때, 수행되지 않는다.

BATCH_JOB_INSTANCE

이전과 동일

BATCH_JOB_EXECUTION

image

BATCH_STEP_EXECUTION

image

BATCH_STEP_EXECUTION_CONTEXT

image
0JUUU commented 2 years ago

JobRepository

JobRepository : 커스터마이징 가넝한!

0JUUU commented 2 years ago

JobLauncher

내가 커스텀한 JobLauncher를 동작시켜야하므로 application.yml에 다음과 같이 설정

spring:
    batch:
        job:
          enabled: false

실시간 통신을 위한 웹 관련 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

동기적 실행

@RestController
public class JobLauncherController {

    @Autowired
    private Job job;

    @Autowired
    private JobLauncher jobLauncher;

    @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();

        jobLauncher.run(job, jobParameters);

        return "batch completed";
    }
}

비동기적 실행

@RestController
public class JobLauncherController {

    @Autowired
    private Job job;

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private BasicBatchConfigurer basicBatchConfigurer;

    @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();

        SimpleJobLauncher jobLauncher = (SimpleJobLauncher) basicBatchConfigurer.getJobLauncher();
        // 밑에 문장은 예외 발생 -> typeCasting 불가 (왜? 이미 인터페이스로 선언했던 친구이기에 JobLauncher에서는 갖고 있지 않은 setTaskExecutor를 호출할 수 없음!
//        SimpleJobLauncher jobLauncher = (SimpleJobLauncher) this.jobLauncher;
        jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
        jobLauncher.run(job, jobParameters);

        return "batch completed";
    }
}

둘의 차이를 알기 위해 Step에 의도적으로 Thread.sleep(3000);처럼 pause를 준다.