241104 AJAX (DB연동/문제)

241104 에이콘 아카데미 수업을 기반하여 작성되었음을 알립니다.

AJAX

과거 xml을 컴퓨터와 컴퓨터 사이의 데이터를 전달하는 방식으로 많이 썼다. 현재는 대부분 JSON방법을 사용하여 데이터를 전달한다.

txt, csv, tsv를 데이터 전달을 위해 사용하긴 하는데 xml, JSON 방식은 데이터를 구조적으로 전달할 수 있어 수정, 삭제, 등록 등의 CRUD 작업이 가능하지만 반면 txt, csv, tsv 방법들은 불가능하다.

AJAX는 클라이언트 사이드에서 자바스크립트로 요청하고 서버 측의 응답도 자바스크립트에서 받게 된다. 참고로 xml을 자바스크립트로 받는 경우는 그냥 데이터로만 받을 수 있다.

기존에는 HTML로 브라우저에서 받는 것인데 AJAX를 사용하게 된다면 비동기 방식으로 자바스크립트 내에서 응답을 받을 수 있다. 사용자가 서버에 요청만 하면 응답을 받기까지 기다리기만 하면 되는 점이 AJAX의 큰 장점이라고 볼 수 있고, 새로고침이나 페이지 이동없이 단 하나의 페이지에서 요청과 응답이 가능하다는 점에서 SPA 구현에 유리하다. (SPA 구현은 리액트나 뷰를 사용하게 된다면 더 쉽게 구현 가능하다고 한다.)

지금까지는 GET/POST 방식으로만 CRUD를 진행해왔지만 추후 배울 RESTful을 사용하게 된다면 요청 방식을 세분화하여 CRUD를 전문적으로 나눌 수 있다고 한다.

AJAX는 SELECT만 가능, AJAX로 INSERT, UPDATE, DELETE를 하려면 바보 같은 짓이다. 

RESTful을 이용하면 SELECT는 물론 INSERT, UPDATE, DELETE도 가능하다!

DB 연동 실습 1 (@MVC + AJAX + JPA)

의존성 추가

Spring Boot DevTools / Spring Web / Spring Data JPA / MariaDB Driver / Lombok

application.properties

spring.application.name=sprweb30_sangdata
server.port=80

#mariadb server connect
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=1111
# jpa
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

Entity

package pack.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;

@Entity
@Getter
@Table(name = "sangdata")
public class Sangpum {
	@Id
	private int code;
	private String sang,su,dan;
}

DTO

package pack.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import pack.entity.Sangpum;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SangpumDto {
	private int code;
	private String sang,su,dan;
	
	public static SangpumDto toDto(Sangpum sangpum) {
		return SangpumDto.builder()
				.code(sangpum.getCode())
				.sang(sangpum.getSang())
				.su(sangpum.getSu())
				.dan(sangpum.getDan())
				.build();
	}
}

Repository

package pack.model;

import org.springframework.data.jpa.repository.JpaRepository;

import pack.entity.Sangpum;

public interface DataRepository extends JpaRepository<Sangpum, Integer> {
	
}

DAO

package pack.model;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import pack.dto.SangpumDto;

@Repository
public class DataDao {
	@Autowired
	private DataRepository dataRepository;
	
	public List<SangpumDto> getSangpumAll() {
		List<SangpumDto> slist = dataRepository.findAll()
				.stream()
				.map(SangpumDto :: toDto)
				.collect(Collectors.toList());
		
		return slist;
	}
}

Controller

package pack.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import pack.dto.SangpumDto;
import pack.model.DataDao;

@Controller
public class SangpumController {
	@Autowired
	private DataDao dataDao;
	
	@GetMapping("sangpums")
	@ResponseBody
	public Map<String, Object> sangpumProcess() {
		List<Map<String, String>> list = new ArrayList<Map<String,String>>();
		Map<String, String> data = null;
		
		for(SangpumDto s:dataDao.getSangpumAll()) {
			data = new HashMap<String, String>();
			data.put("code", String.valueOf(s.getCode())); // int > String으로 타입 변환
			data.put("sang", s.getSang());
			data.put("su", s.getSu());
			data.put("dan", s.getDan());
			list.add(data);
		}
		System.out.println(list);
		
		Map<String, Object> sanglist = new HashMap<String, Object>(); // Map써도 무방함
		sanglist.put("datas", list);
		return sanglist;
	}
}

index

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="sangpums">상품보기(JSON data)</a><br/>
<a href="sangajax.html">상품보기(Ajax : jQuery 요청)</a><br/>
<a href="sangajax2.html">상품보기(Ajax : fetch then 요청)</a><br/>
</body>
</html>

AJAX (jQuery)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$(document).ready(function(){
	$("#btnShow").click(function(){
		$.ajax({
			type:"get",
			url:"sangpums",
			dataType:"json",
			success:function(sangpumdatas){
				//alert(sangpumdatas.datas);
				let str = "";
				const sdatas = sangpumdatas.datas;
				$(sdatas).each(function(index, arr){
					str += "<tr>";
					str += "<td>" + arr["code"] + "</td>";
					str += "<td>" + arr["sang"] + "</td>";
					str += "<td>" + arr["su"] + "</td>";
					str += "<td>" + arr["dan"] + "</td>";					
					str += "</tr>";
				});
				$("tbody").html(str);
			},error:function(){
				jQuery("#showErr").text("에러 발생");
			}
		});
	});
});
</script>
</head>
<body>
<h2>상품정보 출력(@MVC + AJAX + JPA)</h2>
<button id="btnShow">상품 정보 보여주라</button>
<br/><br/>
<table>
  <thead>
    <tr><th>코드</th><th>품명</th><th>수량</th><th>단가</th></tr>
  </thead>
  <tbody>
  
  </tbody>
</table>
<br/>
<div id="showErr"></div>
</body>
</html>

AJAX (fetch then)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function(){
	//document.querySelector("#btnShow").addEventListener("click", func);
	document.getElementById("btnShow").onclick = func; // onclick도 괜찮
});
function func(){
	//alert("a");
	fetch("sangpums")
		.then(response => {
			if(!response.ok){
				throw new Error("네트워크 오류");
			}	
			return response.json();
		})
		.then(sangpumdatas => {
			let str = "";
			const sdatas = sangpumdatas.datas;
			sdatas.forEach(arr => {
				str += "<tr>";
				str += "<td>" + arr["code"] + "</td>";
				str += "<td>" + arr["sang"] + "</td>";
				str += "<td>" + arr["su"] + "</td>";
				str += "<td>" + arr["dan"] + "</td>";					
				str += "</tr>";
			});
			document.querySelector("tbody").innerHTML = str;
		})
		.catch(error => {
			document.querySelector("#showErr").textContent = "에러 발생 : " + error.message;
		});
}
</script>
</head>
<body>
<h2>상품정보 출력(@MVC + AJAX + JPA)</h2>
<button id="btnShow">상품 정보 보여주라2</button>
<br/><br/>
<table>
  <thead>
    <tr><th>코드</th><th>품명</th><th>수량</th><th>단가</th></tr>
  </thead>
  <tbody>
  
  </tbody>
</table>
<br/>
<div id="showErr"></div>
</body>
</html>

결과

JSON으로 변환된 데이터 확인

AJAX (jQuery)

 

AJAX (fetch, then)

💡
"AJAX 구현 시 console.log 와 alert를 사용하여 중간중간 코드에 이상이 없는 지 자주 확인"
"jQuery 사용 시 코드가 좀 더 간결해지나 jQuery의 사용보다 fetch then 사용을 더 강조하셨다. fetch then을 좀 더 연습해봐야 겠다."
"click 이벤트 외에 각 상황별로 절절한 이벤트를 사용하는게 더 좋겠다."

DB 연동 실습 2 (@MVC + AJAX + JPA)

의존성 추가

Spring Boot DevTools / Spring Web / Spring Data JPA / MariaDB Driver / Lombok / Thymeleaf

application.properties

spring.application.name=sprweb31ajax_buser
server.port=80
spring.thymeleaf.cache=false

#mariadb server connect
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=1111
# jpa
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

Entity (Buser)

package pack.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Buser {
	@Id
	private int buserno;
	private String busername, busertel;
}

Entity (Jikwon)

package pack.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class Jikwon {
	@Id
	private int jikwonno;
	private String jikwonname, jikwonjik;
	
	@Column(name = "busernum")
	private String buser;
}

Repository 1

package pack.model;

import org.springframework.data.jpa.repository.JpaRepository;

public interface DataRepository extends JpaRepository<Buser, Integer> {
	
}

Repository 2

package pack.model;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface DataRepository2 extends JpaRepository<Jikwon, Integer> {
	@Query("select j from Jikwon j where buser=?1")
	List<Jikwon> buserDatas(int buserno); // 부서별 직원 조회
}

DAO

package pack.model;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class DataDao {
	@Autowired
	private DataRepository dataRepository;
	
	@Autowired
	private DataRepository2 dataRepository2;
	
	// 부서 자료 읽기
	public List<Buser> buserList() {
		List<Buser> blist = dataRepository.findAll();
		return blist;
	}
	
	// 직원 자료 읽기
	public List<Jikwon> jikwonList(int buserno) {
		List<Jikwon> jlist = dataRepository2.buserDatas(buserno);
		return jlist;
	}
}

Controller

package pack.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import pack.model.Buser;
import pack.model.DataDao;
import pack.model.Jikwon;

@Controller
public class DataController {
	@Autowired
	private DataDao dataDao;
	
	@GetMapping("buserlist")
	public String buserProcess(Model model) {
		List<Buser> blist = dataDao.buserList();
		model.addAttribute("blist", blist);
		return "list";
	}
	
	@GetMapping("jikwonlist")
	@ResponseBody
	public Map<String, Object> jikwonProcess(@RequestParam("buserno")int buserno) {
		List<Map<String, String>> jlist = new ArrayList<Map<String,String>>();
		Map<String, String> data = null;
		
		for(Jikwon j:dataDao.jikwonList(buserno)) {
			data = new HashMap<String, String>();
			data.put("no", String.valueOf(j.getJikwonno()));
			data.put("name", j.getJikwonname());
			data.put("jik", j.getJikwonjik());
			jlist.add(data);
		}
		Map<String, Object> jiklist = new HashMap<String, Object>();
		jiklist.put("datas", jlist);
		return jiklist;
	}
}

Controller (Login)

package pack.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import jakarta.servlet.http.HttpSession;

@Controller
public class LoginController {
	
	@GetMapping("login")
	public String login(HttpSession session) {
		if(session.getAttribute("idKey") == null) {
			return "redirect:/login.html"; // 타임리프가 아님
		} else {
			return "redirect:/buserlist";
		}
	}
	
	@PostMapping("login")
	public String login(HttpSession session, 
			@RequestParam("id")String id, 
			@RequestParam("pwd")String pwd) {
		if(id.equals("aa") && pwd.equals("11")) {
			session.setAttribute("idKey", id);
			return "redirect:/buserlist";
		} else {
			return "redirect:/login.html";
		}
	}
	
	@RequestMapping(value = "logout", method = RequestMethod.GET)
	public String logout(HttpSession session) {
		//session.invalidate(); // 모든 세션을 다 지우므로 권장 방법은 아님
		session.removeAttribute("idKey");
		return "redirect:/";
	}
}

index

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="buserlist">부서 정보</a><br/>
<a href="login">로그인</a>
</body>
</html>

list

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">

<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
function func(buserno){
	//alert(buserno);
	$("#showData").empty(); // 비우고 시작
	
	$.ajax({
		type:"GET",
		url:"jikwonlist?buserno=" + buserno,
		dataType:"json",
		success:function(jikwondatas) {
			//alert(jikwondatas.datas);
			let count = 0;
			let str = "<br/>* 근무 직원 *<br/>";
			str += "<table border='1'>"
			str += "<tr><th>사번</th><th>직원명</th><th>직급</th></tr>";
			const jlist = jikwondatas.datas;
			$(jlist).each(function(idx, obj){
				str += "<tr>";
				str += "<td>" + obj.no + "</td>";
				str += "<td>" + obj.name + "</td>";
				str += "<td>" + obj.jik + "</td>";
				str += "</tr>";
				count++;
			});
			str += "<tr><td colspan='3'>인원수 : " + count + "명</td></tr>";
			str += "</table>"
			
			$("#showData").append(str);
		},
		error:function(){
			$("#showData").text("에러")
		}
	});
}
</script>
</head>
<body>
<a href="logout">로그아웃</a><br>

** 부서 정보 **<br/>
<table border="1">
  <tr><th>부서번호</th><th>부서명</th><th>부서전화</th></tr>
  <th:block th:each="bu:${blist}">
  <tr>
    <td>[[${bu.buserno}]]</td>
    <td>
      <a th:href="|javascript:func(${bu.buserno})|" th:text="${bu.busername}"></a>
    </td>
    <td th:text="${bu.busertel}"></td>
  </tr>
  </th:block>
</table>
<hr/>
<div id="showData"></div>
</body>
</html>

login

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>로그인</h2>
<form action="login" method="post">
아이디 : <input type="text" name="id"><br/>
비밀번호 : <input type="text" name="pwd"><br/>
<input type="submit" value="확인">
</form>
</body>
</html>

결과

부서명 클릭시 AJAX 사용하여 아래에 부서별 직원 출력


DB 연동 문제 (@MVC + AJAX + JPA)

문제

문제 : 직급명 입력 후 확인 버튼을 누르면 해당 직급 직원들 출력
1. AJAX 사용
2. select 박스에 들어가는 직급들은 DB에서 가져온다.
3. 연봉 평균은 소수점 첫번째 자리까지 출력

결과

에러와 고난

🚨 java.lang.IllegalArgumentException: Invalid character found in the request target [/list?jikwonjik=[object%20Object] ]. The valid characters are defined in RFC 7230 and RFC 3986

이 오류는 요청 URL에 허용되지 않는 문자가 포함되어 있을 때 발생한다고 한다.
JavaScript에서 객체가 아닌 select 박스의 실제 값 전달하기하는 방법을 하기 링크 참고하여 해결 완료


> const jikwonjik = $("#select").val(); // select 선택 밸류 얻기

🙏 다마고치 : 자바스크립트 및 jQuery를 활용한 Select 옵션의 텍스트 및 값 가져오기

 

🚨 연봉 평균의 경우 소수점 첫째짜리까지 구하는 방법을 잘 몰랐다.

해당 방법 구글링 후 해결 완료 .toFixed() 사용!

🙏 검은바닷가재 : 매우 간단한! 자바스크립트(JS) 소수점 자르기와 두자리(둘째자리)까지 나오게 하기

 

🚨 타임리프를 사용하여 반복을 돌려 직급을 출력하니 전 사원의 직급이 모두 출력됨

Set의 특성을 활용하여 중복된 직급 제외 후 select 옵션 출력

// List 류는 순서가 있고 중복 허용
// Set 류는 순서가 없고 중복 미허용, 자동으로 unique 화
// Map 류는 키와 밸류 한 쌍으로 이루어짐, 키는 중복될 수 없으나, 밸류는 중복 허용 

 

'Study > Acorn' 카테고리의 다른 글

241107 RESTful / AOP  (3) 2024.11.07
241106 RESTful (CRUD/문제)  (1) 2024.11.07
241105 RESTful (PUT/DELETE/문제)  (0) 2024.11.05
241105 RESTful (GET/POST)  (2) 2024.11.05
241101 AJAX (jQuery/fetch then/AXIOS)  (0) 2024.11.04