Spring Batch api reader 만들기 1편 - 데이터 요청 및 파싱하기

기운찬곰·2022년 8월 16일
0

참고 : https://innopc.tistory.com/31
참고 2: https://www.petrikainulainen.net/programming/spring-framework/spring-batch-tutorial-reading-information-from-a-rest-api/
참고 2 깃허브 : https://github.com/pkainulainen/spring-batch-examples

스프링 부트 배치, RestAPI 데이터 읽기

스프링 배치의 ItemReader는 파일/XML/DB에서 데이터를 읽기 위한 전용 클래스를 제공하지만 RestAPI로 다른시스템의 데이터를 읽어오는 전용 클래스는 아직 없다고 한다.

Rest 클라이언트 기능은 스프링에서 제공하는 RestTemplate를 이용하면 간단하게 구현 가능하다.

사용할 API - 국립중앙도서관 소장자료

참고 : https://www.culture.go.kr/data/openapi/openapiView.do?id=468&category=F&gubun=A

사용이유 : totalCount가 649358로 가장 많아서.

넘어오는 구조는 다음과 같다. title이랑 person 말고는 별로 쓸모는 없군… 음 데이터를 보니까 일본어, 중국어도 많이 보이고 정작 도서에 대한 정보는 별로 없는듯…

GET http://api.kcisa.kr/openapi/service/rest/meta16/getNlTot?serviceKey=서비스키&numOfRows=10&pageNo=1
Accept: application/json
{
  "header": {
    "resultCode": "string",
    "resultMsg": "string"
  },
  "body": {
    "items": {
      "item": [
        {
          "title": "string",
          "creator": "string",
          "collectionDb": "string",
          "extent": "string",
          "description": "string",
          "url": "string",
          "affiliation": "string",
          "subDescription": "string",
          "spatialCoverage": "string",
          "person": "string"
        }
      ]
    },
    "numOfRows": "10",
    "pageNo": "1",
    "totalCount": "string"
  }
}

Spring에서 API 요청 - RestTemplate 사용법

  • Spring에서 지원하는 객체로 간편하게 Rest 방식 API를 호출할 수 있는 Spring 내장 클래스입니다.
  • Spring 3.0부터 지원되었고, json, xml 응답을 모두 받을 수 있습니다.
  • Rest API 서비스를 요청 후 응답 받을 수 있도록 설계되어있으며 HTTP 프로토콜의 메소드(ex. GET, POST, DELETE, PUT)들에 적합한 여러 메소드들을 제공합니다.

※ Spring Framework 5부터는 WebFlux 스택과 함께 Spring은 WebClient 라는 새로운 HTTP 클라이언트를 도입하여 기존의 동기식 API를 제공할 뿐 만 아니라 효율적인 비차단 및 비동기 접근 방식을 지원하여, Spring 5.0 이후 부터는 RestTemplate는 deprecated 되었습니다. (WebClient 사용 지향)

참고 : https://blog.naver.com/hj_kim97/222295259904

하지만 RestTemplate도 아직까지는 많이 쓰이는거 같으므로 여기서는 RestTemplate를 사용해보도록 하겠다.

RestTemplate GET 요청시 headers와 query string을 함께 요청하는 방법에 대해 알아본다. HttpHeaders를 이용해 헤더 값을 지정할 수 있고 이때는 exchange 메서드를 사용해서 요청해야 한다.

query string 을 같이 요청하려면, url string에 직접 추가해줄수도 있겠지만 아무래도 그런것보다는 좀 더 쉽고 보기 좋게 하는 방법이 좋을 거 같아서… 찾아보니 UriComponentsBuilder를 많이 쓰는거 같음.

UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(apiUrl)
                .queryParam("numOfRows", "100")
                .queryParam("pageNo", 1);

log.info("Fetching book data from an external API by using the url: {}", uriBuilder.toUriString());

ResponseEntity<String> response = restTemplate.exchange(uriBuilder.toUriString(), HttpMethod.GET,
        new HttpEntity<>(headers), String.class);

RestTemplate JSON 데이터 파싱하기

우리가 관심있는 부분은 item 배열과 numOfRows, pageNo, totalCount 이다. 이 부분을 파싱해보도록 하자.

json 라이브러리 중에 jackson과 gson이 있는데 jackson이 스프링에 내장되어있어서 따로 설치가 필요없다는 점 때문에 요즘에는 jackson을 더 많이 사용하는듯하다. (스프링에서 밀어주나?)

jackson 사용해서 파싱하기

해당 포스트에서 첫번째 방법을 사용해서 하나씩 파싱해서 형변환하고 이 작업을 반복해서 내가 원하는 값을 뽑아내고 있는데 코드가 정말… 더럽다. 자바에서 json 파싱은 진짜 너무 어려운거 같다.

이게 아니라면 하나씩 클래스를 만들어서 지정해야 하는데 이건 깔끔한 대신에 클래스 하나하나 다 생성해야 되므로 이것도 단점이 있고.

private List<BookDTO> fetchBookDataFromAPI() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Accept", "application/json");

    UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(apiUrl)
            .queryParam("numOfRows", "100")
            .queryParam("pageNo", 1);

    log.info("Fetching book data from an external API by using the url: {}", uriBuilder.toUriString());

    ResponseEntity<String> response = restTemplate.exchange(uriBuilder.toUriString(), HttpMethod.GET,
            new HttpEntity<>(headers), String.class);

    // Json parsing 하는 부분... 근데 이게 최선인가 싶네
    ObjectMapper objectMapper = new ObjectMapper();
    Map<String, Object> responseObject = objectMapper.readValue(response.getBody(),
            new TypeReference<Map<String, Object>>() {});

    Map<String, Object> responseProperty = (Map<String, Object>) responseObject.get("response");
    Map<String, Object> bodyProperty = (Map<String, Object>) responseProperty.get("body");
    Map<String, Object> itemsProperty = (Map<String, Object>) bodyProperty.get("items");

    BookDTO[] bookData = objectMapper.readValue(objectMapper.writeValueAsString(itemsProperty.get("item")), BookDTO[].class);

    int numOfRows = Integer.parseInt(bodyProperty.get("numOfRows").toString());
    int pageNo = Integer.parseInt(bodyProperty.get("pageNo").toString());
    int totalCount = Integer.parseInt(bodyProperty.get("totalCount").toString());

    return Arrays.asList(bookData);
}

jsonschema2pojo 사용해보기

json을 객체로 매핑시키려면 결국 하나씩 클래스를 만들어서 지정해줘야 하는데 이런걸 대신하는 사이트가 있다.

참고 : https://github.com/joelittlejohn/jsonschema2pojo

POJO라는 뜻은 ‘Plain Old Java Object'의 약자이다. json 형태를 넣어주고 원하는 스타일 형식을 넣어주면 그 형식에 맞게 자바 클래스를 생성해준다. 그러면 우리는 이걸 복붙해서 사용하면 되는 것이다.

차라리 이 방식이 더 나은걸수도… 다음에는 이렇게 해봐야지…


깃허브 전체 코드 : https://github.com/ckstn0777/elasticsearch-test

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

0개의 댓글