공휴일 데이터를 DB에 저장해야 하는 서비스를 구현하게 되었다. 처음에는 사용자에게 엑셀 데이터를 받아, 엑셀을 읽어오는 방법으로 구현할까 했는데.. 어떤 사용자가 매년 이걸 하겠는가. 그리고 매년 휴일의 데이터를 하나하나 찾아 넣기에는 무리가 있다고 판단했다. 다음이나 카카오의 달력 API를 받아올까도 했는데, 그게 더 어려울 것 같았다.
여러 방법을 생각해본 결과 한국천문연구원에서 제공하는 특일 정보 API를 받아오는게 제일 나을 것이라는 판단을 했다. 즉, 공공 API 데이터를 받아, 매년 말에 자동으로 실행해 DB에 저장하는 로직을 구현해볼거다.
사용 언어는 Spring, MyBatis, MariaDB 등 이다.
- 공공 API 발급, XML 파싱
- Spring mvc 형태로 변환
공공 API 발급
먼저 아래의 사이트에서 공공 API의 service key 를 발급받는다. 이건 굉장히 간단하니 설명을 생략한다.. 회원가입 및 로그인 후 활용신청 해주면 된다.
https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15012690
XML 데이터 파싱
한국천문연구원 제공 코드
/* 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/B090041/openapi/service/SpcdeInfoService/getAnniversaryInfo"); /*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("10", "UTF-8")); /*한 페이지 결과 수*/
urlBuilder.append("&" + URLEncoder.encode("solYear","UTF-8") + "=" + URLEncoder.encode("2019", "UTF-8")); /*연*/
urlBuilder.append("&" + URLEncoder.encode("solMonth","UTF-8") + "=" + URLEncoder.encode("02", "UTF-8")); /*월*/
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());
}
}
ParsingTest.java
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.sql.*;
import java.time.LocalDate;
import java.util.Properties;
public class ParsingTest {
public static void main(String[] args) throws Exception {
String confmKey = "발급받은 서비스키 입력";
// 다음해 년도 구하기
LocalDate now = LocalDate.now();
int nextYear = now.getYear() + 1;
String solYear = Integer.toString(nextYear);
System.out.println(solYear);
// 12달 api 실행
for (int i = 1; i < 13; i++){
String solMonth = String.valueOf(i);
while (solMonth.length() < 2){
solMonth = "0" + solMonth;
}
// String solMonth = "01";
System.out.println(solMonth);
// Open Api 경로설정
StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo"); /*URL*/
urlBuilder.append("?" + URLEncoder.encode("serviceKey","UTF-8") + confmKey); /*Service Key*/
urlBuilder.append("&" + URLEncoder.encode("solYear","UTF-8") + "=" + URLEncoder.encode(solYear, "UTF-8")); /*연*/
urlBuilder.append("&" + URLEncoder.encode("solMonth","UTF-8") + "=" + URLEncoder.encode(solMonth, "UTF-8")); /*월*/
// url.openCOnnection() 이용해 Connection 생성
URL url = new URL(urlBuilder.toString());
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestMethod("GET"); // POST 방식 (생략 가능)
httpURLConnection.connect(); // 연결 !
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
try (InputStream input = xmlParsingTest.class.getClassLoader().getResourceAsStream("sfw/config/property/app.properties")) {
// DB 경로 호출 (프로시저 호출)
Properties prop = new Properties();
// load a properties file from class path, inside static method
prop.load(input);
String DB_URL = prop.getProperty("sfw.service.mariadb.url"); // app.properties 설정 연결
String USER = prop.getProperty("sfw.service.mariadb.username");
String PASS = prop.getProperty("sfw.service.mariadb.password");
// url 데이터 추출
DocumentBuilderFactory dbFactoty = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactoty.newDocumentBuilder();
Document doc = dBuilder.parse(url.toString());
doc.getDocumentElement().normalize(); // <xml? ... 첫 줄 삭제
// 파싱할 tag
NodeList nList = doc.getElementsByTagName("item");
for (int temp = 0; temp < nList.getLength(); temp++) {
Node nNode = nList.item(temp);
Element eElement = (Element) nNode;
NodeList nlList1 = eElement.getElementsByTagName("dateKind").item(0).getChildNodes();
NodeList nlList2 = eElement.getElementsByTagName("dateName").item(0).getChildNodes();
NodeList nlList3 = eElement.getElementsByTagName("isHoliday").item(0).getChildNodes();
NodeList nlList4 = eElement.getElementsByTagName("locdate").item(0).getChildNodes();
NodeList nlList5 = eElement.getElementsByTagName("seq").item(0).getChildNodes();
String dateKind = nlList1.item(0).getTextContent(); // 종류
String dateName = nlList2.item(0).getTextContent(); // 명칭
String isHoliday = nlList3.item(0).getTextContent(); // 공공기관 휴일여부
String locdate = nlList4.item(0).getTextContent(); // 날짜
String seq = nlList5.item(0).getTextContent(); // 순번
System.out.println(
"result = " + dateKind + " : " + dateName + " : " + isHoliday + " : " + locdate + " : " + seq);
// 프로시저 호출
String sqlQuery = "HOLIDAY_CHK('" + locdate + "','" + dateName + "','" + isHoliday + "')";
Connection sqlConn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = sqlConn.createStatement();
stmt.execute(sqlQuery);
}
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (DOMException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
// 연결 종료
// httpURLConnection.disconnect();
}
}
프로시저
프로시저 관련 개념은 아래 링크를 참고하면 된다.
https://skylarcoding.tistory.com/191
-- 프로시저 : SELECT 검색 후, 1 이상이면 UPDATE/ 아니면 INSERT
DELIMITER $$
CREATE OR REPLACE
DEFINER = `사용자명`@`%` PROCEDURE HOLIDAY_CHK(
IN locdate varchar(8) -- 날짜, locdate
, IN dateName varchar(50) -- 명칭, dateName
, IN holidayn varchar(1) -- 휴일여부, isholiday
)
BEGIN
DECLARE CHK_HOLIDAY INT;
/* 트랜젝션 시작 */
START TRANSACTION;
-- 기존에 날짜가 이미 DB에 있는지 확인
SELECT COUNT(A.LOC_DT)
INTO CHK_HOLIDAY
FROM holiday A WHERE A.LOC_DT = locdate;
IF CHK_HOLIDAY = 0 THEN
INSERT INTO holiday (
SEQ_NO
, LOC_DT
, DATE_NM
, HOLIDAY_YN
, DATE_KIND
) VALUES (
(SELECT NVL(MAX(A.SEQ_NO),0)+1 FROM holiday A)
, locdate
, dateName
, holidayn
, '01'
)
;
ELSEIF CHK_HOLIDAY > 0 THEN
UPDATE holiday A
SET A.DATE_NM = dateName
, A.HOLIDAY_YN = holidayn
, A.DATE_KIND = '01'
WHERE A.LOC_DT = locdate
;
END IF;
/* 커밋 */
COMMIT;
END$$
DELIMITER ;
다음 포스팅에서는 해당 로직을 Spring MVC 구조로 분할해보겠다.
참고자료
https://velog.io/@garam0410/Java-OPEN-API-%ED%8C%8C%EC%8B%B1%ED%95%98%EA%B8%B0-XML
https://green-joo.tistory.com/9
https://hianna.tistory.com/607