86-1. AJAX로 가져온 값을 화면에 뿌리고 화면에 뿌린 요소 클릭 시 상세페이지로 이동
AJAX처리된 동적요소는 on함수로만 처리할 수 있다.
<div class="innerOuter">
<h4>게시글 TOP 5</h4>
<br>
<a href="list.bo" style="float:right; color:lightgray;">더보기</a>
<br><br>
<table id="boardList" class="table table-hover" align="center">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
<th>첨부파일</th>
</tr>
</thead>
<tbody>
<!-- 조회수가 가장 높은 상위 5개의 게시글을 조회해서 뿌려줄 것! -->
</tbody>
</table>
</div>
<script>
$(function(){
topBoardList();
/*
$('#boardList > tbody > tr').click(function(){
location.href = 'detail.bo?bno=' + $(this).children().eq(0).text();
})
*/
// 동적으로 만들어진 요소에 이벤트 부여 ☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★
$(document).on('click', '#boardList > tbody > tr', function(){
location.href = 'detail.bo?bno=' + $(this).children().eq(0).text();
})
});
function topBoardList(){
$.ajax({
url : 'topList.do',
success : function(result){
console.log(result);
let value = '';
for(let i in result){
value += '<tr>'
+ '<td>' + result[i].boardNo + '</td>'
+ '<td>' + result[i].boardTitle + '</td>'
+ '<td>' + result[i].boardWriter + '</td>'
+ '<td>' + result[i].count + '</td>'
+ '<td>' + result[i].createDate + '</td>'
+ '<td>';
if(result[i].originName != null) {
value += '★';
}
+ '</td></tr>';
}
$('#boardList>tbody').html(value);
},
error : function(){
console.log('실패~');
}
});
};
</script>
86-2. Service에서 다중 트랜잭션 처리 방법
여러 트랜잭션 중 하나라도 오류가 있다면 롤백해주어야 한다.
하지만 Spring Container에서 트랜잭션을 관리하면서 오토커밋이 된다.
=> 하나의 트랜잭션으로 묶어서 관리해주자
=> @Transactional 사용 => 라이브러리 추가 등 절차 필요
1) pom.xml에 라이브러리 추가
<!-- 다중 트랜잭션(spring container가 관리) 처리 => @Transactional 애노테이션 사용 라이브러리 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
2) ServiceImpl 클래스에 애노테이션 추가
@EnableTransactionManagement // 트랜잭션 묶어주는 애노테이션을 쓰기위한 애노테이션
3) 메소드에 애노테이션 추가
하나의 트랜잭션으로 묶어주기
@Transactional 사용
@Override
@Transactional
public int insertMember(Member m) {
// SqlSessionTemplate객체가 자동으로 commit해줌
memberDao.insertMember(sqlSession, m);
return memberDao.insertMember(sqlSession, m);
}
86-3. 공공데이터 활용
그 외
공공기관에서 배포하는 데이터들
1) 파일(excel 등)로 제공하는 형태 => table import하면 끝남 => DB에 더미데이터 넣을 때 해봄 => 노잼
2) Open API 방식 => xml또는 JSON형태로 보내줌
-------------------------------------------------------------------------
★ ★ ★ 삼총사 정리 ★ ★ ★
세개가 섞여서 같이 사용될 순 있어도 각각이 다 다른친구들
1. Library
유용한 함수의 모음 (ex. JQuery, ojdbc)
번거로운 코드 작업을 만들어 놓은 함수(혹은 클래스파일)를 이용하여 편하게 쓰기 위해 씀
함수 == 자바에서는 메소드 => 메소드를 모아놓음 => 클래스파일 => 클래스파일을 압축하여 모아놓음 => jar파일
내가 쓰고 싶을 때 입맛에 맞춰 쓸 수 있음
2. Framework
작업 환경 틀 (ex. spring(라이브러리 집합), bootstrap, alertify, mybatis)
모든 게 다 매뉴얼대로 정해져 있음
3. A.P.I (Application Programming Interface)
(ex. Java)
-------------------------------------------------------------------------
사용방법
↓ ↓ ↓ 정리 맨 하단 ↓ ↓ ↓
데이터 찾기 -> 검색 후 활용신청 (자동승인 / 며칠 걸릴 수도 있음 => 전화하면 해주기도 함)
하루 500번 제한있음 (9시에 리셋됨ㅋㅋ)
인터넷 세상 컴퓨터 == 라우터
End Point == 연결연결한 마지막 컴퓨터
상세보기 누르면 알집파일로 기술문서를 볼 수 있음
+ 샘플코드를 볼 수 있음
[인터페이스 표준]
REST방식
Get => 조회요청(조회)
Post => 추가요청(생성)
Put, Patch => 수정요청(수정)
Delete => 삭제요청(삭제)
[서비스 URL]
요청보낼 URL
[메시지 교환유형]
[요청 메시지 명세]
RequestParameter
1 : 필수 / 0 : 옵션
[응답 메시지 명세]
응답 데이터가 뭘로 돌아오는지
ResponseRapameter
[OpenAPI 에러코드정리]
필수 파라미터를 보내지 않으면 500에러 뜸
파라미터 보낼 때 값이 한글이면 Encodeing => URLEncoder.encode("무엇을", "UTF-8") 사용
인증키(개개인마다 다름)는 상수로 선언해놓고
쿼리스트링을 이용해
Call Back URL + 키밸류 키밸류로 요청 URL만듦 (같은 인증키라도 Call Back URL이 다를 수 있고, 다음에 따라 URL이 작동할 수도 있고 에러가 날 수도 있음 주의)
1) xml타입
public class AirPollutionJavaApp {
public static final String SERVICEKEY = "w0uvtsHfvlqUI0UGxrnsUqVoY%2FaHkN9EaTNBAvI41W2uuXNVZ787YPhrEwBKH0iO7xRlNwMVMM3a4gOgIOqHOg%3D%3D";
public static void main(String[] args) throws IOException {
// OpenAPI서버로 요청
// url만들기!
// Call Back URL
String url = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty";
// url += "?serviceKey=서비스키"; => 30에러코드 => 등록하지 않은 서비스키
url += "?serviceKey=" + SERVICEKEY;
url += "&sidoName=" + URLEncoder.encode("서울", "UTF-8");
System.out.println(url);
// 자바코드로 요청 해야함!!!
// ** HttpURLConnection 객체를 활용해서 OpenAPI요청 해보기~ **
// 1. 요청하고자 하는 url을 전달하면서 java.net.URL객체 생성
URL requestUrl = new URL(url);
// 2. 1번과정으로 생성된 URL객체를 가지고 HttpURLConnection객체 생성
HttpURLConnection urlConnection = (HttpURLConnection)requestUrl.openConnection();
// 3. 요청에 필요한 Header설정
urlConnection.setRequestMethod("GET"); // GET방식으로 요청하겠다~
// 4. 해당 OpenAPI서버로 스트림을 연결해서 입력 데이터 읽어오기
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
// => 한글도 들어있으니 문자스트림인 보조스트림 BuffreredReader를 쓸거임
// => 기반스트림과 보조스트림은 같이 가야함 (Reader면 Reader, InputStream이면 InputStream을 씀)
// => urlConnection은 바이트 스트림인 InputStream을 사용함
// => 그럼 또 보조스트림과 기반스트림을 연결하는 스트림을 하나 더 껴줘야함 => InputStreamReader
// System.out.println(br.readLine()); // 파읽읽기 => 한줄씩
String responseText = "";
String line;
while((line = br.readLine()) != null) {
responseText += line + "\n";
}
System.out.println(responseText);
}
}
=> while문을 이용해서 한줄한줄 읽어서 다 뽑아줌
2) json타입
** JSON, GSON을 쓸 때 라이브러리 추가
url += "&returnType=json"; 를 추가하여 json타입으로 가져오면
객체타입 한줄로 받아옴 => while문 안써도됨
IOException 예외처리해주고
반납까지
public class AirPollutionJavaApp {
public static final String SERVICEKEY = "w0uvtsHfvlqUI0UGxrnsUqVoY%2FaHkN9EaTNBAvI41W2uuXNVZ787YPhrEwBKH0iO7xRlNwMVMM3a4gOgIOqHOg%3D%3D";
public static void main(String[] args) throws IOException {
// OpenAPI서버로 요청
// url만들기!
// Call Back URL
String url = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty";
// url += "?serviceKey=서비스키"; => 30에러코드 => 등록하지 않은 서비스키
url += "?serviceKey=" + SERVICEKEY;
url += "&sidoName=" + URLEncoder.encode("서울", "UTF-8");
url += "&returnType=json";
System.out.println(url);
// 자바코드로 요청 해야함!!!
// ** HttpURLConnection 객체를 활용해서 OpenAPI요청 해보기~ **
// 1. 요청하고자 하는 url을 전달하면서 java.net.URL객체 생성
URL requestUrl = new URL(url);
// 2. 1번과정으로 생성된 URL객체를 가지고 HttpURLConnection객체 생성
HttpURLConnection urlConnection = (HttpURLConnection)requestUrl.openConnection();
// 3. 요청에 필요한 Header설정
urlConnection.setRequestMethod("GET"); // GET방식으로 요청하겠다~
// 4. 해당 OpenAPI서버로 스트림을 연결해서 입력 데이터 읽어오기
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
// => 한글도 들어있으니 문자스트림인 보조스트림 BuffreredReader를 쓸거임
// => 기반스트림과 보조스트림은 같이 가야함 (Reader면 Reader, InputStream이면 InputStream을 씀)
// => urlConnection은 바이트 스트림인 InputStream을 사용함
// => 그럼 또 보조스트림과 기반스트림을 연결하는 스트림을 하나 더 껴줘야함 => InputStreamReader
// System.out.println(br.readLine()); // 파읽읽기 => 한줄씩
String responseText = "";
/*
String line;
while((line = br.readLine()) != null) {
responseText += line + "\n";
}
*/
responseText = br.readLine();
System.out.println(responseText);
/*
{"response":
{"body":
{"totalCount":40,
"items":
[
{"so2Grade":"1","coFlag":null,"khaiValue":"43","so2Value":"0.003","coValue":"0.4","pm10Flag":null,"o3Grade":"1","pm10Value":"21","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.026","stationName":"중구","pm10Grade":"1","o3Value":"0.025"},
{"so2Grade":"1","coFlag":null,"khaiValue":"44","so2Value":"0.003","coValue":"0.5","pm10Flag":null,"o3Grade":"1","pm10Value":"21","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.025","stationName":"한강대로","pm10Grade":"1","o3Value":"0.026"},
{"so2Grade":"1","coFlag":null,"khaiValue":"52","so2Value":"0.003","coValue":"0.4","pm10Flag":null,"o3Grade":"2","pm10Value":"15","khaiGrade":"2","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.016","stationName":"종로구","pm10Grade":"1","o3Value":"0.032"},
{"so2Grade":"1","coFlag":null,"khaiValue":"50","so2Value":"0.004","coValue":"0.6","pm10Flag":null,"o3Grade":"1","pm10Value":"25","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.027","stationName":"청계천로","pm10Grade":"1","o3Value":"0.024"},
{"so2Grade":"1","coFlag":null,"khaiValue":"42","so2Value":"0.003","coValue":"0.5","pm10Flag":null,"o3Grade":"1","pm10Value":"17","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.021","stationName":"종로","pm10Grade":"1","o3Value":"0.025"},
{"so2Grade":null,"coFlag":"통신장애","khaiValue":"-","so2Value":"-","coValue":"-","pm10Flag":"통신장애","o3Grade":null,"pm10Value":"-","khaiGrade":null,"sidoName":"서울","no2Flag":"통신장애","no2Grade":null,"o3Flag":"통신장애","so2Flag":"통신장애","dataTime":"2023-11-08 14:00","coGrade":null,"no2Value":"-","stationName":"용산구","pm10Grade":null,"o3Value":"-"},
{"so2Grade":"1","coFlag":null,"khaiValue":"42","so2Value":"0.002","coValue":"0.4","pm10Flag":null,"o3Grade":"1","pm10Value":"21","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.025","stationName":"광진구","pm10Grade":"1","o3Value":"0.024"},
{"so2Grade":"1","coFlag":null,"khaiValue":"47","so2Value":"0.003","coValue":"0.3","pm10Flag":null,"o3Grade":"1","pm10Value":"28","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.023","stationName":"성동구","pm10Grade":"1","o3Value":"0.022"},{"so2Grade":"1","coFlag":null,"khaiValue":"60","so2Value":"0.003","coValue":"0.6","pm10Flag":null,"o3Grade":"1","pm10Value":"28","khaiGrade":"2","sidoName":"서울","no2Flag":null,"no2Grade":"2","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.036","stationName":"강변북로","pm10Grade":"1","o3Value":"0.015"},
{"so2Grade":"1","coFlag":null,"khaiValue":"44","so2Value":"0.002","coValue":"0.3","pm10Flag":null,"o3Grade":"1","pm10Value":"15","khaiGrade":"1","sidoName":"서울","no2Flag":null,"no2Grade":"1","o3Flag":null,"so2Flag":null,"dataTime":"2023-11-08 14:00","coGrade":"1","no2Value":"0.018","stationName":"중랑구","pm10Grade":"1","o3Value":"0.027"}
],"pageNo":1,"numOfRows":10},"header":{"resultMsg":"NORMAL_CODE","resultCode":"00"}}}
*/
// 라이브러리
// JsonObject, JsonArray 를 이용하여 가공할거임
// JSONObject, JSONArray => JSON라이브러리에서 제공하는 클래스 - 다른거임
// JsonObject, JsonArray, JsonElement => GSON에서 제공
JsonObject totalObj = JsonParser.parseString(responseText).getAsJsonObject();
// parsing하면 JsonElement타입으로 반환됨
// System.out.println(totalObj);
JsonObject responseObj = totalObj.getAsJsonObject("response"); // response속성에 접근 => {} JsonObject
// System.out.println(responseObj);
JsonObject bodyObj = responseObj.getAsJsonObject("body"); // body속성에 접근
// System.out.println(bodyObj);
// bodyObj는 속성이 두개나 있네
// 하나는 키값으로 뽑아보자 => 근데 get으로 뽑으니까 element 타입임 => int형으로 받으려면 getAsInt()까지
int totalCount = bodyObj.get("totalCount").getAsInt();
// System.out.println(totalCount);
JsonArray itemArr = bodyObj.getAsJsonArray("items"); //items속성에 접근 => [] JsonArray
// bodyObj.get("items").getAsJsonArray() 이것도 똑같네
// System.out.println(itemArr);
ArrayList<AirVO> list = new ArrayList();
for(int i = 0; i < itemArr.size(); i++) {
JsonObject item = itemArr.get(i).getAsJsonObject();
System.out.println(item);
AirVO air = new AirVO();
air.setStationName(item.get("stationName").getAsString());
air.setDataTime(item.get("dataTime").getAsString());
air.setSo2Value(item.get("so2Value").getAsString());
air.setPm10Value(item.get("pm10Value").getAsString());
air.setO3Value(item.get("o3Value").getAsString());
air.setKhaiValue(item.get("khaiValue").getAsString());
list.add(air);
}
for(AirVO air : list) {
System.out.println(air);
}
// 다 사용한 객체 반납
br.close();
urlConnection.disconnect();
}
}
JSON으로 해보기~
- JSP
$.ajax({
url : 'air.do',
data : {location : $('#location').val()},
success : function(data){
// console.log(data.response.body.items);
const itemArr = data.response.body.items;
let value = '';
console.log(itemArr);
for(let i in itemArr){
let item = itemArr[i];
value += '<tr>'
+ '<td>' + item.stationName + '</td>'
+ '<td>' + item.dataTime + '</td>'
+ '<td>' + item.so2Value + '</td>'
+ '<td>' + item.pm10Value + '</td>'
+ '<td>' + item.o3Value + '</td>'
+ '<td>' + item.khaiValue + '</td>'
+ '</tr>';
}
$('tbody').html(value);
},
error : function(){
console.log('실패했어~');
}
})
- Controller
@ResponseBody
@RequestMapping(value="air.do", produces="application/json; charset=UTF-8")
public String airPollution(String location) throws IOException {
String url = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty";
url += "?serviceKey=" + SERVICEKEY;
url += "&sidoName=" + URLEncoder.encode(location, "UTF-8");
url += "&returnType=json";
url += "&numOfRows=20";
URL requestUrl = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection)requestUrl.openConnection();
// HttpURLConnection은 URLConnection의 자식클래스 => 다운캐스팅
urlConnection.setRequestMethod("GET");
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String responseText = br.readLine();
br.close();
urlConnection.disconnect();
return responseText;
}
XML로 해보기~
- JSP
$.ajax({
url : 'air.do',
data : {location : $('#location').val()},
success : data => {
// console.log(data);
// jQuery에서의 find메소드 : 기준이되는 요소의 하위요소들 중 특정 요소를 찾을 때 사용(HTML, XML 둥 다 사용 가능)
// console.log(data.find('item'));
// console.log($(data).find('item'));
// XML형식의 응답데이터를 받았을 때
// 1. 응답 데이터 안에 실제 데이터가 담겨있는 요소 선택
const itemArr = $(data).find('item');
// 2. 반복문을 통해 실제 데이터가 담긴 요소들에 접근해서 동적으로 요소 만들기!
let value = '';
itemArr.each((i, item) => { // 1번 매개변수(i)에는 인덱스, 2번 매개변수(item)에는 담겨있는 요소
// console.log(i);
// console.log(item);
// console.log($(item).find('stationName').text());
value += '<tr>'
+ '<td>' + $(item).find('stationName').text() + '</td>'
+ '<td>' + $(item).find('dataTime').text() + '</td>'
+ '<td>' + $(item).find('so2Value').text() + '</td>'
+ '<td>' + $(item).find('pm10Value').text() + '</td>'
+ '<td>' + $(item).find('o3Value').text() + '</td>'
+ '<td>' + $(item).find('khaiValue').text() + '</td>'
+ '</tr>';
});
// 3. 동적으로 만들어진 요소를 화면에 출력
$('tbody').html(value);
},
error : () => {
console.log('응 택도 없어~');
}
})
- Controller
@ResponseBody
@RequestMapping(value="air.do", produces="text/html; charset=UTF-8")
public String airPollution(String location) throws IOException {
String url = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty";
url += "?serviceKey=" + SERVICEKEY;
url += "&sidoName=" + URLEncoder.encode(location, "UTF-8");
url += "&returnType=xml";
url += "&numOfRows=20";
URL requestUrl = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection)requestUrl.openConnection();
urlConnection.setRequestMethod("GET");
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String responseText = br.readLine();
String line;
while((line = br.readLine()) != null){
responseText += line;
}
br.close();
urlConnection.disconnect();
return responseText;
}
☆ ☆ ☆ 정리 ☆ ☆ ☆
0. view단에서 가져온 매개변수가 한글이라면 인코딩하여 URL 쿼리스트링 값으로 넣어줘야 함
URLEncoder.encode(가져온값, "UTF-8")
=> 예외처리(IOException 로 퉁쳐버리기)
1. URL 객체 생성 시 매개변수 인자값
=> url = call back URL + 필수항목 requestParameter(서비스키==암호키, 인코딩, 반환타입 등) 쿼리스트링이용
=> URL requestUrl = new URL(url);
=> 예외처리
2. HttpURLConnection을 생성
=> URL객체의 openConnection() 메소드는 URLConnection 타입이지만
HttpURLConnection은 URLConnection의 자식클래스이므로, 다운캐스팅
=> HttpURLConnection urlConnection = (HttpURLConnection)requestUrl.openConnection();
3. 어떤 방식으로 쓸건지 지정
GET방식 => 조회
=> HttpURLConnection객체의 setRequestMethod("GET")사용
4. URL에 문자열을 입력받아야 하므로, 스트림을 이용
urlConnection객체는 getInputStream() 메소드를 이용하여 바이트 스트림을 가져올 수 있음
하지만 가져올 URL에는 한글이 포함되어 있는 경우가 많으므로 문자스트림을 이용해야 함
=> Reader사용 => 보조스트림 BufferedReader도 사용
=> BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
5. BufferedReader의 readLine()메소드를 이용해 문자열 읽어오기
만약 여기서 xml타입으로 읽어온다면 => 여러줄의 문자열을 한줄씩 읽어옴 => while문 이용
만약 여기서 json타입으로 읽어온다면 => 객체형태의 문자열을 딱 한줄 읽어옴 => readLine()메소드 한번 실행
1) xml타입
produces="text/html; charset=UTF-8"
2) json타입
produces= "application/json; charset=UTF-8"
6. 자원반납(역순)
BufferedReader객체 반납 => close()
HttpURLConnection객체 반납 => disconnection()