20240313 Spring 21 - API, JSON/XML parsing, JPA

Leafy·2024년 3월 13일
0

중앙_자바

목록 보기
58/76

API


예시 코드에서는 URLEncoder.encode()를 쓴다. ASCII가 아닌 한글같은 게 있을 수도 있으니까 쓰는거

[Java] String이 불변 객체인 이유는 무엇일까?
Java String이 불변(immutable)인 이유
자바에서 문자열 합칠 때 '+' 연산을 쓰지 마세요! (StringBuilder, StringJoiner, String.join, StringBuffer)

@GetMapping("/airKorea")
public String airKorea(Model model) throws IOException {
	
	StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMinuDustFrcstDspth"); // String 쓰면 메모리 왔다갔다 느려질것
	urlBuilder.append("?serviceKey="+util.getServiceKey());
	urlBuilder.append("&returnType=xml");
	urlBuilder.append("&numOfRows=100");
	urlBuilder.append("&pageNo=1");
	urlBuilder.append("&searchDate=2024-03-11");
	urlBuilder.append("&informCode=PM10");
	
	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()); // 접속 결과. 200대가 정상 접속.
    
    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) { // 몇 줄 있을지 모르니까, rd.readLine 없으면 null로 처리할거
    	sb.append(line);
    }
    
    rd.close();
    conn.disconnect();
    System.out.println(sb.toString());
    
    //data.go.kr에서 에어코리아 접속, 데이터 불러오기 2개 한 것.
    
	return "airkorea"; //반드시 url과 같을 필요가 없다. 파일명을 넣는다.
}

여기까지 하면 sb.toString()을 출력했을 때 한 줄로 xml string이 온다.
데이터가 왔고,
이제는 parsing한다.


returnType을 json으로 바꿈

json모양

JSON Beautifier(JSON Viewer)
구조를 보도록 하자

xml은 xml viewer
jsonformatter xml-viewer
xml은 html처럼 순서가 있다.

json과 xml의 문서 형식은 꼭 알아둬야 한다.

나중에 크롬 익스텐션 JSON VIEWER를 쓸 수도 있다

JSON parsing

dependencies에 추가

implementation 'com.googlecode.json-simple:json-simple:1.1.1'

우리는 이미 프로젝트에 jackson이 있어서 JSON을 쓸 수 있지만
제일 쉬워서 이거의 도움을 받을 것.
나중에는 jackson으로 해도 된다.

import org.json.simple.JSONObject;
아니었다
import org.json.simple.parser.JSONParser;
이거 사용

BufferReader 부분은 다 주석처리

// json -> 자바 데이터 타입 형태로 변경 -> 출력
JSONParser parser = new JSONParser();
JSONObject jsonObject = (JSONObject) parser.parse(new InputStreamReader(url.openStream()));
//System.out.println(jsonObject.toString());
//System.out.println(jsonObject.toJSONString());

Map<String, Object> map = (Map<String, Object>) jsonObject.get("response");
System.out.println(map);

이거의 결과는 response 부분이 나오는 것

이런 식으로 포장된 json을 풀어주는 것.

Map<String, Object> map = (Map<String, Object>) jsonObject.get("response"); // 포장된 json을 풀어주는 것
//System.out.println(map);
map = (Map<String, Object>) map.get("body");
System.out.println(map);

얘도 됐다

Map<String, Object> map = (Map<String, Object>) ((HashMap) jsonObject.get("response")).get("body"); // 포장된 json을 풀어주는 것
System.out.println(map);

결과를 보면 items는 array, list 모양
items list는 JSONArray로 받는다

Map<String, Object> map = (Map<String, Object>) jsonObject.get("response"); // 포장된 json을 풀어주는 것
//System.out.println(map);
map = (Map<String, Object>) map.get("body");
System.out.println(map);
JSONArray jsonArray = (JSONArray) map.get("items");
System.err.println(jsonArray);


나온 모양. 빨간색,, (System.err.println()으로 출력해버려서)

json은 순서가 없지만 jsonArray는 배열이기 때문에 유지된다고 한다

xml parsing

좀더 복잡하다

Document는 w3c
import org.w3c.dom.Document;

@GetMapping("/airKoreaXML")
public String airKoreaXML(Model model) throws IOException, ParserConfigurationException, SAXException {
	
	StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMinuDustFrcstDspth"); // String 쓰면 메모리 왔다갔다 느려질것
	urlBuilder.append("?serviceKey="+util.getServiceKey());
	urlBuilder.append("&returnType=xml");
	urlBuilder.append("&numOfRows=100");
	urlBuilder.append("&pageNo=1");
	urlBuilder.append("&searchDate=2024-03-13");
	urlBuilder.append("&informCode=PM10");
	
	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()); // 응답 결과: 200
	
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 자바는 공장 만들어야
	DocumentBuilder documentBuilder = factory.newDocumentBuilder(); // 공장에서 DocumentBuilder 만든 건가... -> 이때 ParserConfigurationException
	Document document = documentBuilder.parse(conn.getInputStream()); // 이때 SAXException
	document.getDocumentElement().normalize(); // document 정규화
	
	System.out.println(document.getDocumentElement().getNodeName()); // node nodeName == DOM(document object model)
	
	return "airkorea"; //반드시 url과 같을 필요가 없다. 파일명을 넣는다.
}

nodeName response는 이건가보다

response의 자식 요소 body -> items로 가려고 한다

  • xml parsing 결론
@GetMapping("/airKoreaXML")
public String airKoreaXML(Model model) throws IOException, ParserConfigurationException, SAXException {
	
	StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMinuDustFrcstDspth"); // String 쓰면 메모리 왔다갔다 느려질것
	urlBuilder.append("?serviceKey="+apiInfo.getServiceKey());
	urlBuilder.append("&returnType=xml");
	urlBuilder.append("&numOfRows=100");
	urlBuilder.append("&pageNo=1");
	urlBuilder.append("&searchDate=2024-03-13");
	urlBuilder.append("&informCode=PM10");
	
	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()); // 응답 결과: 200
	
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 자바는 공장 만들어야
	DocumentBuilder documentBuilder = factory.newDocumentBuilder(); // 공장에서 DocumentBuilder 만든 건가... -> 이때 ParserConfigurationException
	Document document = documentBuilder.parse(conn.getInputStream()); // 이때 SAXException
	document.getDocumentElement().normalize(); // document 정규화
	
	System.out.println("response : " + document.getDocumentElement().getNodeName()); // node nodeName == DOM(document object model)
	
	NodeList list = (NodeList) document.getDocumentElement().getChildNodes().item(3);//body
	System.out.println(list.getLength());
	System.out.println(list.item(1).getNodeName());//items
	
	NodeList list2 = list.item(1).getChildNodes();//items == 13개
	//System.out.println("items :: " + list2.getLength());
	//System.out.println("items :: " + list2.item(0).getNodeName());
	
	for (int i = 0; i < list2.getLength();  i++) {
		NodeList list3 = list2.item(i).getChildNodes();//item == 13개
		
		for (int j = 1; j < list3.getLength(); j++) {
			Node node = list3.item(j);
			if(node.getNodeType() == Node.ELEMENT_NODE) {
				System.out.println(j + " : " + list3.item(j).getNodeName() + " : " + list3.item(j).getTextContent());
			}
		}
	}
	
	return "airkoreaXML"; //반드시 url과 같을 필요가 없다. 파일명을 넣는다.
}

데이터 구조를 보도록...
xml은 이렇게 복잡해서 json이 대부분이다.

map에 넣어서 보내주는 걸로..

@GetMapping("/airKoreaXML")
public String airKoreaXML(Model model) throws IOException, ParserConfigurationException, SAXException {
	
	StringBuilder urlBuilder = new StringBuilder("http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMinuDustFrcstDspth"); // String 쓰면 메모리 왔다갔다 느려질것
	urlBuilder.append("?serviceKey="+apiInfo.getServiceKey());
	urlBuilder.append("&returnType=xml");
	urlBuilder.append("&numOfRows=100");
	urlBuilder.append("&pageNo=1");
	urlBuilder.append("&searchDate=2024-03-13");
	urlBuilder.append("&informCode=PM10");
	
	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()); // 응답 결과: 200
	
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 자바는 공장 만들어야
	DocumentBuilder documentBuilder = factory.newDocumentBuilder(); // 공장에서 DocumentBuilder 만든 건가... -> 이때 ParserConfigurationException
	Document document = documentBuilder.parse(conn.getInputStream()); // 이때 SAXException
	document.getDocumentElement().normalize(); // document 정규화
	
	System.out.println("response : " + document.getDocumentElement().getNodeName()); // node nodeName == DOM(document object model)
	
	NodeList list = (NodeList) document.getDocumentElement().getChildNodes().item(3);//body
	System.out.println(list.getLength());
	System.out.println(list.item(1).getNodeName());//items
	
	NodeList list2 = list.item(1).getChildNodes();//items == 13개
	//System.out.println("items :: " + list2.getLength());
	//System.out.println("items :: " + list2.item(0).getNodeName());

	// List 생성 - map 20개 담아서 보내는 리스트
	List<Map<String, Object>> mapList = new ArrayList<Map<String,Object>>(); // 20개 담아줄것
	for (int i = 1; i < list2.getLength();  i++) { // i = 1로... node는 1로. 0하면 맨앞에 {}가 한 개 생기더라
		NodeList list3 = list2.item(i).getChildNodes();//item == 13개
		
		// Map 생성 - 각각의 데이터를 담아주는 맵
		Map<String, Object> ele = new HashMap<String, Object>();
		for (int j = 1; j < list3.getLength(); j++) {
			Node node = list3.item(j);
			if(node.getNodeType() == Node.ELEMENT_NODE) {
				//System.out.println(j + " : " + list3.item(j).getNodeName() + " : " + list3.item(j).getTextContent());
				//담기 - node이름을 key, 글 내용을 value로
				ele.put(list3.item(j).getNodeName(), list3.item(j).getTextContent());
				//System.out.println("map: " + ele);
			}
		}
		mapList.add(ele);
		//System.out.println(mapList);
	}
	
	model.addAttribute("data", mapList);
	
	
	return "airkoreaXML"; //반드시 url과 같을 필요가 없다. 파일명을 넣는다.
}

jsoup

자바로 만들어진 HTML Parser

dependency 추가

// https://mvnrepository.com/artifact/org.jsoup/jsoup
implementation 'org.jsoup:jsoup:1.17.2'

html 긁어온다
a.menu-list.somoim > .menu_over에서 .menu_over까지 안 들어가면 span태그 .까지 같이 나옴.
.menu_over은 for문 안의 ele.text() 쪽에서 들어가도 된다

@GetMapping("/html")
public String html() throws IOException {
	
	org.jsoup.nodes.Document doc = Jsoup.connect("http://www.naver.com").get(); // 실제 사이트에서 값 가져오게
	System.out.println(doc);
	return "html";
}

예매 사이트도 html 가져올 수 있다?

클리앙 메뉴 가져오기

@GetMapping("/html")
public String html() throws IOException {
	
	org.jsoup.nodes.Document doc = Jsoup.connect("https://www.clien.net/service/").get(); // 실제 사이트에서 값 가져오게
	Elements element = doc.select("a.menu-list.somoim > .menu_over");
	
	System.out.println(element.size()); //몇 개?
	
	for (Element ele : element) {
		System.out.println(ele.text()); // .menu_over가 타이틀입니다.
	}
	
	//System.out.println(doc);
	return "html";
}

jsoup 파이썬할때도 이런 애들 많이 쓴다. parsing할 때.

HTML 스크롤 프로그레스바 만들기

[JS] 스크롤 프로그레스 바 만들기

js가 높이 나누기 계산해서 넣는 것

무한스크롤

[JavaScript] 무한 스크롤 구현하기(Intersection Observer API)


heidiSQL을 켰다

SELECT mm.* 
FROM (SELECT CAST(@ROWNUM:=@ROWNUM+1 AS UNSIGNED) AS rowNum, m.*
			FROM multiboard m, (SELECT @ROWNUM:=0) AS R
			WHERE mtcate=1
			ORDER BY mtno ASC) mm 
ORDER BY mtno DESC 
LIMIT 0, 10

rowNum으로 mtno를 보완

SELECT ff.*, m.mname  
FROM(SELECT @ROWNUM:=@ROWNUM+1 AS rowNum, bb.*, (SELECT COUNT(*) FROM multiboard WHERE mtdel=1) AS COUNT
  FROM (SELECT  b.mtno, b.mttitle, b.mtdate, b.mtdel, b.mno, b.mtcate
  FROM multiboard b) bb, (SELECT @ROWNUM:=0) AS R
  WHERE mtcate=1
  ORDER BY bb.mtno ASC) AS ff JOIN member m ON ff.mno = m.mno 
ORDER BY mtno DESC
LIMIT 0, 10

이것은 또 무엇인지 알 수 없었다.. 확인해보기

ORM

orm은 쿼리문을 짜지 않는다.
orm을 구현한 게 jpa

JPA

자바 최고 난이도
(프로젝트도 db도 새로 만든다고 하셨다)

새 프로젝트 spring starter project


(타임리프는 추가)
Mybatis는 dependency에서 뺀다. 안 씀.

#DB
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://db주소:포트/db명
spring.datasource.username=아이디
spring.datasource.password=비번

#port
server.port=80

application.properties에는 이정도만

데이터베이스 방언(Dialect)

[Spring JPA ] 데이터베이스 방언(Dialect) 이란?

oracle, mariadb 다 Dialect가 설정돼있다.

우리는 application.properties 쓰는데 application.yml도 있을 수 있다,,?
얘도 application.properties에 추가

#jpa 방언 설정
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB103Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

[JPA] 데이터베이스 스키마 자동 생성

create : 실행할 때마다 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
create-drop : create와 같으나 애플리케이션 종료 시점에 테이블 삭제
update : 엔티티 매핑 정보를 비교하여 변경된 내용만 반영
validate : 엔티티와 테이블이 정상적으로 매핑되었는지만 확인
none : 사용하지 않음

엔티티 -> DTO,,

여기서는 update를 썼는데, create로 해두면 매번 새로 생긴다. jpa는 시작할 때마다(?)

살짝 오류가 생겨서
application.properties에서 수정
MariaDB103Dialect -> MariaDB106Dialect

#jpa 방언 설정
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB106Dialect
# update : 엔티티 매핑 정보를 비교하여 변경된 내용만 반영
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

JPABoardService를 만든다(기존꺼랑 구분 위해서 JPA 붙임)

Controller

Service

Repository

Entity

DTO같은 거고 중요하다.

package com.apple.web.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class JPABoard {

	@Id
	private int jbno;
	
	@Column
	private String jbtitle;
	
	@Column
	private String jbcontent;
}

PK가 @Id
그 외의 컬럼은 @Column

더 명확히 써준다면

import java.time.LocalDateTime;

import org.hibernate.annotations.ColumnDefault;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class JPABoard {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int jbno;
	
	@Column(columnDefinition = "TEXT")
	private String jbtitle;
	
	@Column(columnDefinition = "LONGTEXT")
	private String jbcontent;
	
	@ColumnDefault("CURRENT_TIMESTAMP")
	private LocalDateTime jbdate = LocalDateTime.now();
	
	@ColumnDefault("1")
	private int jbread;
	
	@ColumnDefault("0")
	private int jblike;
	
}

컬럼을 설정하면서 만들어준다는 느낌.
lombok getter, setter도 해준다.

이러고 application.properties
update -> create로 변경. 지우고 새로 만든다는 뜻.

spring.jpa.hibernate.ddl-auto=create

서버재실행
drop table if exists jpaboard 이 부분이 나온다.

Hibernate: 
    drop table if exists jpaboard
Hibernate: 
    create table jpaboard (
        jblike integer default 0 not null,
        jbno integer not null auto_increment,
        jbread integer default 1 not null,
        jbdate datetime(6) default CURRENT_TIMESTAMP,
        jbcontent LONGTEXT,
        jbtitle TEXT,
        primary key (jbno)
    ) engine=InnoDB

application.properties 이거 혹시나해서 추가

# thymeleaf호출시 경로 설정
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

도커
쿠버네티스

AWS

쿠버네티스는 아는데, 쿠브플로는 뭐죠?

5개의 댓글

comment-user-thumbnail
2024년 3월 13일

감사합니다

답글 달기
comment-user-thumbnail
2024년 3월 13일

감사합니다

1개의 답글
comment-user-thumbnail
2024년 3월 13일

너무 멋져용🫢🫢🫢

1개의 답글