Programing/Java & Spring

세무민의 코딩일기 : Spring Batch API 만들기 2탄(Job, Step, TaskLet를 직접 사용해보자)

세기루민 2023. 10. 30. 13:40
728x90

안녕하세요

세기무민입니다.

이전 포스팅에서는 Spring Batch 이론에 대해 다뤄봤다면

이번 프로젝트에서는 실제 코드를 작성해보려고 합니다.


👇이전 포스팅은 아래 링크로👇

 

 

세무민의 코딩일기 : Spring Batch API 만들기 1탄(Spring Batch, Scheduler, TaskLet, Chunk등 이론 정리)

안녕하세요. 오랜만에 코딩일기로 돌아온 세무민입니다. 사실 요즘 회사 일에 치여서 이걸 포스팅 할까 고민하다가 그래도 Spring Batch에 대해 조금이나마 작성하면 좋을 것 같아서 포스팅으로 남

sg-moomin.tistory.com


1. 개발 환경 및 고려 사항

기술 스택은 아래와 같습니다.

  • Spring Boot 2.7.3
  • H2 Database
  • JPA(native, jpql)
    • 초기 프로젝트 생성 시에는 DB Connection은 하지 않고 임의로 코드를 Return하여 Job이 정상 처리되는지 확인

개발 방식은 TaskLet 방식으로 구현

  • 대용량 처리의 경우 Chunk 방식이 맞으나 POC의 경우 TaskLet 방식으로 충분하다고 판단
    • TaskLet은 하나의 Task 안에 reader/processer/writer 역할을 포함하고 있다고 보면 됨

고려할 사항은 아래와 같습니다.

  • Spring Batch를 이용하기 위해서는 수행 관련하여 테이블을 생성해야 한다.
    • application.yml 설정 파일
      • spring.batch.initialize-schema : always
        • 배치 관련 테이블 생성

2. 개발 상세 

2.1 application.yml

spring:
    datasource:
        url: jdbc:h2:mem:[DB 이름] 
        driverClassName: org.h2.Driver
        username: [DB 사용자 이름]
        password: [DB PW]
    batch:
        job:
            names: ${job.name:NONE}
            enabled: false
        jdbc:
            # 인메모리 DB를 사용할 경우 always를 하는것이 좋음 
            # 인메모리 DB의 경우 휘발성으로 매번 TB를 생성해줘야 함
            initialize-schema: always
  • 인메모리 DB 설정과 프로젝트 실행 시 메타 테이블 스키마를 항상 체크하여 없으면 생성해주도록 셋팅한다.
    • 추후 인메모리 DB를 사용하지 않을 경우 always -> never로 변경하여 처리한다.

 

2.2 application.java

@SpringBootApplication
@EnableBatchProcessing
@EnableScheduling
public class SpringbatchapiApplication {

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

}
  • 배치 작업을 위한 기본 구성 설정을 위해 EnableBatchProcessing 어노테이션을 사용합니다.
  • Spring에서 Scheduler 기능 활성화를 위해 EnableScheduling 어노테이션을 사용해줍니다.

 

2.3 Job Class(MemberJobConfiguration.java)

@Slf4j
@RequiredArgsConstructor
@Configuration
public class MemberJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;

    private final JobCompletionListener jobCompletionListener;

    private final MemberStep memberStep;

    @Bean(name = "getMemberJob")
    public Job getMemberJob(){
        return jobBuilderFactory.get("getMemberJob")
            .incrementer(new RunIdIncrementer())
            .start(memberStep.getMemberStep1())
                .on(ExitStatus.STOPPED.getExitCode())
                    .to(memberStep.getMemberStep3())
                .on("*")
                .end()
            .from(memberStep.getMemberStep1())
                .on("*")
                    .to(memberStep.getMemberStep2())
                    .next(memberStep.getMemberStep3())
                .on("*")
                .end()
            .end()
            .build();
    }
}
  • Job을 설정해주는 클래스입니다. 
  • 해당 클래스에서는 Step을 실행시키고 해당 Step의 실행 결과에 따라 다음 Step에 대해 구성하여 처리가 가능합니다.
  • RunIdIncrementer를 이용하여 동일 파라미터를 다시 실행할 수 있도록 옵션을 추가해줍니다. 
    • runId가 매번 증감함에 따라 해당 파라미터를 계속 이용할 수 있게 됩니다.
    • 즉, 해당 Job을 반복하여 사용 가능합니다.
  • Scheduler만 사용할 때와 달리 Batch를 사용하게 되면 Step별로 분리하여 처리할 수 있다는 점이 최대 장점입니다. 
    • on("*")의 경우는 위에 설정한 ExisStatus.STOPPED 상태가 아닌 경우 처리합니다.
    • 즉 위의 코드를 해석하자면 1번 Step에서 정상적으로 작동하지 않을 경우(STOPPED) 3번 Step을 처리하고
      그렇지 않은 경우(STOPPED 이외) 2번 Step > 3번 Step 순서대로 처리한다.

 

2.4 Step Class (MemberStep.java)

@Configuration
public class MemberStep {
    
    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Autowired
    private MemberTasklet memberTasklet;
    
    @Autowired
    private MemberSelectTasklet memberSelectTasklet;

    @Bean(name = "getMemberStep1")
    public Step getMemberStep1(){
        return stepBuilderFactory.get("getMemberStep1")
            .tasklet(memberTasklet)
            .build();
    }

    @Bean(name = "getMemberStep2")
    public Step getMemberStep2(){
        return stepBuilderFactory.get("getMemberStep2")
            .tasklet(memberTasklet)
            .build();
    }


    @Bean(name = "getMemberStep3")
    public Step getMemberStep3(){
        return stepBuilderFactory.get("getMemberStep3")
            .tasklet(memberTasklet)
            .build();
    }
}
  • 저는 임의로 테스트를 하기 위해 3가지의 Step을 만들었고 TaskLet 방식으로 처리하였습니다.
  • 추가로 stepBuilderFactory.get().tasklet 내에 TaskLet 코드를 작성해도 무관하지만
    관리 차원에서 TaskLet Class를 분리하여 관리하는 것이 효율적으로 저는 분리하였습니다.

 

2.5 TaskLet Class(MemberTasklet/MemberTasklet2/MemberTasklet3.java)

/**
 * MemberTaskLet 1
 */
@Slf4j
@Configuration
public class MemberTasklet implements Tasklet {

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("Start GetMember TaskLet");
        contribution.setExitStatus(ExitStatus.STOPPED);
        log.info("End GetMember TaskLet");
        return RepeatStatus.FINISHED;
    }
}


/**
 * MemberTaskLet 2
 */
@Slf4j
@Configuration
public class MemberTasklet2 implements Tasklet {

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("Start GetMember TaskLet 2");
        log.info("End GetMember TaskLet 2");
        return RepeatStatus.FINISHED;
    }
}


/**
 * MemberTaskLet 3
 */
@Slf4j
@Configuration
public class MemberTasklet3 implements Tasklet {
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
        log.info("Start GetMember TaskLet 3");
        log.info("End GetMember TaskLet 3");
        return RepeatStatus.FINISHED;
    }
}
  • 저의 경우 임의로 테스트를 위해 Log로 작성하였으나 실제 처리하는 로직을 작성해주면 됩니다.
  • MemberTaskLet의 경우 StepContribuition에 ExitStatus 상태 값을 설정해주어
    Step에게 해당 TaskLet의 처리 상태를 Return 합니다.
    • Job에서 Step의 상태로 다음 Step을 결정할 수 있도록 처리하기 위함

 

2.6 Scheduler Class(MemberScheduler.java)

@Slf4j
@Component
public class MemberScheduler {
    
    @Autowired
    private JobRegistry jobRegistry;

    @Autowired
    private JobLauncher jobLauncher;

    public JobLauncher getJobLauncher() {
        return jobLauncher;
    }

    @Scheduled(cron = "0/10 * * * * ?")
     public void runJob() {
        //job parameter 
        Map<String, JobParameter> confMap = new HashMap<>();

        Calendar calendar = Calendar.getInstance();
        Date date = calendar.getTime();
        confMap.put("time", new JobParameter(date));
        JobParameters jobParameters = new JobParameters(confMap);

        try {
           jobLauncher.run(jobRegistry.getJob("getMemberJob"), jobParameters);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}
  • cron을 이용하여 특정 시간 때 실행되도록 설정해줍니다.
  • Scheduler만 사용했을 때는 내부 로직을 작성했다면 Scheduler + Spring Batch 구조를 이용할 때는
    JobLauncher를 실행하여 Job이 처리되도록 합니다.
  • JobLauncher를 실행하기 전 JobParameter를 이용하여 실행되는 Job의 파라미터 값을 추가해줍니다.
    • 메타 테이블에 해당 데이터들이 담기게 됩니다.
  • 결론적으로 JobLauncher를 실행하여 Scheduler(JobLauncehr) > Job > Step 순으로 처리되게 됩니다.

3. 실행 결과

3.1 MetaTable 확인 

  • 간단하게 메타 테이블에 어떻게 들어가는지 확인해보도록 하겠습니다.
    • 참고로 6개의 모든 테이블을 보는 것이 좋지만 특정 테이블만 봐도 정상적으로 작동하는지 확인 가능합니다.

 

3.1.1 H2 DB 실행

  • H2 DB를 사용할 경우 url을 http://localhost:8080/h2-console로 입력하시면 위와 같은 화면을 볼 수 있습니다.
  • 여기서 JDBC URL, UserName, Password는 Application.yml에 작성한 내용을 그대로 입력해주면 됩니다.
    • JDBC URL은 이미 등록되어 있을 것입니다.

입력하고 들어오면 메타 테이블 6개가 입력된 것을 확인할 수 있습니다.

 

3.1.2 Batch Job Execution

  • Job Execution TB를 이용하여 Job이 정상적으로 처리되었는지 확인이 가능합니다.
    • Status/Exit Code가 Complete일 경우 정상 처리되었다고 볼 수 있습니다.

 

3.1.3 Batch Job Execution Params

  • Scheduler가 실행될 때 설정한 JobParameter의 값이 로그로 남게 됩니다. 
    • 저의 경우는 Time만 남겨놓은 상태라 해당 Job Execution Id가 언제 실행되었는지 용도로 확인할 수 있습니다.
    • 상세한 데이터를 남기고 싶을 경우 JobParameter에 추가로 값을 넣어주면 됩니다.

 

3.1.4 Batch Step Execution

  • Job이 처리될 때 실행한 Step들을 확인할 수 있습니다.
    • 즉 내가 설정한 Step대로 실행되었는지 확인도 가능하며 중간에 오류가 발생했는지 분석도 가능합니다.
    • Job과 동일하게 Status를 통해 처리 상태를 확인할 수 있습니다.

 

3.2 Job Step.on 처리 

  • 우선 Log로 남긴 상태임으로 Stopped과 Stopped이 아닐 때 로그가 어떻게 남는지 확인해보도록 하겠습니다.

3.2.1 Stopped

  • Step 1 > Step 3 순서대로 처리됩니다.

3.2.1 Not Stopped

  • Step 1 > Step 2 > Step 3 순서대로 처리됩니다.
  •  

4. 기타 

 

GitHub - sg-moomin/springbatchapi

Contribute to sg-moomin/springbatchapi development by creating an account on GitHub.

github.com

GitHub 코드를 확인하고 싶을 경우 해당 링크를 클릭해주세요!!!


Spring Batch의 작동에 대해 간단하게 알아볼 수 있었습니다.

다음 포스팅에서는 조금 더 심화 된 내용으로 돌아오겠습니다.

728x90