[Spring Batch] 공부하면서 기록하는 글

식빵·2023년 4월 23일
0

spring-batch

목록 보기
1/2

기록용 글입니다.

spring boot 기반의 spring batch 공부를 할 때 반복적으로 나오는 코드 및 세팅에 대해서 기록하는 글입니다. 기록성 글이기 때문에 자세한 설명을 쓰지 않습니다.

정리 및 요약글은 추후에 spring batch 에 대한 공부가 어느정도 다듬어지면 하겠습니다.
참고로 여기 나오는 코드, 세팅법은 제가 공부하는 인터넷 강의 를 기반으로 작성된 것입니다.




세팅법

개발 환경

  • Window 10
  • spring boot (v2.7.10)
  • java 17
  • maven dependency
    • postgresql
    • h2
    • spring-boot-starter-batch
    • lombok
    • 그외는 중요하지 않아서 생략합니다...



Configuration 작성 예시

package me.daily.batch;

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;

@Configuration
@RequiredArgsConstructor
public class JobParameterConfiguration {
	
	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;
	
	@Bean
	public Job job() {
		return jobBuilderFactory.get("job")
			.start(step1())
			//.next(step2())
			.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 called");
					return RepeatStatus.FINISHED; 
                    // default 로 무한 반복이다. 한번 실행하고 끝내도록 하자.
				}
			}).build();
	}
	
}



yml 구성

spring:
  profiles:
    active: postgres # Run Configuration 에서 Profile 을 주면 override 됨

---
spring:
  config:
    activate:
      on-profile: local
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb
      username: sa
      password: 
      driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true

---
spring:
  config:
    activate:
      on-profile: postgres
  datasource:
    hikari:
      username: postgres
      password: root
      jdbc-url: jdbc:postgresql://localhost:5432/spring_batch
      driver-class-name: org.postgresql.Driver
  batch:
    jdbc:
      initialize-schema: never
      # initialize-schema: always
    job:
      enabled: false # 자동 실행 방지
  • spring.batch.job.enabled: true 가 default 인데, 이러면 스프링 부트가 자동으로 실행한다는 의미합니다.

  • 자동 실행을 원치 않으면 spring.batch.job.enabled: false 를 주면 되고, 이를 수동 실행하기 위해서는 아래와 같은 코드를 추가적으로 작성해줍니다.

package me.daily.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class JobRunner implements ApplicationRunner {
	
	@Autowired
	private JobLauncher jobLauncher;
	
	@Autowired
	private Job job;
	
	@Override
	public void run(ApplicationArguments args) throws Exception {
		JobParameters jobParameters = new JobParametersBuilder().addString("name", "user2")
			.toJobParameters();
		
		jobLauncher.run(job, jobParameters);
	}
	
}

  • 이 외에도 initialize-schema: embedded 가 default 이다. 이건 테스트용 메모리 DB 에서 쓰면 유용합니다.

  • 하지만 실제 회사에서 사용하거나, 여러사람들이 같이 쓸 때는 never 로 세팅하고, spring batch 가 필요로하는 Schema 들을 수동으로 생성하는 걸 추천합니다.

  • 주관적인 생각이지만 회사에서는 여러 사람이 같은 코드를 사용하는데, 함부로 애플리케이션이 DB 에 접근해서는 안된다고 생각합니다.

  • 다만 로컬 환경(자기 컴퓨터)에서 postgresql(또는 h2)를 설치하고 테스트하는 것은 당연히 initialize-schema 에 대해서 어떻게 세팅하든 상관없습니다.

  • 참고로 spring-batch 에서 자신의 DBMS 에 맞는 sql schema 를 제공하는데, 해당 내용은 아래와 같이 찾아갈 수 있습니다.



sql schema 위치

sql schema 위치는 아래와 같습니다.

  • org.springframework.batch.core.Job 이라는 인터페이스 경로로 갑니다.

  • 해당 경로를 좀만 뒤져보면 아래와 같이 dbms 별 schema sql 찾아낼 수 있습니다.


postgresql 의 경우에는 아래와 같이 나옵니다.

  • 내용을 전체 복사해서, 쿼리를 실행하면 이후에 spring-batch 가 사용하는 메타정보를 저장하는 db 스키마들이 생성됩니다.




PowerShell 파라미터 전달 방식

PS C:\Users\dailyCode\spring-batch\target> `
>> java -jar spring-batch-0.0.1-SNAPSHOT.jar --spring.profiles.active=postgres "name=mickeymouse seq(long)=3L date(date)=2023-03-03 age(double)=16.5"




파라미터 확인 방법

@Configuration
@RequiredArgsConstructor
public class JobParameterConfiguration {

	
	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;
	
	@Bean
	public Job job() {
		return jobBuilderFactory.get("job")
			.start(step1())
			.next(step2())
			.build();
	}
	

	@Bean
	public Step step1() {
		return stepBuilderFactory.get("step1")
			.tasklet((contribution, chunkContext) -> {
				
                // 방식 1
				JobParameters jobParameters = contribution
					.getStepExecution()
					.getJobExecution()
					.getJobParameters();
				
				jobParameters.getString("name");
				jobParameters.getLong("seq");
				jobParameters.getDate("date");
				jobParameters.getDouble("age");
				
                // 방식 2
				Map<String, Object> jobParameters1 = chunkContext.getStepContext().getJobParameters();
				
				System.out.println("step1 called");
				return RepeatStatus.FINISHED;
			}).build();
	}
}




ExecutionContext 에 put,get

@Component
public class ExecutionContextTasklet1 implements Tasklet {
	@Override
	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
		System.out.println("step 1 was executed");
		
		ExecutionContext jobExecutionContext = contribution.getStepExecution().getJobExecution().getExecutionContext();
		ExecutionContext stepExecutionContext = contribution.getStepExecution().getExecutionContext();
		
		String jobName = chunkContext.getStepContext().getStepExecution().getJobExecution().getJobInstance().getJobName();
		String stepName = chunkContext.getStepContext().getStepExecution().getStepName();
		
		if (jobExecutionContext.get("jobName") == null) {
			jobExecutionContext.put("jobName", jobName);
		}
		
		if (stepExecutionContext.get("stepName") == null) {
			stepExecutionContext.put("stepName", stepName);
		}
		
		System.out.println("jobName : " + jobExecutionContext.get("jobName"));
		System.out.println("stepName : " + stepExecutionContext.get("stepName"));
		
		return RepeatStatus.FINISHED;
	}
}



FlowJob 예시

package me.dailycode.springbatch;

import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.ExitStatus;
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;

@RequiredArgsConstructor
@Configuration
public class TransitionConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job batchJob() {
        return jobBuilderFactory.get("batchJob")
                .start(step1())
                    .on("FAILED")
                    .to(step2())
                    .on("FAILED")
                    .stop()
                .from(step1())
                    .on("*")
                    .to(step3())
                    .next(step4())
                .from(step2())
                    .on("*")
                    .to(step5())
                .end()
                .build();
    }

/*

    @Bean
    public Job batchJob() {
        return jobBuilderFactory.get("batchJob")
                .start(step1())
                    .on("FAILED")
                    .to(step2())
                    .on("*")
                    .stop()
                .from(step1()).on("*")
                    .to(step3())
                    .next(step4())
                    .on("FAILED")
                    .end()
                .end()
                .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");
                        contribution.setExitStatus(ExitStatus.FAILED);
                        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();
    }

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


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

}
spring:
  profiles:
    active: local

---
spring:
  config:
    activate:
      on-profile: local
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
      username: sa
      password:
      driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true

---
spring:
  config:
    activate:
      on-profile: postgres
  datasource:
    hikari:
      jdbc-url: jdbc:postgresql://localhost:5432/spring_batch
      username: postgres
      password: postgres
      driver-class-name: org.postgresql.Driver
  batch:
    job:
      names: ${job.name:NONE}
    jdbc:
      initialize-schema: always
#      enabled: true
#    jdbc:
#      initialize-schema: always
#      table-prefix: SYSTEM_

참고

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글