Open 0JUUU opened 2 years ago
Job Configuration
을 통해 생성되는 객체 단위 (실체가 있다) ➡️ 하나의 명세서와 같음 (어떻게 구성하고 실행해라!)다양한 구현체가 있지만, 2개를 알아보자.
실행 흐름
Job
설정 ➡️ Step
설정 ➡️ 설정한 Job
을 JobLauncher
가 실행 ➡️ 실행시켜 Job
이 구동됨 ➡️ Job
이 각각의 Step
실행 ➡️ Step
이 자기가 갖고 있는 tasklet
실행
Job이나 Step이 실행되는 시점마다 생성되어 메타데이터
를 DB에 저장하기 위한 용도
JOB_NAME
(job) + JOB_KEY
(JobParameter hash 값) ➡️ 유일! (중복 저장 불가)Job Instance = Job + Job Parameters
// 스프링부트가 자동실행하게 하지 않고, 수동으로 구성함
@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
같은 JobParameter로 시도
JobParameter 변경
JobParameters jobParameters = new JobParametersBuilder().addString("name", "user2").toJobParameters();
JobParameters
: JobInstance
= 1:1// 수동으로 구성
@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);
}
}
@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를 실행시킬 때, 인자 값을 넘겨주면 JobParameters 설정 가능
// 근데 얘 (long) 안된다고 나옴... 왜?
java -jar spring-batch-0.0.1-SNAPSHOT.jar name=user1 seq=(long)2 date=2022/03/24 age=(double)16.5
JobInstance에 대한 한 번의 시도를 의미하는 객체 ➡️ Job 실행 중 발생한 정보 저장
FAILED
, COMPLETED
등의 Job 실행 결과 상태를 가짐
COMPLETED
➡️ JobInstance 실행이 완료된 것으로 간주, 재실행 불가FAILED
➡️ JobInstance 실행 완료 ❌, 재실행 가능COMPLETED
될 때까지 하나의 JobInstance 내에서 여러 번의 시도 발생 가능JobInstacne와 JobExecution은 1:M 관계 ➡️ JobInstance에 대한 성공/실패 내역을 가짐
실행결과
@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();
}
@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();
}
변함 없다❗️❗️
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;
}
}
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();
}
}
@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();
}
: 성공한 step은 다시 실행하지 않는다.
apply
메서드 호출 ➡️ 상태 업데이트String jobName = contribution.getStepExecution().getJobExecution().getJobInstance().getJobName();
객체의 상태(속성의 값)
를 저장하는 공유 객체{ key : value }
이미 처리한 Row 데이터
는 건너뛰고 이후 수행하도록 할 때 상태 정보 활용!ExecutionContext jobExecutionContext = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext();
ExecutionContext stepExecutionContext = chunkContext.getStepContext().getStepExecution().getExecutionContext();
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");
}
이전과 동일
SyncTaskExecutor
로 설정 (default 값)SimpleAsyncTaskExecutor
로 설정내가 커스텀한 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를 준다.