반응형
목차
- 배치 초기화 설정
- Job and Step
- Job and Flow
- @JobScope / @StepScope
배치 초기화 설정
- 이번 글에서는 Bean으로 등록된 Job들을 어떻게 실행할 지에 대해서 배운다.
- Job을 하나만 실행하는 경우도 있을것이고 2개..n개를 돌리는 경우도 있을것이다.
- 이러한 설정을 application.properties or application.yml 파일에 설정해서 동작시켜보고 어떻게 돌아가는지 원리를 분석해보자.
1.JobLauncherApplicationRunner
- JobLauncherApplicationRunner는 Spring Batch 작업을 시작하는 ApplicationRunner 로서 BatchAutoConfiguration 에서 생성됨
- 스프링 부트에서 제공하는 ApplicationRunner 의 구현체로 어플리케이션이 정상적으로 구동되자 마자 실행됨
- 기본적으로 빈으로 등록된 모든 job 을 실행시킨다.
- 여러개의 Job중 실행시킬 Job만 동작시킬수도 있다.
2. BatchProperties
- BatchProperties는 Spring Batch 의 환경 설정 클래스를 의미한다
- Job 이름, 스키마 초기화 설정, 테이블 Prefix 등의 값을 설정할 수 있다.
- application.properties or application.yml 파일에 설정한다
예시
//application.properties or application.yml
batch:
job:
names: ${job.name:NONE} //Job이름
nitialize-schema: NEVER //초기화 설정
tablePrefix: SYSTEM //Prefix
3. Job 실행 옵션
- 지정한 Batch Job만 실행하도록 할 수 있음
- 아래처럼 동작시킬 Job만 이름을 넣어줘서 동작시킨다.
- spring.batch.job.names: ${ job.name:NONE}
- CLI 환경에서 Parameter 형식으로 동작 시킬수도 있다.
- 어플리케이션 실행시 Program arguments 로 job 이름 입력한다
- -job.name=helloJob
- -job.name=helloJob,simpleJob (하나 이상의 job 을 실행 할 경우 쉼표로 구분해서 입력함)
- 어플리케이션 실행시 Program arguments 로 job 이름 입력한다
예제코드
//configuration1.java
package com.example.springbatch_4_1_batchinitconfiguration;
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.launch.support.RunIdIncrementer;
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 JobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob1() {
return jobBuilderFactory.get("batchJob1")
// .incrementer(new RunIdIncrementer())
.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 has executed");
return RepeatStatus.FINISHED;
}
})
.build();
}
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
System.out.println("step2 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
}
//configuration2.java
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
@Configuration
public class JobConfiguration2 {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob2() {
return jobBuilderFactory.get("batchJob2")
// .incrementer(new RunIdIncrementer())
.start(step3())
.next(step4())
.build();
}
@Bean
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
System.out.println("step3 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
public Step step4() {
return stepBuilderFactory.get("step4")
.tasklet((contribution, chunkContext) -> {
System.out.println("step4 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
}
//applicaiton.yml
spring:
profiles:
active: mysql
# 배치에서 어떤 잡을 돌릴지에 대한 변수 설정 yml
batch:
job:
# enabled: false
names: ${job.name:NONE}
---
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
batch:
jdbc:
initialize-schema: embedded
---
spring:
config:
activate:
on-profile: mysql
datasource:
hikari:
jdbc-url: jdbc:mysql://localhost:3307/springbatch?useUnicode=true&characterEncoding=utf8
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
batch:
jdbc:
initialize-schema: always
- editConfigurations -> Program Arguments에 - --job.name=batchJob1,batchJob2 name=user01 추가
- Arguments에 자신이 원하는 Job이름을 추가한다.
- 이런 형식으로 내가 원하는 batch Job만을 설정해서 동작시킬수 있다.
- 아래와 같이 Job2개가 실행되고 Step4개가 실행된것을 확인할 수 있다.
실행결과
/Library/Java/JavaVirtualMachines/jdk-18.0.1.1.jdk/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.profiles.active=mysql -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=52342:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/SpringBatch_4_1_BatchInitConfiguration/target/classes:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.7.1/spring-boot-starter-web-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.7.1/spring-boot-starter-json-2.7.1.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.3/jackson-databind-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.3/jackson-annotations-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.3/jackson-datatype-jdk8-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.3/jackson-datatype-jsr310-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.3/jackson-module-parameter-names-2.13.3.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.1/spring-boot-starter-tomcat-2.7.1.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.64/tomcat-embed-core-9.0.64.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.64/tomcat-embed-el-9.0.64.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.64/tomcat-embed-websocket-9.0.64.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-web/5.3.21/spring-web-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-beans/5.3.21/spring-beans-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-webmvc/5.3.21/spring-webmvc-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-aop/5.3.21/spring-aop-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-context/5.3.21/spring-context-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-expression/5.3.21/spring-expression-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.1/spring-boot-starter-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot/2.7.1/spring-boot-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.1/spring-boot-autoconfigure-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.1/spring-boot-starter-logging-2.7.1.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/namhyeop/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/namhyeop/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jcl/5.3.21/spring-jcl-5.3.21.jar:/Users/namhyeop/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/namhyeop/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/namhyeop/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-core/4.3.6/spring-batch-core-4.3.6.jar:/Users/namhyeop/.m2/repository/io/micrometer/micrometer-core/1.9.1/micrometer-core-1.9.1.jar:/Users/namhyeop/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.12/HdrHistogram-2.1.12.jar:/Users/namhyeop/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/namhyeop/.m2/repository/javax/batch/javax.batch-api/1.0/javax.batch-api-1.0.jar:/Users/namhyeop/.m2/repository/org/codehaus/jettison/jettison/1.2/jettison-1.2.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-infrastructure/4.3.6/spring-batch-infrastructure-4.3.6.jar:/Users/namhyeop/.m2/repository/org/springframework/retry/spring-retry/1.3.3/spring-retry-1.3.3.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-tx/5.3.21/spring-tx-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jdbc/5.3.21/spring-jdbc-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-batch/2.7.1/spring-boot-starter-batch-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.7.1/spring-boot-starter-jdbc-2.7.1.jar:/Users/namhyeop/.m2/repository/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar:/Users/namhyeop/.m2/repository/com/h2database/h2/2.1.214/h2-2.1.214.jar:/Users/namhyeop/.m2/repository/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar com.example.springbatch_4_1_batchinitconfiguration.SpringBatch41BatchInitConfigurationApplication --job.name=batchJob1,batchJob2 name=user01
Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.1)
2022-07-15 12:07:27.165 INFO 78970 --- [ main] Batch41BatchInitConfigurationApplication : Starting SpringBatch41BatchInitConfigurationApplication using Java 18.0.1.1 on NamHyeop.local with PID 78970 (/Users/namhyeop/Desktop/git자료/Spring_Boot_Study/SpringBatch_4_1_BatchInitConfiguration/target/classes started by namhyeop in /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/SpringBatch_4_1_BatchInitConfiguration)
2022-07-15 12:07:27.166 INFO 78970 --- [ main] Batch41BatchInitConfigurationApplication : The following 1 profile is active: "mysql"
2022-07-15 12:07:27.508 INFO 78970 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-07-15 12:07:27.512 INFO 78970 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-07-15 12:07:27.513 INFO 78970 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.64]
2022-07-15 12:07:27.545 INFO 78970 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-07-15 12:07:27.546 INFO 78970 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 361 ms
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
2022-07-15 12:07:27.600 INFO 78970 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-07-15 12:07:27.601 WARN 78970 --- [ main] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2022-07-15 12:07:27.797 INFO 78970 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-07-15 12:07:28.207 INFO 78970 --- [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta data indicating: MYSQL
2022-07-15 12:07:28.213 INFO 78970 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2022-07-15 12:07:28.249 INFO 78970 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-07-15 12:07:28.254 INFO 78970 --- [ main] Batch41BatchInitConfigurationApplication : Started SpringBatch41BatchInitConfigurationApplication in 1.229 seconds (JVM running for 1.432)
2022-07-15 12:07:28.255 INFO 78970 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [name=user01]
2022-07-15 12:07:28.315 INFO 78970 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob1]] launched with the following parameters: [{name=user01}]
2022-07-15 12:07:28.354 INFO 78970 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
step1 has executed
2022-07-15 12:07:28.384 INFO 78970 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 30ms
2022-07-15 12:07:28.418 INFO 78970 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step2 has executed
2022-07-15 12:07:28.445 INFO 78970 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 27ms
2022-07-15 12:07:28.464 INFO 78970 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob1]] completed with the following parameters: [{name=user01}] and the following status: [COMPLETED] in 134ms
2022-07-15 12:07:28.501 INFO 78970 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob2]] launched with the following parameters: [{name=user01}]
2022-07-15 12:07:28.542 INFO 78970 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
step3 has executed
2022-07-15 12:07:28.575 INFO 78970 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 33ms
2022-07-15 12:07:28.612 INFO 78970 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step4]
step4 has executed
2022-07-15 12:07:28.638 INFO 78970 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step4] executed in 26ms
2022-07-15 12:07:28.658 INFO 78970 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob2]] completed with the following parameters: [{name=user01}] and the following status: [COMPLETED] in 147ms
Job and Step 소개
- JobBuilderFactory & JobBuilder
- SimpleJob
- 내용, 기능을 확인해본다
- SimpleJob이 사실상 정보를 가지고 실행한다.
- StepBuilderFactory & StepBuilder
- Step도 여러 종류의 Step이 있다.
- 각각의 Step을 StepBuilderFactory와 StepBuilder를 살펴본다.
- TaskletStep
- JobStep
- Step내에서 또다른 Job을 실행시키기 위해 존재
JobBuilderFactory / JobBuilder
1.JobBuilderFactory와 JobBuilder란?
- 스프링 배치는 Job과 Step을 쉽게 생성 및 설정 할 수 있도록 util 성격의 빌더클래스들을 제공함
- JobBuilderFactory
- JobBuilder 를 생성하는 팩토리 클래스로서 get(String name) 메서드 제공
- jobBuilderFactory.get(“jobName")
- “jobName” 은 스프링 배치가 Job 을 실행시킬 때 참조하는 Job 의 이름
- 여기서 설정한 이름으로 DB에 저장된다.
- JobBuilder
- Job을 구성하는 설정 조건에 따라 두 개의 하위 빌더 클래스를 생성하고 실제 Job 생성을 위임한다.
- SimpleJobBuilder
- SimpleJob 을 생성하는 Builder 클래스
- Job 실행과 관련된 여러 설정 API를 제공한다
- FlowJobBuilder
- FlowJob 을 생성하는 Builder 클래스
- 내부적으로 FlowBuilder를 반환함으로써 Flow 실행과 관련된 여러 설정 API 를 제공한다
2.아키텍쳐 관점으로 Job을 생산하는 과정 분석
- Job을 생성하는 과정을 순서대로 살펴보자
- JobBuilderFactory에서 JobBuilder를 생상한다. 이때 get()을 사용해서 JobName을 설정한다
- 다음 과정은 SimpleJobBuilder,FlowJobBuilder 둘 중 어떤것을 생성할지 골라야 한다.
- SimplerJobBuilder를 생성하는 경우 JobBuilder에서 start(step)를 메소드를 사용하여 SimpleJobBuilder를 생산한다.
- FlowJobBuilder를 생성하는 경우JobBuilder에서 start(flow) 메소드를 사용할 경우 JobBuilder는 FlowJobBuilder를 생산한다.
- FlowJobBuilder를 생성하는데 API 타입인 경우JobBuilder에서 flow(step) 메소드를 사용할 경우 JobBuilder는 FlowJobBuilder를 생산한다.
- SimpleJobBuilder를 생산한 경우 통해 SimpleJob을 생성한다.
- FlowJobBuilder를 생성한 경우 JobFlowBuilder를 생성한다.
- 이름이 앞뒤로 똑같아서 헷갈리는데 (FlowJob) Builder, (JobFlow) Builder로 생각하면 이해하기 쉽다.
- SimpleJobBuilder는 6개의 API로 이루어져있다.
- start(step)
- next(step)
- on(String pattern) →TransitionBuilder 반환
- start(decider) → FlowBuilder 반환
- next(decider) → FlowBuilder 반환
- split(taskExecutor) → SplitBuilder 반환
- JobFlowBuilder는 7개의 API로 이루어져있다.
- on(String pattern)→TransitionBuilder 반환
- start(step of flow)
- next(step of flow)
- from(step of flow)
- start(decider)
- next(decider)
- from(decider)
클래스 상속 구조 기반으로 살펴보기
- JobBuilderFactory는 JobBuilder를 생산한다.
- JobBuilder는 JobBuilderHelper를 상속한다.
- JobBuilderHelper에는 실질적으로 Job을 생산하는 정보를 다가지고있다.
- JobBuilderHelper는 CommonJobProperties를 참조하고 있다. CommonJobPropertis에는 Job에 사용되는 공통정보를 가지고 있다.
- JobBuilderHelper는 SimpleJobRepository를 참조하고 있다.
- SimpleJobRepository는 4개의 Dao 인스턴스를 가지고 있다.
- JobInstanceDao
- JobExecutionDao
- StepExecutionDao
- ExecutionContextDao
- SimpleJobRepository는 빌드 시점부터 생산되는 메타데이터를 DB에 저장하는 역할을 수행한다.
- JobBuilder는 SimpleJobBuilder와 FlowJobBuilder를 생성한다.
- 그리고 SimpleJobBuilder와 FlowJobBuilder는 JobBuilderHelper를 상속한다.
- SimpleJobBuilder는 SimpleJob을 생성하고 FlowJobBuilder는 FlowJob을 생성한다.
- SimpleJobRepository의 값들이 SimpleJob과 FlowJob 클래스에 값들을 전달하여 작업들(CRUD)을 처리한다.
💡 이러한 JobBuilder가 생성되는 클래스간의 계층 및 구조를 이해해야지만 Job Configuration을 구성할 수 있다.
예제 코드(JobBuilderFactory와 관련된 클래스의 동작 과정 확인)
package com.example.springbatch_4_1_batchinitconfiguration;
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.job.builder.FlowBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
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 JobBuilderConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob1() {
return jobBuilderFactory.get("batchJob1")
.incrementer(new RunIdIncrementer())
.start(step1())
.next(step2())
.build();
}
@Bean
public Job batchJob2(){
return this.jobBuilderFactory.get("batchJob2")
.incrementer(new RunIdIncrementer())
.start(flow())
.next(step2())
.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");
return RepeatStatus.FINISHED;
}
})
.build();
}
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
System.out.println("step2 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Flow flow(){
FlowBuilder<Flow> flowFlowBuilder = new FlowBuilder<>("flow");
flowFlowBuilder.start(step3())
.next(step4())
.end();
return flowFlowBuilder.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");
return RepeatStatus.FINISHED;
}).build();
}
}
- flow를 사용하여 step의 실행동작을 조절해보자!
실행결과
/Library/Java/JavaVirtualMachines/jdk-18.0.1.1.jdk/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.profiles.active=mysql -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=55494:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_4_2_JobBuilderFactory/target/classes:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.7.1/spring-boot-starter-web-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.7.1/spring-boot-starter-json-2.7.1.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.3/jackson-databind-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.3/jackson-annotations-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.3/jackson-core-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.3/jackson-datatype-jdk8-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.3/jackson-datatype-jsr310-2.13.3.jar:/Users/namhyeop/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.3/jackson-module-parameter-names-2.13.3.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.1/spring-boot-starter-tomcat-2.7.1.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.64/tomcat-embed-core-9.0.64.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.64/tomcat-embed-el-9.0.64.jar:/Users/namhyeop/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.64/tomcat-embed-websocket-9.0.64.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-web/5.3.21/spring-web-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-beans/5.3.21/spring-beans-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-webmvc/5.3.21/spring-webmvc-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-aop/5.3.21/spring-aop-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-context/5.3.21/spring-context-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-expression/5.3.21/spring-expression-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.1/spring-boot-starter-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot/2.7.1/spring-boot-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.1/spring-boot-autoconfigure-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.1/spring-boot-starter-logging-2.7.1.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/namhyeop/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/namhyeop/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/namhyeop/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/namhyeop/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-core/5.3.21/spring-core-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jcl/5.3.21/spring-jcl-5.3.21.jar:/Users/namhyeop/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/Users/namhyeop/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/Users/namhyeop/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-core/4.3.6/spring-batch-core-4.3.6.jar:/Users/namhyeop/.m2/repository/io/micrometer/micrometer-core/1.9.1/micrometer-core-1.9.1.jar:/Users/namhyeop/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.12/HdrHistogram-2.1.12.jar:/Users/namhyeop/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/namhyeop/.m2/repository/javax/batch/javax.batch-api/1.0/javax.batch-api-1.0.jar:/Users/namhyeop/.m2/repository/org/codehaus/jettison/jettison/1.2/jettison-1.2.jar:/Users/namhyeop/.m2/repository/org/springframework/batch/spring-batch-infrastructure/4.3.6/spring-batch-infrastructure-4.3.6.jar:/Users/namhyeop/.m2/repository/org/springframework/retry/spring-retry/1.3.3/spring-retry-1.3.3.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-tx/5.3.21/spring-tx-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/spring-jdbc/5.3.21/spring-jdbc-5.3.21.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-batch/2.7.1/spring-boot-starter-batch-2.7.1.jar:/Users/namhyeop/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.7.1/spring-boot-starter-jdbc-2.7.1.jar:/Users/namhyeop/.m2/repository/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar:/Users/namhyeop/.m2/repository/com/h2database/h2/2.1.214/h2-2.1.214.jar:/Users/namhyeop/.m2/repository/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar com.example.springbatch_4_1_batchinitconfiguration.SpringBatch41BatchInitConfigurationApplication --job.name=batchJob1,batchJob2 name=user01
Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.1)
2022-07-15 17:05:45.721 INFO 80240 --- [ main] Batch41BatchInitConfigurationApplication : Starting SpringBatch41BatchInitConfigurationApplication using Java 18.0.1.1 on NamHyeop.local with PID 80240 (/Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_4_2_JobBuilderFactory/target/classes started by namhyeop in /Users/namhyeop/Desktop/git자료/Spring_Boot_Study/8.Spring_Batch/SpringBatch_4_2_JobBuilderFactory)
2022-07-15 17:05:45.722 INFO 80240 --- [ main] Batch41BatchInitConfigurationApplication : The following 1 profile is active: "mysql"
2022-07-15 17:05:46.062 INFO 80240 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-07-15 17:05:46.067 INFO 80240 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-07-15 17:05:46.067 INFO 80240 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.64]
2022-07-15 17:05:46.105 INFO 80240 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-07-15 17:05:46.105 INFO 80240 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 366 ms
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
2022-07-15 17:05:46.160 INFO 80240 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-07-15 17:05:46.161 WARN 80240 --- [ main] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2022-07-15 17:05:46.291 INFO 80240 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-07-15 17:05:46.600 INFO 80240 --- [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta data indicating: MYSQL
2022-07-15 17:05:46.606 INFO 80240 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2022-07-15 17:05:46.639 INFO 80240 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-07-15 17:05:46.644 INFO 80240 --- [ main] Batch41BatchInitConfigurationApplication : Started SpringBatch41BatchInitConfigurationApplication in 1.057 seconds (JVM running for 1.269)
2022-07-15 17:05:46.645 INFO 80240 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [name=user01]
2022-07-15 17:05:46.708 INFO 80240 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob1]] launched with the following parameters: [{name=user01, run.id=1}]
2022-07-15 17:05:46.748 INFO 80240 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
step1 has executed
2022-07-15 17:05:46.776 INFO 80240 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 28ms
2022-07-15 17:05:46.809 INFO 80240 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step2 has executed
2022-07-15 17:05:46.836 INFO 80240 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 27ms
2022-07-15 17:05:46.856 INFO 80240 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=batchJob1]] completed with the following parameters: [{name=user01, run.id=1}] and the following status: [COMPLETED] in 133ms
2022-07-15 17:05:46.899 INFO 80240 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=batchJob2]] launched with the following parameters: [{name=user01, run.id=1}]
2022-07-15 17:05:46.933 INFO 80240 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
--> step3 has executed
2022-07-15 17:05:46.958 INFO 80240 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 25ms
2022-07-15 17:05:46.995 INFO 80240 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step4]
--> step4 has executed
2022-07-15 17:05:47.027 INFO 80240 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step4] executed in 32ms
2022-07-15 17:05:47.071 INFO 80240 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step2 has executed
2022-07-15 17:05:47.098 INFO 80240 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 27ms
2022-07-15 17:05:47.116 INFO 80240 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=batchJob2]] completed with the following parameters: [{name=user01, run.id=1}] and the following status: [COMPLETED] in 209ms
SimpleJob-개념 및 소개
1. 기본개념
- SimpleJob 은 Step 을 실행시키는 Job 구현체로서 SimpleJobBuilder 에 의해 생성된다
- step들은 list로 구현된 Simplejob에 저장되어서 실행되게 된다.
- 여러 단계의 Step 으로 구성할 수 있으며 Step 을 순차적으로 실행시킨다
- 모든 Step 의 실행이 성공적으로 완료되어야 Job 이 성공적으로 완료 된다
- 맨 마지막에 실행한 Step 의 BatchStatus 가 Job 의 최종 BatchStatus 가 된다
2. 흐름
SimpleJob 7가지 역할
예제 코드
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
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 SimpleJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
/**
* SimpleJob의 정보들
*/
@Bean
public Job batchJob1() {
return jobBuilderFactory.get("batchJob1")
.incrementer(new RunIdIncrementer())
.validator(new JobParametersValidator() {
@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {
}
})
.preventRestart()
.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();
}
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
System.out.println("-->step2 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
/**
* step3의 배치 상태를 Faild로 설정하여 JobExecution의 Fail을 유도한다.
*/
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
chunkContext.getStepContext().getStepExecution().setStatus(BatchStatus.FAILED);
contribution.setExitStatus(ExitStatus.STOPPED);
System.out.println("-->step3 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
}
- SimpleJob의 정보들을 직접 넣어서 테스트에 사용해보자
SimpleJob-start()/next()
1.start, next()란?
public Job batchJob(){
return jobBuilderFactory.get(“batchJob")
.start(Step) // 처음 실행할 step 설정, 최초 한 번 설정, SimpleJouBuilder가 생성되고 반환된다.
.next(Step) // 다음에 실행 할 step 들을 순차적으로 연결하도록 설정한다, 여러 번 설정이 가능하며 모든 next()의 step이 종료가 되면 job이 종료된다.
.incrementer()
.validator()
.preventRestart()
.listener()
.build();
}
2.Job 구성의 예시
- simpleJob이라는 이름으로 한 개의 start와 2개의 next로 이러우진 예시이다.
- simpleJob 구문을 통해 Job이 생성되고 JobLauncher가 Job을 실행한다.
- 이후 Job이 Step1을 실행시킨다. 이때 Job은 SimpleJob이다.
- 다음으로 Step이 tasklet을 실행시킨다. 이 작업은 다음에 존재하는 스텝이 존재할 때까지 반복한다.
예제코드
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
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 StartNextConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob() {
return this.jobBuilderFactory.get("batchJob")
.start(step1())
.next(step2())
.next(step3())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
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();
}
}
- 예시의 step들은 현재 SimpleJob형태이다.
- SimpleJobLanucher가 SimpleJob을 실행시킨다.
- 예제 기준 SimpleJobLauncher의 Steps안에 3개의 Step이 들어가있다는걸 알아야 한다.
- SimpleJob을 만드는 SimpleJobBuilder에서는 Step으로만 만들어진다. 이외는 FlowJobBuilder와 연관되어있다.
- 항상 내부 동작에 대한 원리를 알고 있는 상태로 학습을 해야한다는것을 강조함.
SimpleJob-validator()
1. 기본개념
- Job 실행에 꼭 필요한 파라미터를 검증하는 용도
- 검증 통과가 올바르다면 값을 실행하고 아니라면 실행하지 않도록 설정해주는 개념
- Spring이 기본적으로 DefaultJobParametersValidator 구현체를 지원하며, 좀 더 복잡한 제약 조건이 있다면 인터페이스를 직접 구현할 수도 있음
- 일반적으로는 DefaultJobParametersValidator로 충분하다.
2. 구조
3.SimplerJob을 사용할때 validator의 실제 위치
public Job batchJob() {
return jobBuilderFactory.get(“batchJob")
.start() .next()
.incrementer()
.validator(JobParametersValidator) // <- 여기서 Validator를 사용하면 된다!
.preventRestart()
.listener()
.build();
}
4.DefaultJobParametersValidator 흐름도
- Simple Job이 실행하게 되면 Validator의 구현체인 JobParamterValidator로 이동하게 된다.
- 이후 요구되는 Key값의 필수값은 requiredKeys에 저장하고 선택값은 optionKeys에 저장한다.
- 이후 JobPramters에 값을 전달하게 된다.
- JobPrameters에서는 key값의 조건을 만족하면 로직이 수행된다. JobParameters에 Key값이 전달되는 경우 3가지를 예시를 통해서 확인해본다.
- 첫 번째로 date만 넘겨주는 경우다.
- 이 경우는 필수 값인 name이 없기 때문에 검증이 실패해서 실행되지 않는다.
- 두 번째로 date, count만 넘겨주는 경우다.
- 이 경우도 필수 값인 name이 없기 때문에 검증이 실패해서 실행되지 않는다. 옵션 키값인 count는 말 그대로 옵션이다. 필수값이 있어야 실행이 된다.
- 두 번째로 date, name만 넘겨주는 경우다.
- 필수값이 존재하므로 실행된다.
예제코드
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.DefaultJobParametersValidator;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
@Configuration
public class ValidatorConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob() {
return this.jobBuilderFactory.get("batchJob")
.start(step1())
.next(step2())
.next(step3())
// .validator(new DefaultJobParametersValidator(new String[]{"name", "date"}, new String[]{"count"}))
.validator(new CustomJobParametersValidator())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
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();
}
}
package com.example.springbatch_4_1_batchinitconfiguration;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.JobParametersValidator;
public class CustomJobParametersValidator implements JobParametersValidator {
/**
* 파라미터가 JopParamters로 넘어온다
*/
@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {
System.out.println("parameters.getString(\\"name\\") = " + parameters.getString("name"));
if(parameters.getString("name")==null){
throw new JobParametersInvalidException("name parameters is not found");
}
}
}
- 예제는 2가지 정도로 구분할 수 있다.
- 자신이 직접 만든 예외 검증기인 CustomJobParametersValidator, 필수값과 옵션값만 검증해주는 DefaultJobParametersValidator 가 있다.
- CustomJobParamterValidator는 JobParamter로 파라미터 전달이 가능하므로 전달 이후 값을 받아서 검증하는 로직을 작성하면 된다.
- DefaultJobParamtersValidator는 필수값과 옵션값을 예제코드 처럼 작성해서 전달해주면 검증할 수 있다.
- 인텔리제이에서 간단하게 테스트하는 방법은 아래와 같이 파라미터를 전달해서 테스트할 수 있다.
SimpleJob-preventRestart()
1. 기본개념
- Job 의 재 시작 여부를 설정
- 기본 값은 true이며 false로 설정 시 “ 이 Job은 재시작을 지원하지 않는다 ” 라는 의미
- Job 이 실패해도 재시작이 안되며 Job을 재시작하려고 하면 JobRestartException이 발생
- 재시작과 관련 있는 기능으로 Job 을 처음 실행하는 것 과는 아무런 상관 없음
2.흐름도
- Job이 실행되고나서 Job이 실행되었던 적이 있는지를 확인한다.
- 만약 Job의 Jobinstance가 없을경우 JobExecution이 실행되게 되고 비즈니스 로직이 정상적으로 수행된다. 여기까지는 지금까지 배웠던 개념과 일치한다.
- 하지만 JobExecution이 존재할 경우 PreventrRestart 옵션이 True이냐 false이냐 에따라 Job이 한 번 더 수행될수도 있고 아닐 수도 있다.
- 즉, Job 의 실행이 처음이 아닌 경우는 Job 의 성공/실패와 상관없이 오직 preventRestart 설정 값에 따라서 실행 여부를 판단한다
3.SimplerJob을 사용할때 validator의 실제 위치
public Job batchJob() {
return jobBuilderFactory.get(“batchJob")
.start()
.next()
.incrementer()
.validator()
.preventRestart() // 재시작을 하지 않음 (restartable = false)
.listener()
.build();
}
💡 어떤 작업은 실행후 실패가 되더라도 재실행 되지 않아야 하는 경우가 있다. 이럴 경우에 preventRestart()를 사용하면된다.
예제코드
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
@Configuration
public class PreventRestartConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob() {
return this.jobBuilderFactory.get("batchJob")
.start(step1())
.next(step2())
// .preventRestart() //재실행 옵션. false로 설정되어있다.
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
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 was executed");
throw new RuntimeException("step2 was failed");
})
.build();
}
}
- 먼저 step2에서 Exception을 나도록 유도하자
- 이후 실행해보면 JobExecution에 기록이 남을것이고 Faild 처리가 되어있을것이다. JobInstance는 당연히도 한 개만 생성되어있다.
- 이후 preventRestart 옵션을 사용한 뒤 실행해보면 작업이 실패되더라도 JobInstance Aalredy exists and is not restartable 오류를 발생하는것을 확인할 수 있으며 이를 통해 작업이 재실행 되지 않는것을 확인할 수 있다.
SimpleJob-incrementer()
1. 기본개념
- JobParameters 에서 필요한 값을 증가시켜 다음에 사용될 JobParameters 오브젝트를 리턴
- 앞에 글을 읽어보면 JobLauncher는 Job과 JobParamter를 활용해서 Run된다는것을 알 수 있다.
- Job Parameters의 값은 해당 Job을 유일하게 식벽할 수 있는 값을 가지고 있다.
- 하지만 어떤 Job을 실패하더라도 반복적으로 실행이 되어야 하는 경우가 있다. 이럴때 JobParameters 값이 동일해서 수행되지 못하는 경우에는 이러한 Parameters 값을 자동으로 증가시켜주는 기능이 필요하다.
- 이러한 식별값을 자동으로 증가시켜주는것이 incrementer()이다.
- 기존의 JobParameter 변경없이 Job 을 여러 번 시작하고자 할때
- RunIdIncrementer 구현체를 지원하며 인터페이스를 직접 구현할 수 있음(아래는 RunIdIncrementer의 구현체이다 코드를 분석해보자)
- 먼저 parameters에서 parameters가 null일 경우에는 새로운 parameters를 생성하고 아닐 경우에는 기존의 parameters를 가져온다.
- 이후 parameters의 정보가 담겨있는 params에서 id값을 가져온다. 이때 key값이 존재할 경우는 key값을 가져오고 없을 경우에는 새로운 Long id를 생성해서 가져온다.(최초 실행할 경우에는 key값이 없기 때문에 new Long이 있는것이다.)
2.구조
public Job batchJob() {
return jobBuilderFactory.get(“batchJob")
.start()
.next()
.incrementer(new RunIdIncrementer()) // 보통은 이걸 사용한다.
.incrementer(JobParametersIncrementer) //여기에 자신이 생성한 커스텀 Incremneter를 넣어도 되고 1씩 값을 상승해주는 new RunIdIncrementer()를 사용해도된다.
.validator()
.preventRestart()
.listener()
.build();
}
예제 코드
package com.example.springbatch_4_1_batchinitconfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
@Configuration
public class IncrementerConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job batchJob() {
return this.jobBuilderFactory.get("batchJob")
.start(step1())
.next(step2())
// .incrementer(new RunIdIncrementer())
.incrementer(new CustomJobParameterIncrementer())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
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();
}
}
package com.example.springbatch_4_1_batchinitconfiguration;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersIncrementer;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* packageName : com.example.springbatch_4_1_batchinitconfiguration
* fileName : CustomJobParameterIncrementer
* author : namhyeop
* date : 2022/07/24
* description :
* JobParamterIncrenter를 직접 만들어서 Incrementer의 이해도를 높여보자
* id값을 1씩 올리면 있는거를 만드는거니까 날짜 데이터를 활용해서 JobParamters의 중복도를 낮춰보자
* ===================================================
Parameters 설정
실행결과
Job_Execution tables
Job_Execution_Params tables
JobInstance
- 여러번 실행되면서 key값(parameter)은 동일하고 id값(날짜)만 바뀌면서 실행되는것을 알 수 있다.
- 또한 각 실행마다 다른 JobInstance가 생성되며 실행된다.(1번 인스턴스는 다른 테스트 값) 다른 인스턴스 값의 Execution이 실행되고 해당 Execution의 파라미터가 JOB_EXECUTION_PARMS Table에 저장된다.
SimpleJob 아키텍쳐
1.SimpleJob의 전체적인 흐름도와 각 단계에서 생성되는 메타데이터
- 큰 전체적인 로직으로는 Job-Step-Tasklet이 실행되기 되어지고 이 과정에서 각 단계마다 발생하는 메타데이터가 DB에 저장되게 된다. 이런 큰 흐름속에서 내부 로직이 어떻게 흘러가는지 다시 한 번 확인해보자
- JobLanuncher에서 SimpleJob으로 넘어가는 과정에서 JobInstance가 생성되게 된다.
- Job이 실행될 때 하나의 Job을 인식할 수 있는 유일한 JobInstance가 생성된다.
- JobInstance가 한 번 실행할 때마다 JobExecution이 생성된다. Job Instance를 여러번 실행할 수 있기 때문에 JobInstance와 JobExecution은 1대 다 관계이다.
- JobLauncher와 SimpleJob 사이에서 발생하는 JobInstance와 JobExecution이라는 메타데이터가 생성된다.
- SimJob과 Step사이에서 발생하는 메타데이터는 JobListener이다.
- JobListener는 Job을 실행하기전에 구성하여 등록할 수 있다.
- JobExectionListenr의 beforeJob이 수행되게 된다.
- Step단계에서 실행되는 메타이터는 StepExecution이다.
- StepExection이 생성하는 메타데이터는 ExecutionContext이다.
- Step까지 작업이 완료됐다면 step에서 SimpleJob으로 흐름이 오게 되고 이 과정에서 JobListener의 afterJob이 수행되게 된다.
- SimplerJob에서 JobLauncher로 오게 될경우 BatchStatus와 ExitStatus의 상태를 업데이트 해준다.
- 마지막 Step 작업의 상태(성공 or 실패) (예제 기준 밑의 두 번째 Job)에 따라서 작업 상태를 결정한다.
반응형
'Spring Batch' 카테고리의 다른 글
6.Spring Batch의 Flow (1) | 2024.11.07 |
---|---|
5.Spring Batch의 Step (0) | 2024.11.07 |
3.Spring Batch 도메인 이해하기 (0) | 2024.11.02 |
2.Spring Batch 시작하기 (0) | 2024.11.02 |
1.Spring Batch 소개 (0) | 2022.07.04 |