Project : Gradle - Groovy
Language : Java
Spring Boot : 2.7.6
Project Metadata :
Group : www.zigdeal.shop
Artifact : apiBatch
Name : apiBatch
Description : api Batch Application
Package name : www.zigdeal.shop.apiBatch
Packaging : Jar
Java : 11
Dependencies :
Spring Boot DevTools
Spring Batch
Quartz Schedular
Spring Data MongoDB
Lombok
1) Ctrl + Shift + A → share project on github
2) .idea 디렉토리는 commit X
3) .ignore 플러그인 설치 (IntelliJ는 .gitignore에 대한 기본적인 지원 X)
4) Ctrl + Shift + A → plugins 검색 → .ignore 설치
@EnableBatchProcessing
본 스프링부트 Application이 Batch를 사용할 것이라는 Annotation
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
Spring Batch는 JDBC 기반으로 구현되어 있기 때문에 application.yml → spring → datasource에서 DB의 uri 주소를 요구한다
우리는 MongoDB를 사용할 것이기 때문에 datasource에서 DB의 uri 주소를 찾기 않도록 설정을 수정해준다
(이 Annotation을 사용하지 않을 경우 에러가 뜨니 Spring Batch를 MongoDB와 함께 사용할 경우 꼭 추가해주자)
www/zigdeal/shop/apiBatch/batch/ExchangeRateConfiguration.java
하나의 Job에 대한 설정이 이루어지는 곳이다
내부를 하나씩 뜯어보자
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
Spring Batch에서 정의된 JobBuilderFactory와 StepBuilderFactory을 위와 같이 선언해 Spring이 알아서 의존성 주입을 하게 해준다
@Bean
public Job exchangeRateJob(Step exchangeRateStep) {
return jobBuilderFactory.get("exchangeRateJob")
.start(exchangeRateStep)
.build();
}
밑에서 정의할 Step exchangeRateStep
을 의존성 주입하여 Job을 정의한다
이 Job은 하나의 Step 으로 이루어져 있으므로 Step exchangeRateStep
만 실행하도록 한다
@Bean
public Step exchangeRateStep(MongoItemWriter<ExchangeRate> exchangeRateMongoItemWriter) {
return stepBuilderFactory.get("exchangeRateStep")
.<ExchangeRate, ExchangeRate>chunk(1)
.reader(exchangeRateItemReader())
.writer(exchangeRateMongoItemWriter)
.build();
}
실질적인 비즈니스 로직을 수행할 Step이다
.<ExchangeRate, ExchangeRate>chunk(1)
chunk 단위를 1로 선언하고
.reader(exchangeRateItemReader())
다른 패키지에서 구현한 exchangeRateItemReader()
로 필요한 데이터를 읽고
.writer(exchangeRateMongoItemWriter)
아래에 구현한 MongoItemWriter<ExchangeRate> exchangeRateMongoItemWriter
로 DB에 쓴다
reader와 writer 중간에 processor를 추가할 수 있지만 환율의 경우 전처리할 게 없어서 사용하지 않았다
public class ExchangeRateReader implements ItemReader<ExchangeRate> {
@Override
public ExchangeRate read() {
ExchangeRate exchangeRate = new ExchangeRate();
crawling(exchangeRate);
return exchangeRate;
}
ItemReader 부분인데, 새로운 ExchangeRate 객체를 만들고 크롤링한 값을 넣어준다
@Bean
public MongoItemWriter<ExchangeRate> exchangeRateMongoItemWriter(MongoTemplate mongoTemplate) {
return new MongoItemWriterBuilder<ExchangeRate>()
.template(mongoTemplate)
.collection("exchangeRate")
.build();
}
ItemWriter 부분인데, ItemWriter는 사용하는 DB에 따라 다르게 구현된 객체를 사용해야 한다
MongoDB에서는 MongoItemWriter를 제공한다
ItemWriter는 공통적으로 비즈니스 로직을 수행하지 않고 DB에 값을 저장만 하는 단계이기 때문에 위와 같이 어느 정도 정형화되어 있다 (= 복붙하면 된다)
ExchangeRate가 제대로 갱신되는 것을 확인할 수 있다
해당 배치가 반복적으로 실행되는 것을 알 수 있는데, 그 이유는
Spring Batch는 ItemReader가 Null을 반환할 때까지 배치를 계속 돌리기 때문이라고 한다
DB에 잘 저장되었다
하나만 더 첨언하자면 PK를 MongoDB에서 default로 넣어주는 값을 사용하면 같은 이름을 가진 환율 값들이 계속해서 DB에 추가된다 (id를 정의해주지 않고 계속해서 document를 save하기 때문)
그래서 ExchangeRate Domain을 다음과 같이 수정해준다
@Data
@Document(collection = "exchangeRate")
public class ExchangeRate {
@Id
private String name;
private Double exchangeRate;
}
이렇게 하면 PK가 외화의 name값으로 정의되기 때문에 document를 계속 save해도 갱신이 된다
ItemReader에서 crawling을 통해 환율 정보를 가져오는데, 현재 구현되어 있는 로직에서는
class = “spt_con dw”을 찾아 여기서 0번째 element를 읽어오게 구현되어 있다.
이 태그는 환율 화면에서 화살표 방향과 색 (파란색 / 빨간색)을 적용시키기 위한 class이기 때문에 환율이 올라갈 때는 JavaScript로 class = “spt_con up”으로 바뀌게 되어 있다.
물론 2가지를 모두 읽어서 검증하는 방식도 있겠지만 거래소 같은 데서 REST API로 환율 정보 자체를 읽어오는 게 더 깔끔하다는 생각이 든다.
ㄴ 환율 api 추가함.
Open API 제공목록_Open API 제공목록 - 상세 : 한국수출입은행 메인한글홈페이지 (koreaexim.go.kr)
ItemReader에서 crawling이든 REST API로 환율 데이터를 받아오든 절대로 null이 나오는 경우는 없기 때문에 1번 실행하고 더이상 배치가 실행되지 않도록 종료해주는 코드를 추가해야 한다.
ㄴ JobExecution 써야되나?
ㄴ https://www.slipp.net/questions/488 읽고 나서 null 반환하면 될 듯?
Spring Quartz로 일정 시간마다 환율을 갱신해주도록 개선해야 한다