본문 바로가기

국비과정/Spring

20230829 _[62일차]_01. 공공데이터 API 활용 & Thymeleaf 시작

9 / 26 일까지 세미완료 및 발표

10 / 15 까지 이력서 & 자소서 준비

 

 

post.jsp 구성을 div 대신 table로 수정해주고 mbno를 잡아서

ajax를 통해 mbno를 get방식으로 보내준다.

resulttype도 String으로 수정해서 mb_content 값만 가져왔다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>admin || post</title>

<script src="../js/jquery-3.7.0.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/xeicon@2.3.3/xeicon.min.css">
<link rel="stylesheet" href="../css/admin.css">
<link rel="stylesheet" href="../css/multiboard.css">

<style type="text/css">
.boardlist {
	margin: 20px auto;
	text-align: center;
}

.gray {
	background-color: gray;
}

form {
	float: right;
}

table {
	border-collapse: collapse;
	margin: 0 auto;
	text-align: center;
	width: 800px;
	height: auto;
}

td, tr {
	padding: 5px;
	margin: 5px;
}

table:hover {
	cursor: pointer;
}

tr th {
	background-color: black;
	color: orange;
	border-radius: 10px;
}
table{
	margin-top: 30px;
}

</style>
<script type="text/javascript">

	$(function() {
		$(".title").click(function() {
			var mbno = $(this).siblings(".mbno").text();
			var mbdetail = $(this).parent().siblings("." + mbno);
			var mb_content = $(this).parent().siblings(".mb_detail").children();
			
			if (mb_content.is(":visible")) {
				
				$.ajax({
					url : "./postDetail",
					type : "get",
					data : {mbno : mbno},
					dataType : "json",
					success : function(data) {
						mb_content.hide();
						},
						error : function(error) {
							alert("에러발생");
							}
						});
				}
			if (!(mb_content.is(":visible"))) {
				
				$.ajax({
					url : "./postDetail",
					type : "get",
					data : {mbno : mbno},
						dataType : "json",
						success : function(data) {
							mbdetail.html('<td colspan="7" class="mb_content">'+ data.postdetail+ '</td>');
						},
						error : function(error) {
							alert("에러발생");
							}
							
					});
				}
			});
		});
	
</script>
</head>
<body>
	<div class="container">
		<%@ include file="menu.jsp" %>
		<div class="main">
			<div class="article">	
			<h1>게시글 관리 ${list[0].count}개의 글이 있음</h1>	
				<div class="boardList">
		            <button onclick="location.href='./post?cate=0'">전체보기</button>
					<c:forEach items="${boardList}" var="b">
		            		<button onclick="location.href='./post?cate=${b.mb_cate}'">${b.b_catename }</button>
	            	</c:forEach>
	            	<form action="./post" method="get">
	            		<select name="searchN">
	            			<option value="title">제목</option>
	            			<option value="content">내용</option>
	            			<option value="nick">작성자</option>
	            			<option value="id">ID</option>
	            		</select>
	            		<input type="text" name="searchV" required="required">
	            		<input type="hidden" name="cate" value="${param.cate}">
	            		<button type="submit">검색</button>
	            	</form>
            	</div>
				<table>
					<tr>
						<th>번호</th>
						<th>카테고리</th>
						<th>제목</th>
						<th>작성자</th>
						<th>날짜</th>
						<th>조회수</th>
						<th>삭제여부</th>
					</tr>
					<c:forEach items="${list}" var="row">
					<tr class="<c:if test="${row.mb_del eq 0}">gray</c:if> mb_detail-container">
						<td class="mbno">${row.mb_no}</td> 
						<td>${row.b_catename}</td> 
						<td class="title">${row.mb_title}</td>
						<td>${row.m_name}(${row.m_id})</td>
						<td>${row.mb_date}</td>
						<td>${row.mb_read}</td>
						<td>${row.mb_del}</td>
					</tr>
					<tr class="${row.mb_no } mb_detail">
					</tr>
					</c:forEach>
				</table>
			</div>
		</div>
	</div>
</body>

</html>

 

글제목을 클릭했을 때 하단에 글내용을 포함한 tr 태그가 나타나도록 설정해준거다.

게시글 아래에 tr태그를 만들어주고 클래스 이름으로 db에서 뽑아낸 게시글번호mb_detail 두개룰 지정해준다.

각각 제이쿼리 내부에서 변수로 지정해준다.

mbno : 게시글번호

mbdetail : 게시글 하단의 tr태그

mb_content : tr태그의 자식요소로 들어가게 될 td태그 (mb_content를 포함) 

 

ajax 로직을 조건문을 이용해 두개 만들어주고

mb_content (td태그)가 visible이면 다시 hide 해주고 

! visible 이면 mb_content가 포함된 mb_content (td태그)를 tr태그의 html()을 사용해 내부 요소로 추가해준다. 

 

컨트롤러에서는 mbno를 매개변수로 postDetail메소드를 실행시켜서 

multiview에서 mb_content를 가져온다. 

mb_content 하나만 가져올거라 resultType은 Map --> String으로 수정해줬다. 

 

글제목을 클릭하면 아래쪽에 글내용이 뜬다.


나름대로 시도했었지만 실패한 코드도 올려본다

$(function() {
	
	 let activeDiv = null;
	
	$(".title").click(function() {
		let mbno = $(this).siblings(".mbno").text();
 		let parent = $(this).parent();
		//alert(mbno);
		
		let div = $("<div class='div-row'></div>");
		parent.after(div);
		parent.next(".div-row").addClass("display-none");
		
		if (activeDiv != null) {
			activeDiv.remove();
			activeDiv.removeClass("newClass");
    	}
		
		activeDiv = div;
		
         $.ajax({
        	 url: "./postDetail",
        	 type: "post",
        	 data: {mbno: mbno},
        	 dataType: "json",
        	 success:function(data){
        		//alert(data.postdetail.mb_content);
	        	div.html(data.postdetail.mb_content);
	        	div.addClass("newClass");
	        	$(".newClass").show("slow");
        	 },
        	 error:function(data){
        		 alert("오류가 발생했습니다. 다시 시도하지 마십시오 : " + data);
        	 }
         });
         
	});
});

admin - menu.jsp에서 메뉴바 수정

공기질관리(air.jsp) 페이지와 코로나현황(corona.jsp) 메뉴 넣어주고 각각 이동할 jsp페이지 주소 설정해준다.

AdminController에서 각 요청에 대한 페이지 반환 로직 만들어준다.

저번에 신청했었던 API 불러올건가보다.


https://www.data.go.kr/

코로나신청에 들어가서 활용가이드 보면 응답메시지의 데이터가 제이슨 형식이다.

제이슨 뷰어로 확인해보자

Online JSON Viewer (stack.hu)

 

Online JSON Viewer

 

jsonviewer.stack.hu

Text 탭에 코드 붙여넣고 

Viewer 탭에서 확인해보면 아래처럼 데이터를 정리해서 보여준다.

테스트 목적인 경우에는 무료로 사용가능하지만

실제 사용시에는 정보들을 유료로 사용한다. 무료여도 하루에 100번까지만 요청 가능하단다.

 

상세보기에 들어가면 언어별 샘플코드가 준비되어있다.

코드 복사해서 가져온다.

import 해주고 밑줄뜨는건 다 throw로 예외처리 해준다.

마이페이지에 있는 인증키를 아래 "서비스키" 부분에 넣어준다.

서비스키를 UTF-8 로 변경해서 serviceKey라는 이름으로 urlBuilder에 추가해주는거다. 

메뉴바에서 코로나 메뉴를 클릭하면 admin/corona 주소로 잘 이동하고

콘솔창에 응답이 뜬다.

Response code 는 200으로 뜨고, 

response라는 객체 내부에 result라는 배열이 있고 그 내부에 key-value형태의 데이터들이 들어있다.

Response code: 200
{"response":{"result":[{"rate_confirmations":"25.57","cnt_confirmations":"13154","cnt_severe_symptoms":"228","rate_deaths":"0.02","cnt_hospitalizations":"0","rate_severe_symptoms":"0.44","rate_hospitalizations":"0.00","cnt_deaths":"12","mmddhh":"8.28.00시"}],"resultCnt":"1","resultCode":"1","resultMsg":"조회성공"}}

이제 코드를 살펴보자

 

콘솔창에 떴던 대로 Response code 가 200이면 아래 if조건문에 따라 진행된다.

 

콘솔창에 뜬 코드를 json viewr에 넣어보면

오늘자 실제 코로나 현황이 정리되어 나온다.

이걸 Model에 담아서 보낼예정

결과값을 스트링타입으로, corona라는 이름으로 model에 담아서 jsp로 보낸다. 

corona.jsp 생성해서 corona 이름으로 출력해보면 코로나 일일 내역이 잘 뜬다.

 

아래처럼 출력해보면

corona.response 는 json형태의 객체이기 때문에 아래처럼 뜬다.

result배열안의 지정된 index의 value값을 꺼내오면 아래처럼 잘 가져온다.

변수명 맞춰서 jsp에 값들을 테이블 형식으로 뽑아내본다.

다시 정리해주고 클래스명 넣어준 후에

JS문법을 이용해서 출력을 해보자

.querySelector 를 사용해서 해당 클래스에 innerText로

${corona}에서 지정된 index의 value값을 뽑아내 넣어준다.

그러면 이렇게 뜬다.

 


다른방법으로 출력해보기 위해 컨트롤러로 돌아간다.

 

model에 넣어준 corona값을 콘솔창에 출력해보려면 아래처럼 배열의 형태로는 불가능하다.

model에 추가해준 데이터인 corona는 JSON 형식의 문자열 데이터다.

이걸 String => Json => Map 순서로 변환해서 값을 뽑아낼거다.

 

[ String to Json ]

ObjectMapper : Jackson 라이브러리의 핵심 클래스

readTree : JSON 형식의 문자열을 읽어와서 Jackson 라이브러리의 JsonNode 객체로 변환하는 ObjectMapper 의 내부메서드

 

 

JsonNode 객체로 넣어준 jsonN에서 배열 내부의 값을 뽑아내 출력해보면

아래처럼 잘 출력이 된다.

Response code: 200
{"rate_confirmations":"25.57","cnt_confirmations":"13154","cnt_severe_symptoms":"228","rate_deaths":"0.02","cnt_hospitalizations":"0","rate_severe_symptoms":"0.44","rate_hospitalizations":"0.00","cnt_deaths":"12","mmddhh":"8.28.00시"}

 

 

[ Json to Map]

 

* readValue(content, valueType) 메소드를 실행해 objectMapper 객체를 Map으로 변환한다.

   ObjectMapper 의 내부메서드

    * content: JSON 데이터의 문자열 표현을 전달

    * valueType: 변환하려는 Java 객체의 클래스 타입을 지정

 

 

* TypeReference : JSON 데이터를 Java 객체로 변환할 때 사용하는 Jackson 라이브러리의 클래스

                                        역직렬화(deserialize)

 

 TypeReference <Map <String, Object> >  

=> Map 타입으로 JSON 데이터를 역직렬화하겠다 (제네릭 타입 정보를 유지한 채로 역직렬화)

 

 

최종적으로 Map타입으로 변환된 값을 result라는 변수에 담아 model에 붙여 jsp로 보낸다.

그럼 jsp에서도 값을 result에서 뽑아내야 한다.

아래처럼 뜬다.


 

아래에서 대기오염통계 현황 코드도 가져와서 뽑아내보자

 

이것도 코드 붙여넣어주는데 코로나 현황보다 넣어줘야 하는 부분이 많다.

아래부분은 축약해서

returnType = xml 로 짧게 써준다.

 

조회시작일자, 조회종료일자도 넣어준다.

최종적으로 admin/air 페이지로 리턴되도록 해주고

 admin 페이지에서 공기질관리 메뉴 클릭해보면

콘솔창에 xml형태로 값이 뜬다.

Response code: 200
<?xml version="1.0" encoding="UTF-8"?><response>  <header>    <resultCode>00</resultCode>    <resultMsg>NORMAL_CODE</resultMsg>  </header>  <body>    <items>      <item>        <msurDt>2023-08-01</msurDt>        <so2Value>.0028</so2Value>        <coValue>.31</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>30</pm10Value>        <no2Value>.0182</no2Value>        <o3Value>.0413</o3Value>        <pm25Value>24</pm25Value>      </item>      <item>        <msurDt>2023-08-02</msurDt>        <so2Value>.0031</so2Value>        <coValue>.33</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>35</pm10Value>        <no2Value>.0208</no2Value>        <o3Value>.0351</o3Value>        <pm25Value>28</pm25Value>      </item>      <item>        <msurDt>2023-08-03</msurDt>        <so2Value>.0033</so2Value>        <coValue>.27</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>23</pm10Value>        <no2Value>.0189</no2Value>        <o3Value>.0279</o3Value>        <pm25Value>17</pm25Value>      </item>      <item>        <msurDt>2023-08-04</msurDt>        <so2Value>.0034</so2Value>        <coValue>.26</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>22</pm10Value>        <no2Value>.0177</no2Value>        <o3Value>.0284</o3Value>        <pm25Value>15</pm25Value>      </item>      <item>        <msurDt>2023-08-05</msurDt>        <so2Value>.0033</so2Value>        <coValue>.23</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>22</pm10Value>        <no2Value>.0115</no2Value>        <o3Value>.0319</o3Value>        <pm25Value>15</pm25Value>      </item>      <item>        <msurDt>2023-08-06</msurDt>        <so2Value>.0027</so2Value>        <coValue>.25</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>23</pm10Value>        <no2Value>.0109</no2Value>        <o3Value>.0382</o3Value>        <pm25Value>17</pm25Value>      </item>      <item>        <msurDt>2023-08-07</msurDt>        <so2Value>.0027</so2Value>        <coValue>.22</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>11</pm10Value>        <no2Value>.0104</no2Value>        <o3Value>.0228</o3Value>        <pm25Value>7</pm25Value>      </item>      <item>        <msurDt>2023-08-08</msurDt>        <so2Value>.0033</so2Value>        <coValue>.22</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>13</pm10Value>        <no2Value>.0115</no2Value>        <o3Value>.0207</o3Value>        <pm25Value>6</pm25Value>      </item>      <item>        <msurDt>2023-08-09</msurDt>        <so2Value>.0028</so2Value>        <coValue>.21</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>8</pm10Value>        <no2Value>.0089</no2Value>        <o3Value>.018</o3Value>        <pm25Value>3</pm25Value>      </item>      <item>        <msurDt>2023-08-10</msurDt>        <so2Value>.0028</so2Value>        <coValue>.22</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>4</pm10Value>        <no2Value>.01</no2Value>        <o3Value>.032</o3Value>        <pm25Value>2</pm25Value>      </item>      <item>        <msurDt>2023-08-11</msurDt>        <so2Value>.003</so2Value>        <coValue>.37</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>20</pm10Value>        <no2Value>.0138</no2Value>        <o3Value>.0322</o3Value>        <pm25Value>12</pm25Value>      </item>      <item>        <msurDt>2023-08-12</msurDt>        <so2Value>.0028</so2Value>        <coValue>.33</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>19</pm10Value>        <no2Value>.0155</no2Value>        <o3Value>.0294</o3Value>        <pm25Value>12</pm25Value>      </item>      <item>        <msurDt>2023-08-13</msurDt>        <so2Value>.0029</so2Value>        <coValue>.39</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>30</pm10Value>        <no2Value>.013</no2Value>        <o3Value>.0389</o3Value>        <pm25Value>20</pm25Value>      </item>      <item>        <msurDt>2023-08-14</msurDt>        <so2Value>.003</so2Value>        <coValue>.44</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>42</pm10Value>        <no2Value>.0186</no2Value>        <o3Value>.0378</o3Value>        <pm25Value>30</pm25Value>      </item>      <item>        <msurDt>2023-08-15</msurDt>        <so2Value>.0029</so2Value>        <coValue>.3</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>26</pm10Value>        <no2Value>.0107</no2Value>        <o3Value>.0373</o3Value>        <pm25Value>17</pm25Value>      </item>      <item>        <msurDt>2023-08-16</msurDt>        <so2Value>.0029</so2Value>        <coValue>.3</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>18</pm10Value>        <no2Value>.015</no2Value>        <o3Value>.0345</o3Value>        <pm25Value>11</pm25Value>      </item>      <item>        <msurDt>2023-08-17</msurDt>        <so2Value>.0033</so2Value>        <coValue>.46</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>29</pm10Value>        <no2Value>.0205</no2Value>        <o3Value>.0451</o3Value>        <pm25Value>20</pm25Value>      </item>      <item>        <msurDt>2023-08-18</msurDt>        <so2Value>.0035</so2Value>        <coValue>.46</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>38</pm10Value>        <no2Value>.0225</no2Value>        <o3Value>.0552</o3Value>        <pm25Value>28</pm25Value>      </item>      <item>        <msurDt>2023-08-19</msurDt>        <so2Value>.0039</so2Value>        <coValue>.46</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>30</pm10Value>        <no2Value>.0202</no2Value>        <o3Value>.0481</o3Value>        <pm25Value>22</pm25Value>      </item>      <item>        <msurDt>2023-08-20</msurDt>        <so2Value>.0034</so2Value>        <coValue>.39</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>27</pm10Value>        <no2Value>.012</no2Value>        <o3Value>.0476</o3Value>        <pm25Value>20</pm25Value>      </item>      <item>        <msurDt>2023-08-21</msurDt>        <so2Value>.0033</so2Value>        <coValue>.34</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>26</pm10Value>        <no2Value>.0155</no2Value>        <o3Value>.0334</o3Value>        <pm25Value>19</pm25Value>      </item>      <item>        <msurDt>2023-08-22</msurDt>        <so2Value>.0033</so2Value>        <coValue>.27</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>20</pm10Value>        <no2Value>.0151</no2Value>        <o3Value>.0218</o3Value>        <pm25Value>15</pm25Value>      </item>      <item>        <msurDt>2023-08-23</msurDt>        <so2Value>.003</so2Value>        <coValue>.31</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>10</pm10Value>        <no2Value>.0169</no2Value>        <o3Value>.0291</o3Value>        <pm25Value>6</pm25Value>      </item>      <item>        <msurDt>2023-08-24</msurDt>        <so2Value>.0029</so2Value>        <coValue>.28</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>7</pm10Value>        <no2Value>.0124</no2Value>        <o3Value>.0351</o3Value>        <pm25Value>3</pm25Value>      </item>      <item>        <msurDt>2023-08-25</msurDt>        <so2Value>.0031</so2Value>        <coValue>.37</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>19</pm10Value>        <no2Value>.0148</no2Value>        <o3Value>.0399</o3Value>        <pm25Value>10</pm25Value>      </item>      <item>        <msurDt>2023-08-26</msurDt>        <so2Value>.0032</so2Value>        <coValue>.52</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>39</pm10Value>        <no2Value>.0137</no2Value>        <o3Value>.0693</o3Value>        <pm25Value>27</pm25Value>      </item>      <item>        <msurDt>2023-08-27</msurDt>        <so2Value>.003</so2Value>        <coValue>.45</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>32</pm10Value>        <no2Value>.0125</no2Value>        <o3Value>.0372</o3Value>        <pm25Value>21</pm25Value>      </item>      <item>        <msurDt>2023-08-28</msurDt>        <so2Value>.0027</so2Value>        <coValue>.31</coValue>        <msrstnName>강남구</msrstnName>        <pm10Value>15</pm10Value>        <no2Value>.0145</no2Value>        <o3Value>.0238</o3Value>        <pm25Value>9</pm25Value>      </item>    </items>    <numOfRows>100</numOfRows>    <pageNo>1</pageNo>    <totalCount>28</totalCount>  </body></response>

 

이건 xml 뷰어로 확인해보자. 코드복사해서 아래 사이트에 돌려보면

 

https://codebeautify.org/xmlviewer

 

Best Online XML Viewer, XML Formatter, XML Editor, Analyser, Beautify-Beautifier, Minify, Tree structure

Free XML Viewer - XML Editor - XML Formatter : Convert XML Strings or File to a Friendly Readable Format, Beautify-Beautifier, Minify, XML tree view

codebeautify.org

 

아래처럼 뜬다.

 

리턴타입만 json으로 바꿔보면 

콘솔창에 json 형식으로 출력된다.

Response code: 200
{"response":{"body":{"totalCount":28,"items":[{"msurDt":"2023-08-01","so2Value":".0028","coValue":".31","msrstnName":"강남구","pm10Value":"30","no2Value":".0182","o3Value":".0413","pm25Value":"24"},{"msurDt":"2023-08-02","so2Value":".0031","coValue":".33","msrstnName":"강남구","pm10Value":"35","no2Value":".0208","o3Value":".0351","pm25Value":"28"},{"msurDt":"2023-08-03","so2Value":".0033","coValue":".27","msrstnName":"강남구","pm10Value":"23","no2Value":".0189","o3Value":".0279","pm25Value":"17"},{"msurDt":"2023-08-04","so2Value":".0034","coValue":".26","msrstnName":"강남구","pm10Value":"22","no2Value":".0177","o3Value":".0284","pm25Value":"15"},{"msurDt":"2023-08-05","so2Value":".0033","coValue":".23","msrstnName":"강남구","pm10Value":"22","no2Value":".0115","o3Value":".0319","pm25Value":"15"},{"msurDt":"2023-08-06","so2Value":".0027","coValue":".25","msrstnName":"강남구","pm10Value":"23","no2Value":".0109","o3Value":".0382","pm25Value":"17"},{"msurDt":"2023-08-07","so2Value":".0027","coValue":".22","msrstnName":"강남구","pm10Value":"11","no2Value":".0104","o3Value":".0228","pm25Value":"7"},{"msurDt":"2023-08-08","so2Value":".0033","coValue":".22","msrstnName":"강남구","pm10Value":"13","no2Value":".0115","o3Value":".0207","pm25Value":"6"},{"msurDt":"2023-08-09","so2Value":".0028","coValue":".21","msrstnName":"강남구","pm10Value":"8","no2Value":".0089","o3Value":".018","pm25Value":"3"},{"msurDt":"2023-08-10","so2Value":".0028","coValue":".22","msrstnName":"강남구","pm10Value":"4","no2Value":".01","o3Value":".032","pm25Value":"2"},{"msurDt":"2023-08-11","so2Value":".003","coValue":".37","msrstnName":"강남구","pm10Value":"20","no2Value":".0138","o3Value":".0322","pm25Value":"12"},{"msurDt":"2023-08-12","so2Value":".0028","coValue":".33","msrstnName":"강남구","pm10Value":"19","no2Value":".0155","o3Value":".0294","pm25Value":"12"},{"msurDt":"2023-08-13","so2Value":".0029","coValue":".39","msrstnName":"강남구","pm10Value":"30","no2Value":".013","o3Value":".0389","pm25Value":"20"},{"msurDt":"2023-08-14","so2Value":".003","coValue":".44","msrstnName":"강남구","pm10Value":"42","no2Value":".0186","o3Value":".0378","pm25Value":"30"},{"msurDt":"2023-08-15","so2Value":".0029","coValue":".3","msrstnName":"강남구","pm10Value":"26","no2Value":".0107","o3Value":".0373","pm25Value":"17"},{"msurDt":"2023-08-16","so2Value":".0029","coValue":".3","msrstnName":"강남구","pm10Value":"18","no2Value":".015","o3Value":".0345","pm25Value":"11"},{"msurDt":"2023-08-17","so2Value":".0033","coValue":".46","msrstnName":"강남구","pm10Value":"29","no2Value":".0205","o3Value":".0451","pm25Value":"20"},{"msurDt":"2023-08-18","so2Value":".0035","coValue":".46","msrstnName":"강남구","pm10Value":"38","no2Value":".0225","o3Value":".0552","pm25Value":"28"},{"msurDt":"2023-08-19","so2Value":".0039","coValue":".46","msrstnName":"강남구","pm10Value":"30","no2Value":".0202","o3Value":".0481","pm25Value":"22"},{"msurDt":"2023-08-20","so2Value":".0034","coValue":".39","msrstnName":"강남구","pm10Value":"27","no2Value":".012","o3Value":".0476","pm25Value":"20"},{"msurDt":"2023-08-21","so2Value":".0033","coValue":".34","msrstnName":"강남구","pm10Value":"26","no2Value":".0155","o3Value":".0334","pm25Value":"19"},{"msurDt":"2023-08-22","so2Value":".0033","coValue":".27","msrstnName":"강남구","pm10Value":"20","no2Value":".0151","o3Value":".0218","pm25Value":"15"},{"msurDt":"2023-08-23","so2Value":".003","coValue":".31","msrstnName":"강남구","pm10Value":"10","no2Value":".0169","o3Value":".0291","pm25Value":"6"},{"msurDt":"2023-08-24","so2Value":".0029","coValue":".28","msrstnName":"강남구","pm10Value":"7","no2Value":".0124","o3Value":".0351","pm25Value":"3"},{"msurDt":"2023-08-25","so2Value":".0031","coValue":".37","msrstnName":"강남구","pm10Value":"19","no2Value":".0148","o3Value":".0399","pm25Value":"10"},{"msurDt":"2023-08-26","so2Value":".0032","coValue":".52","msrstnName":"강남구","pm10Value":"39","no2Value":".0137","o3Value":".0693","pm25Value":"27"},{"msurDt":"2023-08-27","so2Value":".003","coValue":".45","msrstnName":"강남구","pm10Value":"32","no2Value":".0125","o3Value":".0372","pm25Value":"21"},{"msurDt":"2023-08-28","so2Value":".0027","coValue":".31","msrstnName":"강남구","pm10Value":"15","no2Value":".0145","o3Value":".0238","pm25Value":"9"}],"pageNo":1,"numOfRows":100},"header":{"resultMsg":"NORMAL_CODE","resultCode":"00"}}}

json viewer로 확인가능

viewer에서 아래의 정리된 형식으로 확인가능

 

컨트롤러 정리를 해준다. 축약형태로 정리

 

그리고 returnType을 xml로 변환해준다.

 

이제 결과값 String을 xml로 변환해볼거다.

DocumentBuilderFactory 사용, 객체 만들어주고 throw 로 예외처리 해준다.

문서형식으로 변환

throw로 던져주는데

예외 너무 길면 그냥 아래처럼 하나로 처리해준다.

여기서 스탑.....쌤이 코드를 다시 열심히 만들어서 주셨다.


주신 코드로 다시 컨트롤러 수정해주고 아래의 설정된 경로에 air.xml파일을 넣어준다.

 

컨트롤러 /air 요청에 대한 로직 수정완료

[ air.jsp ]

<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>admin || main</title>
<link rel="stylesheet"
   href="//cdn.jsdelivr.net/npm/xeicon@2.3.3/xeicon.min.css">
<link rel="stylesheet" href="../css/admin.css">
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
   google.charts.load('current', {
      'packages' : [ 'line' ]
   });
   google.charts.setOnLoadCallback(drawChart);

   function drawChart() {

      var data = new google.visualization.DataTable();
      data.addColumn('date', '날짜');
      data.addColumn('number', '미세먼지');
      data.addColumn('number', '초미세먼지');

      data.addRows([
         <c:forEach items="${list }" var="a">
            [ new Date('${a.msurDt}'), ${a.pm10Value}, ${a.pm25Value} ],
         </c:forEach>
            ]);

      var options = {
         chart : {
            title : '강남구 대기질',
            subtitle : '2023년'
         },
         width : 1000,
         height : 500
      };

      var chart = new google.charts.Line(document.getElementById('linechart_material'));

      chart.draw(data, google.charts.Line.convertOptions(options));
   }
</script>
</head>
<body>
   <div class="container">
      <%@ include file="menu.jsp"%>
      <div class="main">
         <div class="article">
            <h1>강남구 공기질</h1>
            <div id="linechart_material"></div>
            <table border="1">
               <tr>
                  <td>날짜</td>
                  <td>이산화질소</td>
                  <td>미세먼지</td>
                  <td>초미세먼지</td>
                  <td>일산화탄소</td>
                  <td>아황산 가스</td>
               </tr>
               <c:forEach items="${list }" var="a">
                  <tr>
                     <td>${a.msurDt }</td>
                     <td>${a.no2Value}</td>
                     <td>${a.pm10Value}</td>
                     <td>${a.pm25Value}</td>
                     <td>${a.coValue}</td>
                     <td>${a.so2Value}</td>
                  </tr>
               </c:forEach>
            </table>


         </div>
      </div>
   </div>
</body>
</html>

그러면 아래처럼 띄울 수 있다.

 

jsp 코드 살펴보면 

google에서 line 형태의 차트를 가져온거다.

데이터가 여러개여서 이 차트로 해줬지만 데이터가 하나라면 다른 차트 이용할 수 있다.


https://developers.google.com/chart?hl=ko 

 

Charts  |  Google for Developers

브라우저 및 휴대기기용 양방향 차트

developers.google.com


날짜, 미세먼지, 초미세먼지 를 foreach로 뽑아내면 아래처럼 출력된다 (페이지 소스보기에서 확인가능)


스크롤바 만들어보자

 

일단 페이지 하단에 br을 넣어서 빈공간 만들어주고

 

div로 스크롤바 공간(progress-container)과 스크롤바(progress-bar)를 만들어준다.

아래처럼 스크롤바 공간(progress-container)이 노란색으로 생겼다.

그런데 차트가 메뉴바를 가린다.

 

메뉴바 스타일이 적용되어 있는 admin.css에서

menu의 positionfixed로 수정해주고 (다른 요소에 영향받지 않고 항상 고정되어 있도록) 

z-index를 9로 설정해준다.

 

air.jsp에서는 z-index를 10으로 설정해줬기 때문에 스크롤바 공간(progress-container)은 메뉴바 요소 위에 보여진다.

 

아래처럼 메뉴바가 제대로 뜨고 그 위에 스크롤바 공간(progress-container) 도 잘 뜬다.

 

* z-index 

요소의 쌓임 순서(레이어 순서)를 지정, 요소들이 겹쳐져 있을 때 어떤 요소가 다른 요소 위에 올지 결정.

숫자가 높을수록 가장 위에 표시된다. (음수값도 사용 가능)

 

 

스크롤을 내리면 위에 만들어준 스크롤바도 움직인다.

배경색 yellow 빼주면

초록색 스크롤바만 남는다.

 


Thymeleaf 

vs-code 에서도 작성가능, 웹브라우저에서 단독으로 실행가능. 

서버에서 동적으로 HTML을 생성할 수 있으며, Spring Framework 의 여러가지 기능들과 잘 통합된다.

 

새로운 프로젝트 생성하자.

thymeleaf 프로젝트 생성하고 com.phyho.web 패키지 생성

필요한것들 추가추가

짜잔

새로운 컨트롤러 패키지 및 클래스 생성

컨트롤러 선언해주고 슬러시 요청에 대해 index.html 반환

index.html 생성

 

실행해보면 잘된다.

thymeleaf 사용을 위한 선언을 해준다.

 

jstl과 마찬가지로 thymeleaf 가 먼저 랜더링 되고 이후에 나머지 html들이 로딩된다.

 

이제 Model을 사용해서 text를 보내본다.

아래방식으로는 출력이 안된다. (jsp문법)

 

thymeleaf 문법은 th로 시작해야 한다. 

<span th:text="${txt}">출력됩니다.</span>

 

html의 '출력됩니다.' 를 서버의 '서버에서 보낸 메시지 입니다' 가 덮어써버린다.

 

아래 thymeleaf  기본태그들은 외워야한다.

문자열은 그대로 출력된다.

 

컨트롤러에서 이렇게 설정해주고

문자열 출력태그(utext) 사용하는데 태그가 포함되어있으면 태그까지 같이 출력된다.

컨트롤러에서 div 태그와 함께 스타일지정도 해주면

태그가 출력되면서 지정해준 스타일도 적용이 된다. 신기하다.

 

위에서부터 순서대로

 

[[태그까지 출력]]

text = 태그제외하고 문자열 출력

utext= 태그까지 출력

[(태그제외하고 출력)]


링크는 a태그를 이용해서 @{경로} 형태로 써준다.

 

 

소스코드 보기에서 보면 서버쪽에서 이미 랜더링 완료된 이후에 넘어오게 되므로

text 에 서버에서 불러온 데이터를 확인할 수 있다.

 

 

bno를 가진 주소를 넣는 형태는 아래 두종류다.

thymeleaf 에서는 아래부분 코드형태로 사용할 예정

 

두 코드 모두 주소창에는 아래의 형태로 보여진다.

주소창에 bno와 user를 같이 잡도록 해주면

 

 

bno값을 컨트롤러에서 255로 지정해주고

클릭했을 때 이 bno값을 잘 잡아오는지 보자.

잘 잡아온다.

 


변수선언  => th:with="사용할 변수= 값"

아래처럼 변수와 값, 출력 태그를 따로 적어주면 값을 못잡아온다.

 

하나의 span 태그 내부에서 변수/값 설정호출을 함께 해줘야 name에 대한 값(홍길동)을 잘 뽑아낸다.


비교연산자 _ boolean으로 반환

 

 


if 조건문


Spring boot - Thymeleaf 기본 문법 (tistory.com)

 

Spring boot - Thymeleaf 기본 문법

1. Thymeleaf 기본 표현 자세한 문법은 Thymeleaf 사이트의 튜토리얼을 참고하는게 좋습니다. 기본 표현, 조건문, 반복문 세가지를 중점으로 보겠습니다. Thymeleaf Document 참고 (https://www.thymeleaf.org/doc/tuto

eblo.tistory.com


컨트롤러에서 List값을 만들어서 html 에서 출력해볼거다.

잘 출력되는지 확인

 

반복문(forEach)

 

ul 태그와 li로 출력해본다.

 

 

$ 달러표시 대신에 * 로 찍어봐도 출력값은 똑같다. 


String / StringBuilder / StringBuffer 차이점

 

String

=> 불변(immutable) _할당된 공간이 변하지 않는 특성

		String str = new String();	// 객체생성
		
		str = "hello";
		System.out.println(str);	// hello
		
		str = str + " world";
		System.out.println(str);	// hello world

String 객체 생성 후 str은 "hello"라는 값의 주소를 참조하게 된다.

이후 문자열을 수정하게 되면 첫번째 str과 다른 새로운 인스턴스가 생성된다. 두번째 str은 "hello world"라는 값을 가진 새로운 주소를 참조하게 되는거다.

즉, 한 번 할당된 공간은 변하지 않아 기존의 "hello"값을 가지고 있던 메모리영역이 남아있게 되고, 추후 garbage collection 에 의해 정리된다.

 

따라서 문자열의 수정, 삭제가 빈번한 경우 성능이 떨어질 수 있다.

 

 

 

StringBuilder / StringBuffer 

=> 가변(mutable) _ 동일 객체내에서 문자 변경 가능

 

		StringBuffer sb = new StringBuffer("hello");	
		System.out.println(sb);	// hello
		
		sb.append(" world");
		System.out.println(sb);	// hello world

String과 다르게 StringBuffer는 한번 생성된 객체 내부에서 문자열 수정이 가능하다.

첫번째 sb와 두번째 sb는 같은 객체다.  .append() .delete() 등의 API를 이용해서 문자열 수정 및 삭제가능.

값 비교시에는 .toString() 을 사용해서 String객체로 변환 후에 .equals() 를 사용해야한다.

 

 

StringBuffer => 동기화지원, 멀티스레드 환경에서 안전 (thread-safe)

StringBuilder => 동기화지원X, 단일스레드 환경에서 성능 향상.