공공 데이터 포털이란?
공공데이터포털은 공공기관이 생성 또는 취득하여 관리하고 있는 공공데이터를 한 곳에서 제공하는 통합 창구이다.
공공데이터포털에서는 국민이 쉽고편리하게 공공데이터를 이용할 수 있도록 파일데이터, 오픈API, 시각화 등 다양한 방식으로 제공하고 있으며, 누구라도 쉽고 편리한 검색을통해 원하는 공공데이터를 빠르고 정확하게 찾을 수 있다.
https://www.data.go.kr/index.do
공공데이터 포털
국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase
www.data.go.kr
사용하기 위한 필요 과정
정말 간단하다. 사이트에 회원 가입 후 사용하길 희망하는 오픈 API에 활용신청 하여 API 키를 발급받으면 된다.
주의할 점이 있다면 대부분 API는 상시 허가로 되어 있어 신청하자마자 바로 사용할 수 있지만 몇몇 API와 데이터 같은 경우에는 신청 후 승인까지의 대기시간이 있을 수 있다는 점이다.
사용하기 전 미리보기 기능
마이페이지 -> API 신청 -> 신청한 API -> 미리보기 버튼을 활용하여 Rest API 방식으로 요청을 보내는 파라미터들을 임의로 조작하여 미리 어떤 결과가 나오는지 확인이 가능하다.
만약 공공데이터 사용이 처음이라면 미리보기 기능을 통해 어디까지 활용이 가능하고 어떻게 활용해야 겠다 계획을 세우고 하는 것을 추천한다.
<미리 보기 한 결과>
데이터 포털에서 제공해주는 Java 1.8 샘플 코드
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.io.BufferedReader;
import java.io.IOException;
public class ApiExplorer {
public static void main(String[] args) throws IOException {
StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList"); /*URL*/
urlBuilder.append("?" + URLEncoder.encode("serviceKey","UTF-8") + "=서비스키"); /*Service Key*/
urlBuilder.append("&" + URLEncoder.encode("pageNo","UTF-8") + "=" + URLEncoder.encode("1", "UTF-8")); /*페이지번호*/
urlBuilder.append("&" + URLEncoder.encode("numOfRows","UTF-8") + "=" + URLEncoder.encode("3", "UTF-8")); /*한 페이지 결과 수*/
urlBuilder.append("&" + URLEncoder.encode("entpName","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*업체명*/
urlBuilder.append("&" + URLEncoder.encode("itemName","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*제품명*/
urlBuilder.append("&" + URLEncoder.encode("itemSeq","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*품목기준코드*/
urlBuilder.append("&" + URLEncoder.encode("efcyQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약의 효능은 무엇입니까?*/
urlBuilder.append("&" + URLEncoder.encode("useMethodQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약은 어떻게 사용합니까?*/
urlBuilder.append("&" + URLEncoder.encode("atpnWarnQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약을 사용하기 전에 반드시 알아야 할 내용은 무엇입니까?*/
urlBuilder.append("&" + URLEncoder.encode("atpnQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약의 사용상 주의사항은 무엇입니까?*/
urlBuilder.append("&" + URLEncoder.encode("intrcQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약을 사용하는 동안 주의해야 할 약 또는 음식은 무엇입니까?*/
urlBuilder.append("&" + URLEncoder.encode("seQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약은 어떤 이상반응이 나타날 수 있습니까?*/
urlBuilder.append("&" + URLEncoder.encode("depositMethodQesitm","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*이 약은 어떻게 보관해야 합니까?*/
urlBuilder.append("&" + URLEncoder.encode("openDe","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*공개일자*/
urlBuilder.append("&" + URLEncoder.encode("updateDe","UTF-8") + "=" + URLEncoder.encode("", "UTF-8")); /*수정일자*/
urlBuilder.append("&" + URLEncoder.encode("type","UTF-8") + "=" + URLEncoder.encode("xml", "UTF-8")); /*응답데이터 형식(xml/json) Default:xml*/
URL url = new URL(urlBuilder.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-type", "application/json");
System.out.println("Response code: " + conn.getResponseCode());
BufferedReader rd;
if(conn.getResponseCode() >= 200 && conn.getResponseCode() <= 300) {
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
rd = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
StringBuilder sb = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
rd.close();
conn.disconnect();
System.out.println(sb.toString());
}
}
해당 코드를 그대로 가져와서 수정하여 사용하여도 되지만 가급적 최신 자바 코드 라이브러리와 스프링 부트의 라이브러리를 활용하는 방법으로 코드를 수정하고자 한다.
최신 자바 라이브러리(HttpClient) 사용 버전
@RestController
public class DrugOpenAPIController {
@GetMapping("/publicData")
public String getDrugInfo() throws Exception {
HttpClient client = HttpClient.newHttpClient();
String url = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
String charset = StandardCharsets.UTF_8.name();
String query = String.format("serviceKey=%s&pageNo=%s&numOfRows=%s&entpName=%s&itemName=%s&itemSeq=%s&efcyQesitm=%s&useMethodQesitm=%s&atpnWarnQesitm=%s&atpnQesitm=%s&intrcQesitm=%s&seQesitm=%s&depositMethodQesitm=%s&openDe=%s&updateDe=%s&type=%s",
URLEncoder.encode("Decoding 인증키", charset),
URLEncoder.encode("1", charset),
URLEncoder.encode("1", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("", charset),
URLEncoder.encode("json", charset));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url + "?" + query))
.GET()
.header("Content-Type", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Response code: " + response.statusCode());
HttpHeaders headers = response.headers();
headers.map().forEach((k, v) -> System.out.println(k + ":" + v));
String responseBody = response.body();
// Jackson 객체 생성
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// JSON 문자열을 Map 객체로 변환
Map<String, Object> map = mapper.readValue(responseBody, Map.class);
// 'body' key에 해당하는 부분을 추출
Map<String, Object> bodyMap = (Map<String, Object>) map.get("body");
// 'items' key에 해당하는 부분을 추출
List<Map<String, Object>> items = (List<Map<String, Object>>) bodyMap.get("items");
// items를 JSON 문자열로 변환
String prettyItemsJson = mapper.writeValueAsString(items);
System.out.println(prettyItemsJson); // 수정된 JSON 출력
return prettyItemsJson;
}
}
해당 코드에서는 Jackson 객체를 사용하여 응답 body 부분의 item 항목의 데이터를 파싱 하는 것 까지 추가해보았다.
스프링 부트에서 권장하는 라이브러리(WebClient) 사용 버전
<필요한 의존성> https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux/3.1.0
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.0'
<블록킹 방식>
@RestController
public class DrugOpenAPIController {
private final static String BASE_URL = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
private final String API_KEY = "encoding API키";
@GetMapping(value = "/publicData", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> getDrugInfo() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
WebClient webClient = WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.build();
ResponseEntity<String> response = webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("serviceKey", API_KEY)
.queryParam("pageNo", "1")
.queryParam("numOfRows", "1")
.queryParam("entpName", "")
.queryParam("itemName", "")
.queryParam("itemSeq", "")
.queryParam("efcyQesitm", "")
.queryParam("useMethodQesitm", "")
.queryParam("atpnWarnQesitm", "")
.queryParam("atpnQesitm", "")
.queryParam("intrcQesitm", "")
.queryParam("seQesitm", "")
.queryParam("depositMethodQesitm", "")
.queryParam("openDe", "")
.queryParam("updateDe", "")
.queryParam("type", "json")
.build())
.retrieve()
.toEntity(String.class).block();
return response;
}
}
<논 블록킹 방식>
@RestController
public class DrugOpenAPIController {
private final static String BASE_URL = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList";
private final String API_KEY = "encoding API키";
@GetMapping(value = "/publicData", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> getDrugInfo() {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
WebClient webClient = WebClient.builder()
.uriBuilderFactory(factory)
.baseUrl(BASE_URL)
.build();
Mono<String> response = webClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("serviceKey", API_KEY)
.queryParam("pageNo", "1")
.queryParam("numOfRows", "1")
.queryParam("entpName", "")
.queryParam("itemName", "")
.queryParam("itemSeq", "")
.queryParam("efcyQesitm", "")
.queryParam("useMethodQesitm", "")
.queryParam("atpnWarnQesitm", "")
.queryParam("atpnQesitm", "")
.queryParam("intrcQesitm", "")
.queryParam("seQesitm", "")
.queryParam("depositMethodQesitm", "")
.queryParam("openDe", "")
.queryParam("updateDe", "")
.queryParam("type", "json")
.build())
.retrieve()
.bodyToMono(String.class);
return response;
}
}
첫 번째 방식(ResponseEntity<String>반환)에서
.toEntity(String.class).block(); 구문을 사용하고 있는데, 이는 호출한 스레드가 데이터를 수신할 때까지 대기하게 하며, 데이터가 도착하면 작업을 계속 진행합니다.
이 방식은 쉽고 직관적인 프로그래밍을 가능하게 하지만, I/O 작업이 많거나 네트워크 지연이 발생할 경우 대기 시간이 길어지고, 이로 인해 전체적인 시스템 성능이 저하될 수 있습니다.
두 번째 방식(Mono<String>반환)은
논블로킹 방식으로, 호출한 스레드가 결과를 기다리지 않고 바로 반환합니다.
이 방식은 리액티브 프로그래밍 패러다임을 따르며, 데이터가 준비될 때까지 다른 작업을 계속 수행할 수 있습니다.
이로 인해 리소스를 효율적으로 사용하고, 시스템 성능을 개선할 수 있습니다.
단, 이 방식은 프로그래밍이 복잡해질 수 있으며, 비동기 프로그래밍에 익숙하지 않은 개발자들에게는 이해하기 어려울 수 있습니다.
결론: 블로킹 방식은 쉽고 직관적이지만, 대기 시간이 길어질 수 있으며, 논블로킹 방식은 복잡하지만, 성능 향상과 리소스 효율성을 얻을 수 있다.
주의 사항 (Content-Type 체크)
스프링 프레임워크는 기본적으로 application/json 타입으로 받는 것을 기대하기 때문에
아래와 같이 Content-Type이 application/json 이면 상관 없으나
해당 사진 처럼 간혹 공공포털 api 에서 text/json으로 반환 해주는 경우가 있어
해당 경우에는 추가적으로 다음 로직을 추가하여야 한다.
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(configurer -> configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.valueOf("text/json"))))
.build();
WebClient webClient = WebClient.builder()
.baseUrl(BASE_URL)
.exchangeStrategies(strategies)
.build();
JSON Viwer 확장 플러그인을 사용하여 깔끔하게 표시되는 결과
WebClient란?
WebClient는 Spring 5에서 도입된 비동기적인 방식의 HTTP 프로토콜을 통해 요청을 보낼 때 사용하는 클래스로 기존에 사용되었던 동기적 방식의 RestTemplate를 대체하여 사용되며 블로킹, 논블로킹 I/O 작업을 지원한다.
(위에 코드에서는 block(); 메소드를 통해 블로킹 I/O 작업을 사용하였다.)
<정리가 잘 되어있는 링크>
https://velog.io/@dnrwhddk1/Spring-Web-Client-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%82%AC%EC%9A%A9
'Development > Spring' 카테고리의 다른 글
Spring vs SpringBoot (0) | 2023.07.16 |
---|---|
DTO, POLO 데이터 객체 (0) | 2023.07.01 |
Android, Spring Boot, Mysql 데이터 파이프 라인 구축 실습 (AWS-EC2, RDS) -3(완) JAR 파일 생성 및 실행 (2) | 2023.06.17 |
Android, Spring Boot, Mysql 데이터 파이프 라인 구축 실습 (AWS-EC2, RDS) -2 EC2 접속 및 DB 생성 (0) | 2023.06.17 |
Android, Spring Boot, Mysql 데이터 파이프 라인 구축 실습 (AWS-EC2, RDS) -1 인스턴스 생성 (0) | 2023.06.17 |