Spring에서 XML/JSON을 AJAX로 처리하고 변환하는 방법에 대해 알아보자.
- 최근에는 서비스나 데이터를 HTTP 기반 API 형태로 제공하는 경우가 많다.
- 이들 API의 특징 중 하나는 응답으로 XML이나 JSON 형식을 사용한다는 것이다.
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/security/tags"
prefix="sec"%>
<%@ page session="false" contentType="text/html; charset=UTF-8"%>
<%@ page session="false"%>
<script src="/resources/js/dept.js"></script>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Hello world!</h1>
<P>The time on the server is ${serverTime}.</P>
<form action="" method="get">
empno : <input type="text" id="empno" name="empno" value="7369">
<input type="button" id="idCheck" value="ID중복체크"> <br>
ename : <input type="text" id="ename" name="ename" value="hong">
</form>
<script>
$(function () {
$("#idCheck").on("click", function() {
var empno = $("#empno").val();
console.log(`>empno = \${empno}`);
$.ajax({
url:"/idcheck", //이 요청에 대해 응답해주는 HomeAjaxController.java 만들기
method:"GET",
data:{empno:empno}, // js Object, json data - 자동으로 java로 받아서 파라미터로 사용가능
dataType:"json", // new EmpVO() -> json 변환 응답 - js Object 변환
success:function(data, callback, xhr){
alert(data.idCheckResult);
},
error:function(xhr, errorType){
alert(errorType);
}
});
});
});
</script>
<!-- 부서 추가 모달창 -->
<!-- The Modal -->
<div id="add-modal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<h2>Ajax 부서 추가</h2>
</div>
<div class="modal-body">
<div class="group">
<label>부서번호</label>
<input type="text" class="short" name="deptno" value="50">
</div>
<div class="group">
<label>부서명</label>
<input type="text" class="short" name="dname" value="QC">
</div>
<div class="group">
<label>지역명</label>
<input type="text" class="short" name="loc" value="SEOUL">
</div>
<div>
<button id="add-dept" type="button" class="ok">확인</button>
<button type="button" class="delete">삭제</button>
</div>
</div>
<div class="modal-footer">
<h3>Modal Footer</h3>
</div>
</div>
</div>
<script>
//확인 버튼을 누르면 ajax로 부서 추가 + 테이블에도 추가
$("#add-modal #add-dept").on("click", function (){ //확인 버튼을 onclick할 때
//모달창 안의 텍스트박스에 입력한 값을 얻어오는 작업
let deptno = $("#add-modal :text[name=deptno]").val();
let dname = $("#add-modal :text[name=dname]").val();
let loc = $("#add-modal :text[name=loc]").val();
console.log(`deptno==\${deptno}`);
//dept.js 파일에 있는 ajax 함수 이용
//function add(dept, callback, error) { js Object } -> 컨트롤메서드 java Object
let dept={
deptno : deptno,
dname : dname,
loc : loc
};
deptService.add(dept, function(result) {
if(result==='SUCCESS'){
alert(result);
}//if
});//add
}); // click
$("#add-modal button.delete").on("click",function(){
if ( confirm("정말 삭제할까요?") ) {
let deptno = $("#add-modal :text[name=deptno]").val();
console.log(`>> 삭제할 deptno == \${ deptno }`);
deptService.remove( deptno, function (result){
alert(result);
} ); // remove
} // if
}); //delete click
</script>
</body>
</html>
package org.doit.ik;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpVO;
import org.doit.ik.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RestController //응답하는 것이 viewpage나 viewname이 아니라 '데이터'(xml,json)인 경우에 RestController를 사용
@Log4j
public class HomeAjaxController {
private static final Logger logger = LoggerFactory.getLogger(HomeAjaxController.class);
@Setter(onMethod=@__({@Autowired}))
private MemberMapper memberMapper;
/* @GetMapping(value="/idcheck")
public int idCheck(String empno) { //@RequestBody는 생략해도 된다.
//앞에서 넘어오는게 JS 객체인데도 알아서 java 형태로 변환되는 것
log.info(">/idcheck...GET방식 Ajax" + empno);
return this.memberMapper.idCheck(empno);
} */
@GetMapping(value="/idcheck"
, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public EmpVO idCheck(String empno) { //@RequestBody는 생략해도 된다.
//앞에서 넘어오는게 JS 객체인데도 알아서 java 형태로 변환되는 것
log.info(">/idcheck...GET방식 Ajax" + empno);
int idCheckResult = this.memberMapper.idCheck(empno);
//예전에는 JSP: JSONObject, JsonArray JSON 라이브러리 사용해서 가공
return new EmpVO(empno, "홍길동", idCheckResult);
}
@PostMapping("/scott/dept/new")
public ResponseEntity<String> insertDept(@RequestBody DeptDTO dto){ //@RequestBody는 JSON데이터를 자동으로 뒤에 선언된 형태로 변환해준다
//응답하는 데이터 뿐아니라 상태값까지 넘길때는 ResponseEntity를 리턴타입으로 한다.
log.info(">/scott/dept/now POST...");
int insertResult = this.memberMapper.insertDept(dto);
return insertResult==1? new ResponseEntity<String>("SUCCESS", HttpStatus.OK): new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
} //응답하는 상태값까지 같이 넘길 때는 ResponseEntity를 사용한다.
@DeleteMapping(value = "/scott/dept/{deptno}"
, produces= {MediaType.TEXT_PLAIN_VALUE})//제공되는 데이터는 text 형태의 value 값이라는 의미
//{변수명}을 주고 @PathVariable("deptno") int deptno하면 url 속에 있는 값을 받아서 변수 deptno에 저장하겠다는 것
public ResponseEntity<String> DeleteDept(@PathVariable("deptno") int deptno){
//url을 읽어들일 때 @PathVariable 사용
//예전에는 특정 글을 보기 위해 /board/get.htm?seq=10을 줬다면 이제 /board/10+GET으로 가져올 수도 있다.
//여기서 /board/delete.htm?seq=10 를 /board/10+DELETE 이런식으로 url 자체에 의미가 담기는게 바로 Restfull이다.
log.info("/scott/dept/"+deptno+ "DELETE 요청함");
int deltetResult = this.memberMapper.deleteDept(deptno); //1, 0
return deltetResult==1? new ResponseEntity<String>("SUCCESS",HttpStatus.OK)
: new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
//삼항연산자를 이용해 성공하면 성공메세지 SUCCESS와 함께 응답상태를 같이 보내고, 실패 시 응답 상태만 보내준다.
}
}
console.log("Dept Module...");
var deptService = (function(){
function add(dept, callback, error){
console.log("add dept...");
$.ajax({
type:'post',
url:'/scott/dept/new',
data:JSON.stringify(dept),
cache:false,
contentType:"application/json; charset=utf-8",
beforeSend:function(xhr){
//console.log("add dept... beforeSend");
},
success:function(result, status, xhr){
//console.log("add dept... success");
if(callback){
callback(result);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // add
function remove(deptno, callback, error){
console.log("remove dept...");
$.ajax({
type:'delete',
url:'/scott/dept/'+ deptno,
cache:false,
success:function(deleteResult, status, xhr){
if(callback){
callback(deleteResult);
}
},
error:function(xhr, status, er){
if(error){
error(er);
}
}
});
} // remove
return {
add : add,
remove : remove
};
})();
package org.doit.ik.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmpVO {
private String empno;
private String ename;
private int idCheckResult;
}
package org.doit.ik.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor //디폴트 생성자
@NoArgsConstructor //모든 필드에 대한 생성자
public class DeptDTO {
private int deptno;
private String dname;
private String loc;
private int numberOfEmps; //사원수
}
package org.doit.ik.mapper;
import java.util.List;
import org.doit.ik.domain.DeptDTO;
public interface MemberMapper {
int idCheck(String empno);
List<DeptDTO> selectDept(); //부서정보를 Select 해 List에 담는 인터페이스
int insertDept(DeptDTO dto);
int deleteDept(int deptno); // 삭제하고자 하는 부서번호를 준다.
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.MemberMapper">
<select id="idCheck" resultType="int">
SELECT COUNT(*)
FROM emp
WHERE empno = #{empno}
</select>
<!-- List<deptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
<!-- id 속성에는 반드시 인터페이스의 메소드명이 들어가야 한다. -->
<!-- resultType 속성: select 태그에는 반드시 resultType(return타입)이 들어가야 한다. 담기는 객체의 타입이 들어감 -->
SELECT d.deptno, dname, loc, COUNT(e.empno) numberOfEmps
FROM dept d FULL JOIN emp e ON d.deptno = e.deptno
GROUP BY d.deptno, dname, loc
ORDER BY deptno ASC
</select>
<!-- int insertDept(DeptDTO dto); -->
<insert id="insertDept">
INSERT INTO dept(deptno, dname, loc)
VALUES(#{deptno}, #{dname}, #{loc})
<!-- #{} 해주면 자바 객체 안의 값을 가져오는 것 -->
<!-- -->
</insert>
<!-- 삭제하는 코드 -->
<delete id="deleteDept"> <!-- <delete>코드 안에 써주는 것</delete> -->
DELETE FROM dept
WHERE deptno = #{deptno}
</delete>
</mapper>
Restfull?
- 예전에는 특정 글을 보기 위해 /board/get.htm?seq=10을 줬다면 이제 /board/10+GET으로 가져올 수도 있다.
- 여기서 /board/delete.htm?seq=10 를 /board/10+DELETE 이런식으로 url 자체에 의미가 담기는게 바로 Restfull이다.
package org.doit.ik;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpVO;
import org.doit.ik.mapper.DeptEmpMapper;
import org.doit.ik.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class DeptEmpController {
@Setter(onMethod=@__({@Autowired}))
private DeptEmpMapper deptEmpMapper;
@GetMapping("/deptEmp")
public String depEmp(
@RequestParam( value = "deptno" , defaultValue = "10") int deptno
, Model model) {
model.addAttribute("deptList", this.deptEmpMapper.selectDept());
model.addAttribute("empList", this.deptEmpMapper.selectEmp(deptno));
return "deptEmpTest";
}
} // class
package org.doit.ik.mapper;
import java.util.List;
import org.doit.ik.domain.DeptDTO;
import org.doit.ik.domain.EmpDTO;
public interface DeptEmpMapper {
List<DeptDTO> selectDept();
List<EmpDTO> selectEmp(int deptno);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.DeptEmpMapper">
<!-- List<DeptDTO> selectDept(); -->
<select id="selectDept" resultType="org.doit.ik.domain.DeptDTO">
SELECT deptno, dname, loc
FROM dept
ORDER BY deptno ASC
</select>
<select id="selectEmp" resultType="org.doit.ik.domain.EmpDTO">
SELECT *
FROM emp
WHERE deptno = #{deptno}
ORDER BY ename ASC
</select>
</mapper>
/deptEmp/20
으로 가지고 가도록 한다.<%@ page 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">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Insert title here</title>
<link rel="shortcut icon" type="image/x-icon" href="../images/SiSt.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<link rel="stylesheet" href="/resources/cdn-main/example.css">
<script src="/resources/cdn-main/example.js"></script>
</head>
<body>
<div>
<select id="selDept" name="deptno">
<c:forEach items="${ deptList }" var="dto">
<option value= "${ dto.deptno }">${ dto.dname }</option>
</c:forEach>
</select>
<br>
<br>
<table>
<thead>
<tr>
<th><input type="checkbox" id="ckbAll" name="ckbAll">전체 선택</th>
<th>empno</th>
<th>ename</th>
<th>job</th>
<th>hiredate</th>
<th>mgr</th>
<th>sal</th>
<th>comm</th>
<th>deptno</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${ not empty empList }">
<c:forEach items="${ empList }" var="dto">
<tr>
<td><input type="checkbox" name="ckbEmp" data-empno="${ dto.empno }"></td>
<td>${ dto.empno }</td>
<td>${ dto.ename }</td>
<td>${ dto.job }</td>
<td>${ dto.hiredate }</td>
<td>${ dto.mgr }</td>
<td>${ dto.sal }</td>
<td>${ dto.comm }</td>
<td>${ dto.deptno }</td>
</tr>
</c:forEach>
</c:when>
<c:otherwise>
<tr>
<td colspan="9" style="text-align: center">employee does not exist.</td>
</tr>
</c:otherwise>
</c:choose>
</tbody>
<tfoot>
<tr>
<td colspan="9" style="text-align: center">
<button id="checkedEmpno">선택한 empno 확인</button>
</td>
</tr>
</tfoot>
</table>
</div>
<script>
$("#selDept").change(function (){
let deptno = $(this).val();
var params = "deptno="+deptno;
$.ajax({ //요청 시 /deptEmp/20 으로 가지고 가도록 한다.
url:`/deptEmp/\${deptno}`,
dataType:"json",
type:"GET",
//data:params,
cache:false,
success:function (data, textStatus, jqXHR){
console.log( data );
$("table tbody").empty();
if( data.length == 0 ){
let tr = `<tr>
<td colspan="9" style="text-align: center">employee does not exist.</td>
</tr>`;
$( tr ).appendTo("table tbody");
}
$( data ).each( function (i, elem){
let tr = `<tr>
<td><input type='checkbox' name='ckbEmp' data-empno='\${elem.empnp}'></td>
<td>\${elem.empno}</td>
<td>\${elem.ename}</td>
<td>\${elem.job}</td>
<td>\${elem.hiredate}</td>
<td>\${elem.mgr}</td>
<td>\${elem.sal}</td>
<td>\${elem.comm}</td>
<td>\${elem.deptno}</td>
</tr>`
$( tr ).appendTo("table tbody");
} );
},
error:function (){
alert('에러발생~~~');
}
});
});
</script>
</body>
</html>
AJAX로 hidden 태그 넘기기
- head 태그 내에 csrf 값을 받아둔다.
<script> // jquery ajax 처리시 csrf 값 저장. var csrfHeaderName = "${_csrf.headerName}"; var csrfTokenValue = "${_csrf.token}"; </script>
- js 파일 안에 beforesend 설정
beforeSend : function(xhr) { xhr.setRequestHeader(csrfHeaderName, csrfTokenValue); }