Spring XML/JSON 변환 처리

tabi·2023년 7월 3일
0

Spring

목록 보기
3/15
post-thumbnail

Spring에서 XML/JSON을 AJAX로 처리하고 변환하는 방법에 대해 알아보자.

  • 최근에는 서비스나 데이터를 HTTP 기반 API 형태로 제공하는 경우가 많다.
  • 이들 API의 특징 중 하나는 응답으로 XML이나 JSON 형식을 사용한다는 것이다.

1. 스프링의 XML/JSON 변환 처리

  • 스프링 MVC는 쉽게 XML과 JSON 형식을 처리할 수 있는 방법으로 @RequestBody와 @ResponseBody 애노테이션을 제공한다.

2. @RequestBody와 @ResponseBody, HttpMessageConverter

  • 웹 브라우저와 웹 서버 간 데이터를 주고 받을 때 사용되는 HTTP 프로토콜은 아래 그림과 같이 헤더와 몸체로 구성되어 있다.
  • 요청 몸체: 웹 브라우저에 전송할 데이터가 담김

2-1. @RequestBody

  • 요청 몸체를 자바 객체로 변환할 때 사용
  • 예시) 요청 파라미터 문자열을 String 자바 객체로 변환하거나, JSON 형식의 요청 몸체를 자바객체로 변환

2-2. @ResponseBody

  • 자바 객체를 JSON 형식이나 XML 형식의 문자열로 변환할 때 사용

3. 예시

3-1. 예시 1

  • AJAX를 활용해 부서번호를 추가, 삭제 하는 코드를 작성해보자.
  1. home.jsp
<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>
  1. HomeAjaxController.java
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와 함께 응답상태를 같이 보내고, 실패 시 응답 상태만 보내준다.
		} 
}
  1. dept.js
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
  };

})();
  1. org.doit.ik.domain에 EmpVO, DeptDTO 추가
  • EmpVO.java
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;
}
  • DeptDTO.java
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; //사원수

}
  1. MemberMapper 인터페이스에 코드 추가
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); // 삭제하고자 하는 부서번호를 준다.

}
  1. MemberMapper.xml 코드 추가
<?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이다.

3-2. 예시 2

  1. http://localhost/deptEmp 요청을 받는 컨트롤러 메서드 생성 DeptEmpController.java
  • DeptEmpController는 부서정보, 사원정보를 얻어오도록 해야 한다.
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
  1. 부서정보/사원정보를 가져오는 deptEmpMapper
  • deptEmpMapper.java 인터페이스
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);
}
  • deptEmpMapper.xml 구현
<?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>
  1. 위에서 결과물을 담고난 뒤 deptEmpTest.jsp 로 간다.(view)
  • 게시물 요청 시 /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); }

profile
개발 공부중

0개의 댓글