You will be fine

<Spring Batch> API 호출해서 DB에 저장하기(with JPA)

by BFine
반응형

2021/02/25 - [개발공부/Spring ] - 예제 프로그램 따라하기

 

예제 프로그램 따라하기

스케줄러 형태로 되어있는 배치는 많이 봤는데 Spring Batch를 사용한 배치 프로그램은 본적이 없는 것 같다. 궁금하기도 하고 공부해뒀다가 나중에 시간될때 적용해봐야겠다!!! 예제 : spring.io/guides

willbfine.tistory.com

저번에 예제 프로그램을 따라해보고 이정도면 크게 어렵지 않네? 라는 착각을 하고 있었다...

예제를 활용해서 간단한거 만들어봤는데 오류가 오류가... 쉴틈없이 쏟아졌다.. Spring Batch 쉽지않다

 

가. 고민하기

 a. 뭘 만들까?

  -  일단 API 요청을 통해 JSON을 받아서 그걸 활용해서 만들어 보고 싶었다.

  -  처음에는 Tistory API를 써보려 했는데 보안 정책이 강화되어 implict 방식이 없어졌다 

  -  고민하던중 예전에 공부할때 쓰던 영화 API가 생각나서 이걸로 하기로 결정했다.

       => www.kobis.or.kr/kobisopenapi/homepg/apiservice/searchServiceInfo.do

 

영화진흥위원회 오픈API

제공서비스 영화관입장권통합전산망이 제공하는 오픈API서비스 모음입니다. 사용 가능한 서비스를 확인하고 서비스별 인터페이스 정보를 조회합니다.

www.kobis.or.kr

 

 b. 동작

  -  단순하다 순서는

         1. 영화 API를 호출 (JSON)

         2. 오늘 상영객수 * 만이천원(영화 관람료.. 많이 올랐네)

         3. 일매출액을 DB에 저장 (JPA) 

 

나. Step

 a. reader

  -  API를 호출해야하기 때문에 상속받아 구현했다. 

  -  WebClient를 사용해서 API를 호출해 보았다. 예전에 한번 써봤는데 기억이 나지 않아서 오류가 많이 생겼다.

        => JSON 응답일 경우 Jackson 관련 설정을 꼭 해줘야한다.

@RequiredArgsConstructor
public class BoxOfficeItemReader implements ItemReader<BoxOfficeResult> {

    @Override
    public BoxOfficeResult read() {

       ExchangeStrategies strategies = ExchangeStrategies
                 .builder()
                 .codecs(clientDefaultCodecsConfigurer -> {
                            clientDefaultCodecsConfigurer.defaultCodecs()
                            .jackson2JsonEncoder
                            (new Jackson2JsonEncoder(new ObjectMapper(),
                            MediaType.APPLICATION_JSON));
                            clientDefaultCodecsConfigurer
                            .defaultCodecs()
                            .jackson2JsonDecoder
                            (new Jackson2JsonDecoder(
                            new ObjectMapper(),
                            MediaType.APPLICATION_JSON));

                        }).build();

              WebClient webClient = WebClient.builder()
              .exchangeStrategies(strategies).build();
              
              Mono<InputData> boxOfficeResultMono = webClient.get()
              .uri(BASE+BOXOFFICE+API_KEY+targetDt)
              .accept(MediaType.APPLICATION_JSON)
              .retrieve()
              .bodyToMono(InputData.class)
              .log();

      InputData result = boxOfficeResultMono.block();
      return result.getBoxOfficeResult();
     
    }

 

 b. processor

  -  로직을 처리하자

public class BoxOfficeItemProcessor implements ItemProcessor<BoxOfficeResult, List<OutputData>> {

    private final int price = 12000;

    @Override
    public List<OutputData> process(BoxOfficeResult input){
        LocalDate targetDate = LocalDate.now();
        List<DailyBoxOfficeList> dailyBoxOfficeList = input.getDailyBoxOfficeList();

        return dailyBoxOfficeList.stream().map(i -> OutputData.builder()
        			.movieName(i.getMovieNm())
        			.targetDate(targetDate)
       				.totalRevenue(Integer.parseInt(i.getAudiCnt()) * price)
        			.build())
        			.collect(Collectors.toList());
    }
}

 

 c. writer

  -  제일 고생한 부분이다 .. 처음에는 JpaItemBuilder를 활용해서 예제처럼 해보려고 했지만 문제가 생겼다.

        => Processor 에서 List를 리턴하기 때문에 Not an entity[class java.util.ArrayList] 발생했다.

  -  참고 :  jojoldu.tistory.com/140  

 

Spring Batch ItemWriter에 List 전달하기

안녕하세요? 이번 시간엔 springboot-batch에서 writer에 List를 전달하는 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을

jojoldu.tistory.com

  -  위 블로그에 잘 정리 되어있는데  결국 writer도 상속받아서 처리해야 한다.

  -  만들고 나서 보니 An EntityManagerFactory is required 오류가 발생한다.

        => afterPropertiesSet 에서 entityManagerFactory가 set 되기전에 호출하기 때문에 발생한다.

@AllArgsConstructor
public class BoxOfficeItemWriter<T> extends JpaItemWriter<List<T>> {

    private final JpaItemWriter<T> jpaItemWriter;

    @Override
    @Transactional
    public void write(List<? extends List<T>> items) {

        List<T> collect = new ArrayList<>();

        for(List<T> list : items){
            collect.addAll(list);
        }

        jpaItemWriter.write(collect);
    }

    @Override
    public void afterPropertiesSet(){
		// An EntityManagerFactory is required 방지
    }
}

다. Config

 a. Batch

  -  공들인 시간에 비해 큰 틀은 예제와 거의 비슷하다...

@Configuration
@EnableBatchProcessing
@RequiredArgsConstructor
public class BatchConfig {

    public final JobBuilderFactory jobBuilderFactory;
    public final StepBuilderFactory stepBuilderFactory;
    public final EntityManagerFactory entityManagerFactory;

    @Bean
    public BoxOfficeItemReader reader(){
        return new BoxOfficeItemReader();
    }

    @Bean
    public BoxOfficeItemProcessor processor(){
        return new BoxOfficeItemProcessor();
    }

    @Bean
    public BoxOfficeItemWriter<OutputData> writer(){
        JpaItemWriter<OutputData> writer = new JpaItemWriter<>();
        writer.setEntityManagerFactory(entityManagerFactory);
        return new BoxOfficeItemWriter<>(writer);
    }


    @Bean
    public Step step(){

        return stepBuilderFactory.get("step")
            .<BoxOfficeResult,List<OutputData>>chunk(1)
            .reader(reader())
            .processor(processor())
            .writer(writer())
            .build();
    }

    @Bean
    public Job job(JobCompleteListener jobCompleteListener,Step step){
        return jobBuilderFactory.get("job")
            .listener(jobCompleteListener)
            .flow(step)
            .end()
            .build();
    }
}

라. 테스트

 a. 실행

  -  여기까지 했으면 실행시 Batch 무한루프가 발생할 것이다.

        => 단순하다 Reader는 null이 아닐때까지 반복한다. DB나 파일에 끝이 있듯이 이것도 끝을 만들어 주어야한다.

  -  null 처리 이후에 다시 실행해보면..

 b. DB 확인

  -  데이터가 잘 입력되었는지 확인해 보자

  -  어림잡아서 했지만 톱 순위의 일매출이 장난아니다.. 코로나 아니었으면 얼마일까...

  -  내장으로 사용할때는 몰랐는데 DB에 Spring Batch 관련 스키마가 저장된다.

  -  다음 포스팅에는 이부분을 정리하면서 dataSource 구성을 변경해봐야겠다.

  -  내장 DB에 Spring Batch Table Schema 를 처리하도록 해봐야지  

         => 저 테이블이 공용DB에 만들어진다면 상상만해도 끔직하다.

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기