본문 바로가기
Spring

스프링 정리3 - AJAX

by moca7 2024. 11. 15.

 

 

 

ㅁ ajax는 forward도 redirect도 아니다.

- ajax 요청은 forward도, redirect도 하면 안된다.

- 그냥 쿼리 실행 결과를 return 한다.

 

 

 

ㅁ @ResponseBody

- Spring에서는 기본적으로 return result;로 문자열 리턴하면 포워딩하려 한다.  그래서 404 에러가 발생할 수 있다.

- return 하는 문자열이 응답 뷰가 아니라 응답 데이터라는 것을 알려줘야 한다.

- 메소드 상단에 @ResponseBody 어노테이션을 붙이면 반환값이 응답 뷰가 아니라 응답 데이터다.

 

- 응답 뷰가 아닌 응답 데이터를 반환하는 경우, 돌려보내는 데이터에 대한 타입을 써줘야 한다.

단순한 숫자나 영문이면 타입을 쓰지 않아도 상관없지만 한글 데이터는 깨질 수 있어서 무조건 써야 한다.

 

- @GetMapping, @PostMapping의 produces 속성에 응답 데이터에 대한 MIME 타입을 쓸 수 있다.

@GetMapping(value="/detail1.do", produces="text/html; charset=utf-8")

@GetMapping(value="/detail3.do", produces="application/json") // 스프링이면 jackson 필요, 부트면 내장.

 

 

 

※ @ResponseBody

- 비동기식으로 데이터 응답시 필요한 어노테이션.

- 해당 어노테이션이 붙은 메소드에서의 반환 값은 응답 뷰가 아닌 어떤 data(text, json, xml 등)라는 걸 의미한다.

 

 

 

 

 

 

 

ㅁ 직렬화(Serialization)

 

 

(1)  JSON 직렬화
- JavaScript 객체를 JSON 형식으로 직렬화해 서버에 전송하는 방법.

- JSON은 AJAX에서 가장 일반적으로 사용되는 데이터 포맷이다.

 

const data = { id: "user1", pwd: "password123" };

$.ajax({
    url: "/your-endpoint",
    type: "POST",
    data: JSON.stringify(data),  // 객체를 JSON 형식으로 직렬화
    contentType: "application/json",
    success: function(response) {
        console.log("서버 응답:", response);
    }
});

 

 

 

 

(2) 폼 데이터 직렬화 (Form Data Serialization)
- HTML 폼 데이터를 URL 인코딩 방식으로 직렬화하여 서버로 전송할 때 주로 사용한다.

- jQuery의 serialize() 메서드를 사용하면 간편하게 데이터를 직렬화할 수 있다.

 
<form id="myForm">
    <input type="text" name="id" value="user1">
    <input type="password" name="pwd" value="password123">
</form>

 

const formData = $("#myForm").serialize();  // 폼 데이터를 직렬화

$.ajax({
    url: "/your-endpoint",
    type: "POST",
    data: formData,  // 직렬화된 데이터를 전송
    success: function(response) {
        console.log("서버 응답:", response);
    }
});

 

 

 

 

(3) FormData 객체 사용
- FormData 객체를 통해 파일 업로드를 포함한 폼 데이터를 직렬화하여 전송할 수 있다.

- 주로 파일을 포함한 데이터를 전송할 때 사용된다.

 

const formElement = document.getElementById("myForm");
const formData = new FormData(formElement);  // FormData 객체 생성

$.ajax({
    url: "/your-endpoint",
    type: "POST",
    data: formData,
    processData: false,  // jQuery가 데이터를 처리하지 않도록 설정
    contentType: false,  // 기본 Content-Type을 사용하지 않도록 설정
    success: function(response) {
        console.log("서버 응답:", response);
    }
});

 

 

 

 

 

ㅁ ajax

 

 

- $.ajax로 url을 요청한다.

$.ajax는 비동기적으로 서버에 요청을 보내는 jQuery 메서드다.

- $.ajax 안에 속성 하나하나 마다 콤마를 붙여줘야 한다.

 

- ajax data 속성을 쿼리스트링, 객체 방식으로 할 수 있었다.

- type에 get이나 post를 쓸 수 있었다. 생략시 get이다. 

 

- 주로 상단에는 요청과 관련된 코드를, 하단에는 응답과 관련된 코드를 작성한다.

success 이전은 요청 처리고, success부터는 응답 처리다.

 

 

 

- success에는 통신에 성공했을 때 실행할 함수를,

error에는 통신에 실패했을 때 실행할 함수를,

complete에는 성공 여부와 상관없이 항상 실행할 함수를 작성한다.

 

 

- AJAX 요청 시 서버에서 돌아오는 응답 데이터는 success 콜백 함수의 첫 번째 인자로 전달된다.

이 함수 내에서 응답 데이터를 처리하거나 화면에 반영할 수 있다.

이를 통해 서버와 클라이언트 간의 비동기 통신이 가능하다.

- success 속성의 function의 매개변수로 resData를 두면 응답 데이터를 받을 수 있다.

항상 console.log(resData)로 응답 데이터의 구조 파악을 먼저 한다.

 

 

 

 

 

ㅁ 예시

 

 

(i) manage1.jsp

 

 
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
   
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<script src="${contextPath}/resources/js/jquery-3.7.1.min.js"></script>

</head>
<body>
  <h3>회원관리 1번 페이지</h3>

  <form id="mem-form">
    번호 : <input type="text" name="userNo"> <br>
    아이디 : <input type="text" name="userId"> <br>
    비밀번호 : <input type="text" name="userPwd"> <br>
   
    <button type="button" onclick="fn_ajax1();">조회1(아이디, 비번으로 이름 조회)</button>
    <button type="button" onclick="fn_ajax2();">조회2(아이디, 비번으로 이름 조회)</button>
    <button type="button" onclick="fn_ajax3()">조회3(번호로 회원전체정보 조회)</button>
    <button type="button" onclick="fn_ajax4()">전체조회</button>
    <br>
   
    <button type="button" onclick="fn_etc1()">번외1(응답데이터가 다수일 경우)</button>
    <button type="button" onclick="fn_etc2()">번외2(요청시 전달값 다수를 Map으로 받아보기)</button>
  </form>
   
    <hr>
   
    <div id="result">
      결과가 보여지는 영역
    </div>

    <script>
        // (1) 조회1(아이디, 비번으로 이름 조회)
        function fn_ajax1() {

            let id = $("input[name=userId]").val();
            let pwd = $("input[name=userPwd]").val();

            $.ajax({
                // 요청
                url: "${contextPath}/member1/detail1.do",
                data: "id=" + id + "&pwd=" + pwd, // 쿼리스트링으로 전달값 작성
                type: "get",

                // 응답
                success: function(resData){
                    $("#result").text(resData);
                },
                error: function(){

                }
            })

        }
 
   
      // (2) 조회2(아이디, 비번으로 이름 조회)    
      function fn_ajax2() {
       
        $.ajax({
          url: "${contextPath}/member1/detail2.do",
          type: "get",
          data: $("#mem-form").serialize(), // form의 모든 입력요소들을 직렬화

          success: function(resData){
            $("#result").text(resData);
          },
          error: function(){
            console.log("조회2 버튼에 대한 ajax 통신 실패");
          }
         
        })
       
      }


   
      // (3) 회원번호로 회원의 정보 조회(1명 - dto)
      function fn_ajax3() {
       
        $.ajax({
          url: "${contextPath}/member1/detail3.do",
          type: "get",
          data: { no: $('input[name=userNo]').val() },

          success: function(resData){
          console.log(resData);

          let value = '<ul>'
                  + '<li>번호: ' + resData.userNo + '</li>'
                  + '<li>아이디: ' + resData.userId + '</li>'
                  + '<li>이름: ' + resData.userName + '</li>'
                + '</ul>';
               
          $('#result').html(value);    

          },
          error: function(){
            console.log("조회3 버튼에 대한 ajax 통신 실패");
          }
         
        })
       
      }


      // (4) 전체 회원 정보 조회(여러명 - list)
      function fn_ajax4() {
       
        $.ajax({
          url: "${contextPath}/member1/list.do",

          success: function(resData){
          console.log(resData); // [{}, {}, {}]

          let table = "<table border='1'>";

          for(let i=0; i<resData.length; i++) {
            table += '<tr>'
                  + '<td>' + resData[i].userNo + '</td>'
                  + '<td>' + resData[i].userId + '</td>'
                  + '<td>' + resData[i].userName + '</td>'
                + '</tr>';
          }

          table += "</table>";

          $('#result').html(table);  

          },
          error: function(){
            console.log("전체조회 버튼에 대한 ajax 통신 실패");
          }
         
        })
       
      }



      // (5) 번외1 - 응답 데이터가 다수인 경우
      function fn_etc1() {
       
        $.ajax({
          url: "${contextPath}/member1/etc1.do",

          success: function(resData){
          console.log(resData);
          console.log('no', resData.no);
          console.log('list', resData.list);
          console.log('m', resData.m);

          },
          error: function(){
            console.log("번외1 버튼에 대한 ajax 통신 실패");
          }
         
        })
       
      }


    // (6) 번외2 - 요청시 전달값 다수를 Map으로 받아보기
    function fn_etc2() {
   
      $.ajax({
        url: "${contextPath}/member1/etc2.do",
        type: 'post',
        data: JSON.stringify( { // json 객체(자바스크립트 객체) => json 문자열 변환
          no: 10,
          name: '김잔디',
          arr: ['홍길동', '김말똥', 20]
        }),
        contentType: 'application/json',

        success: function(resData){
          console.log(resData);

        },
        error: function(){
          console.log("번외2 버튼에 대한 ajax 통신 실패");
        }
       
      })
       
      }
    </script>
   
</body>
</html>
 

 

 

(1) 조회1(아이디, 비번으로 이름 조회)

- form 안에 버튼을 두지만 submit 시키지 않고 함수를 실행시킨다.

한 form에 여러개의 버튼을 뒀다.

 

- AJAX를 사용하면 폼 데이터를 직접 제출(submit)하지 않고 JavaScript로 처리하기 때문에, 폼 태그는 필수 요소가 아니다.

- 그런데도 form 태그를 사용한 이유

1. <form> 태그는 관련된 입력 필드들을 하나의 논리적 그룹으로 묶어주는 역할을 합니다.

2. serialize() 메서드를 사용해 폼 데이터를 자동으로 직렬화할 수 있습니다.

3. 미래 확장성 고려

 

 

 

(2) 조회2(아이디, 비번으로 이름 조회)

- form의 모든 입력요소들을 직렬화 (조회1 버튼과 같은 쿼리스트링으로 만들어짐)

- 해당 form요소.serialize() => "userNo=10&userId=user01&userPwd=pass01"

- form 요소에 serialize() 메서드를 실행하면 해당 폼 데이터가 쿼리스트링 형태로 변환되며, 이때 쿼리스트링에 포함되는 각 key값은 폼 필드의 name 속성값이 된다.

- form요소를 직렬화했다. 직접 쿼리스트링으로 쪼개도 되고 직렬화해도 된다.

 

 

(4) 전체 회원 정보 조회(여러명 - list)

- 요청시 전달값이 없어서 data 속성을 생략한다.

요청 방식도 get방식이라 type 속성을 생략한다. (생략하면 기본적으로 get 방식이다)

 

 

(6) 번외2 - 요청시 전달값 다수를 Map으로 받아보기

- 요청시 전달되는 값들을 map으로 바로 받아내는 방법.

- Jackson 라이브러리가 있어서 가능한 방법이다.

 

- 데이터 속성값을 json 방식으로 작성한다. 숫자 하나, 문자열 하나, 배열 하나를 JSON 형식으로 작성해 전송한다.

 

- 서버에서 각 파라미터를 따로따로 변수에 할당하지 않고 한 번에 Map으로 받을 수 있다.

그러려면 JavaScript에서 JSON 객체를 JSON 문자열 형식으로 변환해야 한다.

data 속성의 중괄호 블록에 해당하는 JavaScript 객체를 json 문자열로 바꿔야 한다.

 

- JSON.stringify()를 통해 JSON 객체를 JSON 문자열로 변환한 후 서버에 전달하면,

서버에서는 이를 JSON 형식으로 파싱하여 Map으로 받아 처리할 수 있다.

 

 

- contentType 속성은 항상 요청 시 전달되는 값의 타입을 지정하는 속성이다.

contentType: 'application/json'으로 설정하면 서버는 이 요청이 JSON 데이터임을 인식하고 JSON으로 처리할 준비를 한다.

 

 

 

 

 

(ii) MemberController1

 

 
package com.br.ajax.controller;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.br.ajax.dto.MemberDto;
import com.br.ajax.service.MemberService;

import lombok.RequiredArgsConstructor;

@RequestMapping("/member1")
@RequiredArgsConstructor
@Controller
public class MemberController1 {

    private final MemberService memService;
   
    private Logger logger = LoggerFactory.getLogger(MemberController1.class);
   

   
    /* 기존의 HttpServletResponse 객체 이용하는 방식

    @GetMapping("/detail1.do")
    public void memberDetail(String id, String pwd, HttpServletResponse response) throws IOException {
        logger.debug("request id: {}, pwd: {}", id, pwd);
        String result = memService.selectMemberByIdPwd(id, pwd);
       
        response.setContentType("text/html; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print(result);
    }
    */
   
   
    // Spring 방식
    @ResponseBody
    @GetMapping(value="/detail1.do", produces="text/html; charset=utf-8")
    public String memberDetail(String id, String pwd) {

        logger.debug("request id: {}, pwd: {}", id, pwd);

        String result = memService.selectMemberByIdPwd(id, pwd);
       
        return result;
    }
   
   
    @ResponseBody
    @GetMapping(value="/detail2.do", produces="text/html; charset=utf-8")
    public String memberDetail2(String userId, String userPwd) {
        return memService.selectMemberByIdPwd(userId, userPwd); // 문자열 바로 반환
    }
   
       
    @ResponseBody
    @GetMapping(value="/detail3.do", produces="application/json")
    public MemberDto memberDetail3(@RequestParam(value="no", defaultValue="1") int no) { // 요청시 전달값 받기

        MemberDto mem = memService.selectMemberByNo(no);
        return mem; // {필드명:필드값, 필드명:필드값, ...}
    }
   

    @ResponseBody
    @GetMapping(value="list.do", produces="application/json")
    public List<MemberDto> memberList() {
        List<MemberDto> list = memService.selectMemberList();
        return list; // [{}, {}, {}, ...]
    }


    @ResponseBody
    @GetMapping(value="etc1.do", produces="application/json")
    public Map<String, Object> responseMapTest() {
       
        // 만일 응답할 데이터로 숫자, List, Dto가 있다는 가정
        Map<String, Object> map = new HashMap<>();
        map.put("no", 1);
        map.put("list", memService.selectMemberList());
        map.put("m", memService.selectMemberByNo(2));

        return map;

        /*
            {
                no: 1,
                list: [{}, {}, {}],
                m: {}
            }
        */

    }



    @ResponseBody
    @PostMapping(value="/etc2.do", produces="application/json")
    public void requestBodyTest(@RequestBody Map<String, Object> map) {
        logger.debug("map: {}", map);
        logger.debug("map>no: {}", map.get("no"));
        logger.debug("map>name: {}", map.get("name"));
        logger.debug("map>arr: {}", map.get("arr")); // list 객체가 나온다. 배열 형태로 출력된다.
    }


}
 

 

- requestBodyTest()가 String 반환할거면 produces는 없어야 함.

 

 

 

(3) 회원번호로 회원의 정보 조회(1명 - dto)

 

- 응답 데이터가 배열인지 객체인지 보기 위해 console.log로 찍어본다.

 

- 자바 객체 (Dto, List, Map, 배열 등)를 응답 데이터로 반환하려면 json 데이터로 변환해서 응답해야 된다.

스프링에서도 gson 라이브러리를 연동하면 기존 방식으로도 가능하다.

그런데 그 때는 response 객체도 제시해야 한다. 귀찮다.

- 자동으로 json으로 변환처리해주는 jackson 라이브러리를 등록하면 바로 반환만 하면 된다.

 

- 스프링에서는 기존 방식처럼 GSON 라이브러리(자바 객체와 JSON 간의 변환 - 직렬화 및 역직렬화)를 사용하지 않고도 자바 객체를 응답할 수 있다.

- pom.xml에 jackson 라이브러리 등록 후  @ResponseBody 어노테이션을 추가, 메소드 반환형을 변경, MIME 타입 지정(produces="application/json")을 하면 된다.

 

 

- 스프링에서 pom.xml에 jackson 라이브러리를 추가하면 그냥 DTO를 리턴만 해도 json으로 변환되서 반환된다.

- 스프링 부트는 기본적으로 Jackson 라이브러리를 포함하고 있으므로, 별도의 설정 없이도 DTO를 JSON 응답으로 반환할 수 있다.

 

 

 

 

(5) 번외1 - 응답 데이터가 다수인 경우

 

- 응답 데이터로 list도 돌려주고 dto도 돌려줘야 하는데 리턴은 하나밖에 안된다.

이럴때는 응답데이터들을 담을 dto를 만들든지 Map 객체를 사용해서 반환한다.


- 응답데이터가 int형 변수, List, DTO 3개다.

- 한 기능(요청)이었으면 애초에 서비스에서 돌아올 때부터 map으로 돌아왔을 것이다.

 

 

 

 

 

 

 

ㅁ @RestController

 

- 현재 모든 메소드들이 응답 뷰가 아닌 응답 데이터를 돌려주는 메소드다.

모든 메소드에 @ResponseBody가 붙어있다.

 

- 페이지 이동(포워딩, 리다이렉트) 없이 컨트롤러가 응답 데이터를 돌려줄 목적으로만 제작되어 있으면

클래스 위에 @Controller 대신 @RestController 어노테이션을 작성할 수 있다.

 

- 이러면 각 메소드들 위에 @ResponseBody 어노테이션을 쓰지 않아도 된다. 

안의 메소드들이 전부 @ResponseBody가 붙은 채로 동작한다.

그냥 응답해도 응답 뷰가 아니라 응답 데이터를 리턴하는 컨트롤러로 인식된다.

 

 

'Spring' 카테고리의 다른 글

스프링 정리4 - 첨부파일  (0) 2024.11.25
스프링 정리2 - 로깅  (1) 2024.11.14
스프링 정리 - MVC  (0) 2024.11.13
디비  (1) 2024.11.06
[스프링부트] 10. 트랜잭션 처리  (0) 2024.11.06