241128 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.
뷰
AI 없이 코드를 짜는 현직자는 없다. AI를 잘 다룰줄 아는 것이 중요하다.
질문을 잘해야 대답을 잘해준다. 그렇기에 기초를 튼튼히 다지는 것이 중요하다.
또 툴을 잘 사용하여 개발 시간 최적화, 코드의 최적화를 잘하고 글로 잘 적어내어보자!
프로젝트 코드를 다 짜고 용어를 정확히 찝어가며 설명할 수 있어야 한다.
프로젝트를 통해서 최종적으로 온갖 것들을 공부하는 것이다.
뷰 실습 (ajax-axios 1)
axois cdn
<script src="https://unpkg.com/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> // 배포용
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.js"></script> // 개발용
JSON 데이터 : http://openapi.seoul.go.kr:8088/sample/json/SeoulLibraryTimeInfo/1/5/
JSON 구조 : SeoulLibraryTimeInfo 안에 row가 들어가있는 구조
ex15ajax_lib.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<button @click="fetchData">도서관 정보 읽기</button>
<button @click="clearData">도서관 정보 삭제</button>
<div v-for="lib in libraries" :key="lib.LBRRY_NAME">
<h3>{{lib.LBRRY_NAME}}</h3>
주소 : {{lib.ADRES}}<br/>
전화 : {{lib.TEL_NO}}<hr/>
</div>
</div>
<script>
const {createApp} = Vue;
createApp({
data() {
return {
libraries:[] // 도서관 정보를 담아둘 배열
};
},
methods:{
fetchData(){
axios.get("http://openapi.seoul.go.kr:8088/sample/json/SeoulLibraryTimeInfo/1/5/")
.then(response => {
this.libraries = response.data.SeoulLibraryTimeInfo.row;
})
.catch(error => {
console.error('fetch err : ', error);
})
},
clearData(){
this.libraries = []; // 배열 비우기
},
}
}).mount("#app");
</script>
</body>
</html>
읽기
삭제
뷰 실습 (ajax-axios 2)
위도, 경도 넣고 지역 날씨 정보 얻기
https://api.open-meteo.com/v1/forecast?latitude=38.146609&longitude=127.3132256¤t_weather=true
ex16ajax_weather.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<button @click="fetchData">철원군 날씨 정보 얻기</button>
<div v-if="weather">
<h3>날씨 정보</h3>
예보 시간 : {{weather.time}}<br/>
온도 : {{weather.temperature}}<br/>
풍속 : {{weather.windspeed}}<br/>
풍향 : {{weather.winddirection}}<br/>
</div>
</div>
<script>
const {createApp} = Vue;
createApp({
data() {
return {
weather:null
};
},
methods:{
fetchData(){
axios.get("https://api.open-meteo.com/v1/forecast?latitude=38.146609&longitude=127.3132256¤t_weather=true")
.then(response => {
this.weather = response.data.current_weather;
})
.catch(error => {
console.error('fetch err : ', error);
})
},
}
}).mount("#app");
</script>
</body>
</html>
다른 도메인의 정보를 가져오는 것은 cors 정책에 위반되지만 해당 오픈api 서버들이 접근을 허용해주기 때문에 가능
뷰 실습 (ajax-axios 3)
우리가 서버를 만들고 데이터를 가져와보자! (이클립스, 아파치 톰캣 서버)
기존 실습했던 abcReact 자료 사용
test.jsp
<%@page import="org.json.simple.JSONObject"%>
<%@page import="org.json.simple.JSONArray"%>
<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/json; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
JSONArray jikwons = new JSONArray();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
String result = "";
try {
Class.forName("org.mariadb.jdbc.Driver");
String url = "jdbc:mariadb://localhost:3306/test";
conn = DriverManager.getConnection(url, "root", "1111");
} catch(Exception e) {
System.out.println("=== DB 연동 오류 ===");
e.printStackTrace();
return;
}
try {
pstmt = conn.prepareStatement("SELECT * FROM jikwon");
rs = pstmt.executeQuery();
while(rs.next()) {
JSONObject obj = new JSONObject();
obj.put("jikwonno", rs.getInt("jikwonno"));
obj.put("jikwonname", rs.getString("jikwonname"));
obj.put("jikwonjik", rs.getString("jikwonjik"));
obj.put("jikwonpay", rs.getString("jikwonpay"));
jikwons.add(obj);
}
out.print(jikwons.toString());
} catch(Exception e) {
System.out.println("=== DB 연동 오류 ===");
e.printStackTrace();
return;
} finally {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
}
%>
서버 실행 후 JSON 객체의 직원 데이터 가져와보기 (서버 살려놓기)
http://localhost:8080/abcReact/test.jsp
ex17ajax_jikwon.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<button v-on:click="showFunc">직원 자료 보기</button>
<table v-if="datas && datas.length > 0">
<tr>
<th>사번</th><th>이름</th><th>직급</th><th>연봉</th>
</tr>
<template v-for="data in datas">
<tr>
<td>{{data.jikwonno}}</td>
<td>{{data.jikwonname}}</td>
<td>{{data.jikwonjik}}</td>
<td>{{data.jikwonpay}}</td>
</tr>
</template>
</table>
</div>
<script>
const {createApp} = Vue;
createApp({
data() {
return {
datas:[]
}
},
methods:{
showFunc(){
axios.get("http://localhost:8080/abcReact/test.jsp")
.then(response => {
console.log(response.data);
this.datas = response.data;
})
.catch(error => {
console.log("에러 : ", error);
})
}
}
}).mount("#app");
</script>
</body>
</html>
cors 정책 위반으로 에러가 뜬다. 서버에서 다른 도메인의 접근을 허용해주어야한다.
다시 이클립스로 가서 접근을 허용해주자
cors 해결
test.jsp
// cors 해결
response.setHeader("Access-Control-Allow-Origin", "*"); // 헤더에 Access-Control-Allow-Origin 문구를 넣어준다.
response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); // 요청 방식별 허용 여부
JSONArray jikwons = new JSONArray();
... 생략
%>
cors 허용 후 데이터를 가져올 수 있다.
뷰 실습 (ajax-axios 4)
세 개의 테이블 조인 예제 사용
sprweb_webjpa_join3plus
3개의 테이블을 조인하고 데이터를 가져오는 것을 확인
Buser
package pack;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
public class Buser {
@Id
private int buserno;
private String busername;
private String buserloc;
private String busertel;
@OneToMany(mappedBy = "buser", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
private List<Jikwon> jikwons;
}
Jikwon
package pack;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Data
@NoArgsConstructor
public class Jikwon {
@Id
private int jikwonno;
private String jikwonname;
@ManyToOne
@JoinColumn(name = "busernum")
private Buser buser;
private String jikwonjik;
private int jikwonpay;
private Date jikwonibsail;
private String jikwongen;
private String jikwonrating;
@OneToMany(mappedBy = "jikwon", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
private List<Gogek> gogeks; // gogek와의 관계 추가
}
Gogek
package pack;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
public class Gogek {
@Id
private int gogekno;
private String gogekname;
private String gogektel;
private String gogekjumin;
@ManyToOne
@JoinColumn(name = "gogekdamsano")
@JsonBackReference // @ManyToOne에 대한 순환참조 방지
private Jikwon jikwon; // 직렬화할때는 직원 데이터를 제외하고 진행한다.
}
JikwonDto
package pack;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class JikwonDto {
private int jikwonno;
private String jikwonname;
private String buser; // 부서 정보를 가져오기 위함
private String jikwonjik;
private int jikwonpay;
private String jikwonibsail;
private String jikwongen;
private String jikwonrating;
}
JoinController
package pack;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JoinController {
@Autowired
private JoinService joinService;
// private final JoinService joinService;
//
// public JoinController(JoinService joinService) {
// this.joinService = joinService;
// }
@GetMapping("/joindata")
public List<BuserJikwonGogekDTO> getJoinData() {
return joinService.getJoinedData();
}
@GetMapping("/busers")
public List<Buser> getAllBusers() {
return joinService.getAllBusers();
}
@GetMapping("/jikwons")
public List<JikwonDto> getAllJikwons() {
return joinService.getAllJikwons();
}
@GetMapping("/gogeks")
public List<Gogek> getAllGogeks() {
return joinService.getAllGogeks();
}
}
JoinService
package pack;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
@Service
public class JoinService {
private final JikwonRepository jikwonRepository;
private final GogekRepository gogekRepository;
private final BuserRepository buserRepository;
public JoinService(BuserRepository buserRepository, JikwonRepository jikwonRepository, GogekRepository gogekRepository) {
this.buserRepository = buserRepository;
this.jikwonRepository = jikwonRepository;
this.gogekRepository = gogekRepository;
}
public List<BuserJikwonGogekDTO> getJoinedData() {
// DTO로 직접 데이터를 조회
return buserRepository.findAllJoinedData();
}
public List<Buser> getAllBusers() {
// Buser 데이터를 조회
return buserRepository.findAll();
}
public List<JikwonDto> getAllJikwons() {
// jikwon 데이터를 조회
return jikwonRepository.findAll().stream()
.map(jikwon -> new JikwonDto(
jikwon.getJikwonno(),
jikwon.getJikwonname(),
jikwon.getBuser().getBusername(),
jikwon.getJikwonjik(),
jikwon.getJikwonpay(),
jikwon.getJikwonibsail().toString(),
jikwon.getJikwongen(),
jikwon.getJikwonrating()
)).collect(Collectors.toList());
}
public List<Gogek> getAllGogeks() {
// gogek 데이터를 조회
return gogekRepository.findAll();
}
}
💡 fetch / await fetch 차이점
fetch :
/ 그냥 비동기로 실행
/ then 체인 사용하여 처리 (then이 길어지면 코드가 복잡해보임, 가독성이 떨어짐)
/ 마구 사용
await fetch :
/ 요청이 끝날 때까지 대기
/ try-catch로 처리(에러 처리가 쉽다)
/ async와 함께 사용
클라이언트단으로 이동
ex18ajax_route.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style></style>
</head>
<body>
<div id="app">
<h1>자료보기</h1>
<nav>
<router-link to="/">전체자료</router-link> |
<router-link to="/busers">부서자료</router-link> |
<router-link to="/jikwons">직원자료</router-link> |
<router-link to="/gogeks">고객자료</router-link>
</nav>
<router-view></router-view>
</div>
<script>
// 전체자료 컴포넌트
const AllData = {
template: `
<div>
<h2>전체자료</h2>
<table v-if="allData.length">
<thead>
<tr>
<th>부서번호</th><th>부서명</th><th>직원명</th><th>관리고객명</th>
</tr>
</thead>
<tbody>
<tr v-for="data in allData" :key="data.jikwonno">
<td>{{data.buserno}}</td>
<td>{{data.busername}}</td>
<td>{{data.jikwonname}}</td>
<td>{{data.gogekname}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
allData: [],
};
},
mounted() {
axios
.get("http://localhost/joindata")
.then((response) => {
this.allData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
};
// 부서자료 컴포넌트
const BuserData = {
template: `
<div>
<h2>부서자료</h2>
<table v-if="buserData.length">
<thead>
<tr>
<th>부서번호</th><th>부서명</th><th>위치</th><th>전화번호</th>
</tr>
</thead>
<tbody>
<tr v-for="data in buserData" :key="data.buserno">
<td>{{data.buserno}}</td>
<td>{{data.busername}}</td>
<td>{{data.buserloc}}</td>
<td>{{data.busertel}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
buserData: [],
};
},
mounted() {
axios
.get("http://localhost/busers")
.then((response) => {
this.buserData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
};
// 직원자료 컴포넌트
const JikwonData = {
template: `
<div>
<h2>부서자료</h2>
<table v-if="jikwonData.length">
<thead>
<tr>
<th>사번</th><th>이름</th><th>직급</th><th>연봉</th>
</tr>
</thead>
<tbody>
<tr v-for="data in jikwonData" :key="data.jikwonno">
<td>{{data.jikwonno}}</td>
<td>{{data.jikwonname}}</td>
<td>{{data.jikwonjik}}</td>
<td>{{data.jikwonpay}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
jikwonData: [],
};
},
mounted() {
axios
.get("http://localhost/jikwons")
.then((response) => {
this.jikwonData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
};
// 고객자료 컴포넌트
const GogekData = {
template: `
<div>
<h2>고객자료</h2>
<table v-if="gogekData.length">
<thead>
<tr>
<th>고객번호</th><th>고객이름</th><th>전화</th>
</tr>
</thead>
<tbody>
<tr v-for="data in gogekData" :key="data.gogekno">
<td>{{data.gogekno}}</td>
<td>{{data.gogekname}}</td>
<td>{{data.gogektel}}</td>
</tr>
</tbody>
</table>
<p v-else>데이터가 없어요!</p>
</div>
`,
data() {
return {
gogekData: [],
};
},
mounted() {
axios
.get("http://localhost/gogeks")
.then((response) => {
this.gogekData = response.data;
})
.catch((error) => {
console.log("err : ", error);
});
},
};
// Vue Router 설정
const routes = [
{ path: "/", component: AllData },
{ path: "/busers", component: BuserData },
{ path: "/jikwons", component: JikwonData },
{ path: "/gogeks", component: GogekData },
];
// 라우터 생성
const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes,
});
// Vue 앱 생성
const app = Vue.createApp({});
app.use(router);
app.mount("#app");
</script>
</body>
</html>
에러
🚨 CORS
1. Controller에 @CrossOrigin(origins = "도메인")을 걸어준다.
2. WebConfig 클래스를 만들어 접근을 허용해준다.
WebConfig
package pack;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // “*“같은 와일드카드를 사용
.allowedMethods("GET", "POST") // 허용할 HTTP method
.allowCredentials(true); // 쿠키 인증 요청 허용
}
}
결과
뷰로 작성한 html을 static에 넣고 실행시켜도 잘 작동한다.
'Study > Acorn' 카테고리의 다른 글
241202 노드 (개요, CommonJs, ESM, 동기/비동기) (3) | 2024.12.02 |
---|---|
241129 뷰(RESTful) (1) | 2024.11.29 |
241127 뷰 (이벤트, 구글 차트, route, ajax) (0) | 2024.11.27 |
241126 뷰 (이벤트) (1) | 2024.11.26 |
241125 뷰 (CDN, 라이프사이클, 컴포넌트, defer, 디렉티브) (1) | 2024.11.25 |