[Spring Batch] 환율 정보 API 를 간단한 배치 스케줄러 추가 - 예제편
들어가기 앞서
지난 시간 간단하게 만들었던 Batch에 Scheduler(스케줄러)와 Log를 추가한 내용을 작성한 글입니다. 스프링 배치를 사용하면서 언제, 어떻게 호출하고 실행시킬지에 대한 고민을 해야 합니다. 배치는 일괄적인 처리를 위해서 단독으로 사용되기도 하지만 실제 업무에서는 스케줄러와 같이 사용되는 것을 자주 볼 수 있습니다. 그렇다면 스케줄러 이외에 어떤 경우에 배치를 호출할까요? 클라이언트가 특정 URL 주소를 접근하게 되었을 때에도 사용합니다. 코드 내 Controller에서 해당 Service를 호출하고 Service에 특정 Batch를 호출하여 사용합니다.
배치(Batch)라고 떠올리실 때 '특정 시간에 특정 작업을 하는 것'이라고 생각하시면 안 됩니다. 배치는 데이터들을 일괄적으로 처리하기 위한 작업이며, 특정 일자나 특정 시간에 맞춰 작업을 하는 것은 스케줄러(Scheduler)입니다.
배치를 실행하고 특정 테이블(A)에 영향을 미치게 된다면, 특정 테이블(A) 에 걸려있는 트리거(Trigger)가 실행되고 예상치 못한 결과를 가져오기도 합니다. 배치를 사용하는 것은 일관성을 보장해 주지만 다른 영향도를 분석하고 사용하는 것이 중요합니다.
서론이 조금 길었네요. 본론으로 바로 가보겠습니다.
ExchangeBatchApplication.java
메인 함수가 있는 클래스 파일입니다.
@EnableScheduling
스프링 스케줄러(Scheduler)를 사용하기 위해, Spring Boot 실행파일에 @EnableScheduling를 선언하였습니다.
ExchangeBatchApplication.java 전체 코드
package com.main.exchangeBatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ExchangeBatchApplication {
public static void main(String[] args) {
// 스프링 애플리케이션을 실행합니다.
SpringApplication.run(ExchangeBatchApplication.class, args);
}
}
ExchageBatchScheduler.java
@Scheduled(cron = "0/10 * * * * *")
테스트를 위해 배치가 10초마다 발생되도록 설정하였습니다.
크론(cron) 표현식 정리
- 스케줄러 (Scheduler) 시간을 세팅할 때 사용됩니다.
- 필드는 총 7개이며, 연도는 생략이 가능합니다.
위 이미지에서 맨 마지막인 연도는 생략이 되었습니다.
cron에서 사용하는 특수문자
○ * : 모든 값을 뜻합니다.
○ ? : 특정한 값이 없음을 뜻합니다.
○ - : 범위를 뜻합니다.
○ , : 특별한 값일 때만 동작
○ / : 시작시간 / 단위
○ L : 일에서 사용하면 마지막 일, 요일에서는 마지막 요일(토요일)
○ W : 가장 가까운 평일
○ # : 몇째 주의 무슨 요일을 표현
@Scheduled(cron = "0/10 * * * * *") // 10초
public void runJob() throws Exception {
JobParameters parameters = new JobParametersBuilder()
.addString("jobName", "exchangeJob" + System.currentTimeMillis())
.toJobParameters();
// add parameters as needed
jobLauncher.run(job, parameters);
}
parameters의 유무
코드에서 parameters를 넘겨주는 코드를 발견할 수 있습니다.
addString으로 값을 넘겨주지 않고 아무것도 안 해도 되지 않을까 싶어 아래와 같은 코드를 작성을 해서 테스트해 보았습니다.
@Scheduled(cron = "0/10 * * * * *") // 10초
public void runJob() throws Exception {
JobParameters parameters = new JobParametersBuilder().toJobParameters();
jobLauncher.run(job, parameters);
}
결과는 어떻게 나왔을까요?
Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=step, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
'Step 이 이미 완료되었거나 재시작이 가능하지 않기 때문에 Step 실행(StepExecution)은 수행되지 않는다'
결론적으로 오류는 아니며, 정상적으로 실행은 되었지만 수행이 되지 않았습니다.
JobInstance를 식별하는 JobParameter
JobInstance를 고유하게 식별할 수 있게 하기 위해선 외부(또는 내부)로부터 받은 파라미터 값이 필요합니다. 제 코드에서는 실행시점을 나타내는 값 (System.currentTimeMillis()) 을 이용하여 JobInstance를 생성합니다.
ExchangeBatchScheduler.java 전체 코드
package com.main.exchangeBatch.scheduler;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ExchangeBatchScheduler {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Scheduled(cron = "0/10 * * * * *") // 10초
public void runJob() throws Exception {
JobParameters parameters = new JobParametersBuilder()
.addString("jobName", "exchangeJob" + System.currentTimeMillis())
.toJobParameters();
// add parameters as needed
jobLauncher.run(job, parameters);
}
}
로그(Log)
System.out.println을 사용한 것을 log.info로 수정하였습니다.
처음 자바를 사용해서 개발을 할 때 작성하는 것은 System.out.println("Hello world!");입니다. 하지만 실제 업무에서 운영 중인 코드를 보면 System.out.println()가 적혀있는 곳을 확인하기 어렵습니다. (필자는 반드시 다 지우려는 편입니다.)
Log와 System.out.println 에 대한 차이점은 번역하고 싶은 글이 있어 따로 정리할 예정이며, 아래 인용글은 좋은 내용이 적혀있어 공유드립니다.
한 번 요청 시 5000명의 사용자를 요청하고, 처리 과정에서 응답시간이 20초 걸리는 사이트가 있는데, 원인을 알아보니 5000명의 정보를 다 System.out.println()으로 처리하고 있던 것이다. 이는 System.out.println()을 줄임으로써 응답시간이 6초까지 줄었다.
- 이상민, 자바 성능 튜닝이야기, 인사이트, 2013
@Slf4j
@Slf4j는 롬복(Lombok) 어노테이션 중 하나로, SLF4J(Logger Facade for Java)를 사용하기 위한 로거를 자동으로 생성합니다. SLF4J는 Java 어플리케이션에서 로깅을 위한 추상화를 제공하며, 실제로 사용할 로깅 구현은 클래스패스에 존재하는 로깅 프레임워크에 의해 결정됩니다
ExchangeBatch.java
package com.main.exchangeBatch.batch;
... 생략
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class ExchangeBatch {
... 생략
@Bean
public Tasklet tasklet(){
return ((contribution, chunkContext) -> {
List<ExchangeDto> exchangeDtoList = exchangeUtils.getExchangeDataAsDtoList();
for (ExchangeDto exchangeDto : exchangeDtoList) {
log.info("통화 : " + exchangeDto.getCur_nm());
log.info("환율 : " + exchangeDto.getDeal_bas_r());
// 추가적인 필드가 있다면 출력 또는 활용
}
return RepeatStatus.FINISHED;
});
}
}
실행 결과
Github 링크
https://github.com/SeoYounSeok/exchangeBatch.git
참고 사이트