스프링 배치(Spring Batch)는 대용량 데이터를 처리하기 위한 프레임워크로, 스프링 프레임워크 기반에서 작동한다.
일반적으로 배치 작업은 대량의 데이터를 처리하거나, 주기적이고 반복적인 작업을 실행하는 데 사용되며, 스프링 배치는 이러한 작업을 효율적이고 안정적으로 처리할 수 있는 프레임워크다.
이제 스프링 배치를 구현할 준비를 해보자.
implementation 'org.springframework.boot:spring-boot-starter-batch'
testImplementation 'org.springframework.batch:spring-batch-test'
spring:
batch:
job:
enabled: false # 프로젝트 실행 시 자동으로 배치 작업이 가동되는 것을 방지
Job으로 하나의 배치 작업을 정의하고,
실제 배치 처리는 Job 아래에 존재하는 하나의 Step에서 수행한다.
Step에서 "읽기 > 처리 > 쓰기" 과정을 구상해야 하며, Step을 등록하기 위한 Bean을 등록해야 한다.
@Configuration
@EnableBatchProcessing // 스프링 배치를 작동시켜준다.
@RequiredArgsConstructor
public class BatchConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager platformTransactionManager;
private final BeforeRepository beforeRepository;
private final AfterRepository afterRepository;
}
@Bean
public Job firstJob() {
// 첫번째 매개변수 : 해당 Job을 지칭할 이름 선언 ("firstJob")
// 두번째 매개변수 : 해당 작업에 대한 트래킹을 진행하기 위해 jobRepository를 넣어주면
// 스프링 배치가 자동으로 작업이 진행되는지를 메타 데이터 테이블에 기록해준다.
return new JobBuilder("firstJob", jobRepository)
.start(firstStep) // 이 작업에서 처음 시작할 Step 선언
.next(secondStep) // Step이 1개 이상일 때, next()로 이후의 Step을 선언
.build(); // build()로 마무리하면 해당 작업이 정의된다.
}
@Bean
public Step firstStep() {
return new StepBuilder("firstStep", jobRepository)
// <[Reader에서 읽어들일 데이터 타입], [Writer에서 쓸 데이터 타입]>
.<BeforeEntity, AfterEntity>chunk(10, platformTransactionManager)
.reader(beforeReader) // 읽는 메서드 자리
.processor(middleProcessor) // 처리 메서드 자리
.writer(afterWriter) // 쓰기 메서드 자리
.build();
}
@Bean
public RepositoryItemReader<BeforeEntity> beforeReader() {
return new RepositoryItemReaderBuilder<BeforeEntity>()
.name("beforeReader")
.pageSize(10)
.methodName("findAll") // Repository의 findAll 메서드
.repository(beforeRepository)
.sorts(Map.of("id", Sort.Direction.ASC))
.build();
}
@Bean
public ItemProcessor<BeforeEntity, AfterEntity> middleProcessor() {
// <[Reader에서 읽어들일 데이터 타입], [Writer에서 쓸 데이터 타입]>
return new ItemProcessor<BeforeEntity, AfterEntity>() {
// process 메서드를 통해 item(BeforeEntity)에 읽어들인 데이터가 담기게 됨
@Override
public AfterEntity process(BeforeEntity item) throws Exception {
AfterEntity afterEntity = new AfterEntity();
afterEntity.setName(item.getName()); // 읽어들인 데이터를 쓸 데이터에 옮겨담음
return afterEntity;
}
};
}
@Bean // RepositoryItemWriter<[저장될 엔티티]>
public RepositoryItemWriter<AfterEntity> afterWriter() {
return new RepositoryItemWriterBuilder<AfterEntity>()
.repository(afterRepository) // afterRepository를 통해서
.methodName("save") // save 쿼리 실행
.build();
}
application.yml에서 배치 자동 실행에 대한 변수 값을 false로 설정했기 때문에
배치를 실행시키기 위한 Job 실행도구,jobLauncher
를 구현해야 한다.
❓ false로 설정한 이유
- 서버가 실행되자마자 배치가 실행되면, 원하는 날짜에 실행할 수 없다.
원하는 특정 일자에 배치를 실행하기 위해 스케줄링 혹은 특정 API 호출을 통해 실행되도록 설정한다.
@Controller
@RequiredArgsConstructor
public class ApiController {
private final JobLauncher jobLauncher;
private final JobRegistry jobRegistry;
@GetMapping("/first")
public String firstApi(@RequestParam("value") String value) throws Exception {
// 쿼리 파라미터로 받아온 value 값을 date 변수에 넣어준다.
JobParameters jobParameters = new JobParametersBuilder()
.addString("date", value)
.toJobParameters();
// "firstJob" : 2-1 Job(작업 정의)에서 Bean으로 정의한 Job의 이름
jobLauncher.run(jobRegistry.getJob("firstJob"), jobParameters);
return "ok";
}
}
1개의 Job에 대해 한 번 호출한 파라미터는 재사용이 불가하다.
http://localhost:8080/first?value=a
호출 시,
배치 작업이 실행되어 BeforeEntity -> AfterEntity로 Insert 작업이 수행된다.
한 번 더 http://localhost:8080/first?value=a
호출 시, 예외가 발생한다.
이미 a라는 파라미터가 들어갔기 때문에 해당 배치가 실행되지 않는다.
http://localhost:8080/first?value=b
호출 시, 배치가 제대로 실행된다.
@SpringBootApplication
@EnableScheduling // 스케줄링 어노테이션 활성화 설정
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
@Configuration
@RequiredArgsConstructor
@Slf4j
public class ScheduleConfig {
private final JobLauncher jobLauncher;
private final JobRegistry jobRegistry;
// 한국 시간 기준으로 10초마다 배치 작업 실행
@Scheduled(cron = "10 0 0 * * *", zone = "Asia/Seoul")
public void runFirstJob() throws Exception {
log.info("first schedule start");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");
String date = dateFormat.format(new Date());
JobParameters jobParameters = new JobParametersBuilder()
.addString("date", date)
.toJobParameters();
// "firstJob" : 2-1 Job(작업 정의)에서 Bean으로 정의한 Job의 이름
jobLauncher.run(jobRegistry.getJob("firstJob"), jobParameters);
}
}
References