본문 바로가기
백엔드(Back-End)/Spring Boot

[sts4-Spring Boot] 17. REST 방식의 댓글 리스트, 작성, 삭제

by 기딩 2023. 12. 7.
728x90

2023.12.07 - [백엔드(Back-End)/Spring Boot] - [sts4-Spring Boot] 16. REST 방식으로 댓글 CRUD

 

[sts4-Spring Boot] 16. REST 방식으로 댓글 CRUD

게시글 CRUD를 해보았는데, 이번에는 REST 방식으로 댓글을 CRUD 해보자! 먼저 데이터베이스에서 댓글 테이블을 만들어야 한다. comment 테이블 - 댓글 테이블의 인덱스가 PK, 댓글과 연결되는 게시글

silvow94.tistory.com

 

 

데이터베이스와 연동하여 CRUD 기능과 테스트까지 완성했다.

이제 REST 방식으로 웹 페이지에 댓글 리스트를 보여주도록 하자.

 

REST API 설계 규칙

HTTP 요청 메서드

POST : 자원(resource) 생성
GET : 자원 조회
PUT : 자원 수정
PATCH : 자원 수정
DELETE : 자원 삭제

 

리소스(Resource)
서비스를 제공하는 시스템의 자원

REST API에서 URI는 명사를 사용해서 자원만 표현
(insert 같은 동사가 사용되면 REST 방식 X)
리소스의 행위는 무조건 HTTP 요청 메서드로 정의
유형 올바른 표현 잘못된 표현
게시글 등록 POST /boards POST /boards/insert
특정 게시글 조회 GET /boards/1 GET /boards/select/1
게시글 수정 (PUT or PATCH) /boards/1 (PUT or PATCH) /boards/update/1
게시글 삭제 DELETE /boards/1 DELETE /boards/1/delete

 

계층 관계

슬래시(/)로 표현
URI의 마지막에 슬래시 있으면, 다음 계층 있는 걸로 오해할 수 있으니 주의
GET /users/members/{gidding}
          -> 사용자 중 회원이면서 아이디가 "gidding"에 해당하는 자원 조회
GET /users/members/{gidding}/games
          -> 회원 중 아이디가 "gidding"인 회원이 가진 모든 게임 조회

 

가독성

URI는 알파벳 소문자로 작성
길어지면 하이픈( - ) 사용 o, 언더바( _ ) 사용 금지

 

컬렉션 Collection과 다큐먼트 Document

리소스 안에 포함
컬렉션 : 여러 객체가 모인 것
다큐먼트 : 하나의 객체
GET /libraries/center
          -> libraries 컬렉션과 center 다큐먼트로 조합된 리소스 (= 중앙도서관)
GET /libraries/center/books/{math}
          -> libraries, books 컬렉션과 center, math 다큐먼트 조합 리소스(= 중앙 도서관, 수학 도서)

 

Gson 라이브러리 추가

build.gradle의 dependencies

 

Open-source Java library

- JSON과 자바 객체의 직렬화 (자바 객체 -> JSON), 역직렬화(JOSN -> 자바 객체) 처리

 

 

 

 

CommentController 클래스

 

1. 댓글 리스트 출력하기

 

- @RequestController : 컨트롤러의 모든 메서드는 화면이 아닌, 리턴 타입에 해당하는 데이터 자체 리턴

- @PathVariable : URL 경로에서 변수 추출하여 메서드의 파라미터로 전달 (boardIdx와 매핑)

- @ModelAttribute : 전달받은 객체를 자동으로 뷰로 전달 (페이징 처리에 활용)

- 로직 : getCommentList 메서드 호출 결과를 commentList에 저장

 

 

2. 댓글 작성하기

 

- @RequestMapping : "/comments" URI에 대해 POST 메서드 처리 (REST URI 규칙을 위해 insert와 update 구분)

-   @RequestBody : 클라이언트는 게시글번호, 댓글내용, 댓글작성자를 JSON 문자열로 전송

   → 서버(컨트롤러)는 파라미터로 전달받음 

  → @RequestBody는 이를 객체로 변환

  → 객체 변환된 JSON은 CommentDTO 클래스의 객체인 params에 매핑

- JsonObject : 메서드 내부에서 받은 params 이용해, commentService로 댓글 등록하고 결과를 jsonObj에 담아 반환

 

 

3. 컨트롤러 처리 - 댓글 삭제하기 deleteComment

 

- @DeleteMapping ; HTTP 요청 메서드 중 DELETE 의미

- @PathVariable : {idx} 댓글 번호를 idx와 매핑

- 로직 : JSON 객체 생성 
→ 댓글서비스의 deleteComment() 실행 결과를 JSON 객체에 저장

→ 각 catch 문의 메시지를 JSON 객체에 저장

→ JSON 객체 리턴

package com.board.controller;
//import 생략
import com.board.domain.CommentDTO;
import com.board.service.CommentService;
import com.google.gson.JsonObject;


@RestController
public class CommentController {
	@Autowired
	private CommentService commentService;
	
	@RequestMapping(value = { "/comments", "/comments/{idx}" }, method = { RequestMethod.POST, RequestMethod.PATCH })
	public JsonObject registerComment(@PathVariable(value = "idx", required = false) Long idx, @RequestBody final CommentDTO params) {
		JsonObject jsonObj = new JsonObject();
		
		try {
			boolean isRegistered = commentService.registerComment(params);
			jsonObj.addProperty("result", isRegistered);
		} catch (DataAccessException e) {
			jsonObj.addProperty("message", "데이터베이스 처리 과정에 문제가 발생하였습니다.");
		} catch (Exception e) {
			jsonObj.addProperty("message", "시스템에 문제가 발생하였습니다.");
		}
		
		return jsonObj;
	}
	
	// idx 값을 주소로 가지고 들어감
	@GetMapping(value = "/comments/{boardIdx}")
	public List<CommentDTO> getCommentList(@PathVariable("boardIdx") Long boardIdx, @ModelAttribute("params") CommentDTO params) {
		List<CommentDTO> commentList = commentService.getCommentList(params);
		return commentList;
	}
	
	@DeleteMapping(value = "/comments/{idx}")
	public JsonObject deleteComment(@PathVariable("idx") final Long idx) {
		JsonObject jsonObj = new JsonObject();
		
		try {
			boolean isDeleted = commentService.deleteComment(idx);
			jsonObj.addProperty("result", isDeleted);
		} catch (DataAccessException e) {
			jsonObj.addProperty("message", "데이터베이스 처리 과정에 문제가 발생했습니다.");
		} catch (Exception e) {
			jsonObj.addProperty("message", "시스템에 문제가 발생했습니다.");
		}
		return jsonObj;
	}

}

 

 


moment js 라이브러리 참조 처리

 

리소스 디렉터리의 static/plugin 폴더에 common 폴더 생성

- 그치만 일단 사용하지 않음

moment.js
0.17MB

 

템플릿의 body.html

 

 


view.html 댓글 영역

 

- 기능 없이 하드코딩 했던 부분 제거

- 작성 버튼 누르면 insertComment 함수 실행

<th:block layout:fragment="add-content">
    <div class="box-content">
        <div class="card-content">
            <div class="clearfix">
                <h4 class="box-title pull-left">Comment</h4>
            </div>

            <form class="form-horizontal form-view">
                <div class="input-group margin-bottom-20">
                    <input type="text" id="content" class="form-control" />
                    <div class="input-group-btn">
                        <button type="button" class="btn waves-effect waves-light" th:onclick="insertComment([[ ${board.idx} ]])">
                        <i class="fa fa-commenting" aria-hidden="true"></i>
                        </button>
                    </div>
                </div>
                <ul class="notice-list"></ul>
            </form>
        </div>
    </div>
</th:block>

 

 

view.html의 자바스크립트

 

  1.  댓글 리스트 조회
  • jQuery ready(onload) 함수
    게시글 상세 페이지의 로딩 -> printCommentList 함수 호출
  • printComment List
    uri : 댓글컨트롤러의 get댓글리스트 메서드의 매핑 uri를 지정
  • $.get()
    기존의 Ajax 방식보다 더 간편하게 GET 방식 요청 처리
  • commentsHtml 
    <ul> 태그에 댓글 렌더링 할 HTML 담는 용도
  • $.each() 
    댓글 수만큼 반복문 실행하여 추가
    (백틱( ` ) 기호 사이에서 ${ }로 자바스크립트의 변수/함수 사용)
  • html() 함수 
    commentsHtml에 담긴 댓글 리스트(<li 태그)를 notice-list 클래스가 지정된 <ul> 태그에 렌더링 

댓글 목록

 

 

2. 댓글 등록/작성하기

  • content
    댓글 입력창
    Empty 상태에서 등록 버튼 클릭 -> setAttribute 함수로 <input> 태그의 placeholder 속성 지정
  • uri
    CommentController의 registerComment 메서드와 매핑된 "comments" URI
  • headers
    - Content-Type : application/json 으로 설정
    - X-HTTP-Method-Override : REST 방식의 HTTP 요청 메서드 중 put, patch, delete는 브라우저 따라 지원 안 하기도 
    So, 브라우저에서는 POST 방식으로 데이터 전송 -> 헤더에서 REST 방식의 HTTP 요청 메서드 전송
  • params
    파라미터로 전달받은 boardIdx(게시글 번호), content, writer를 JSON으로 저장 (아직 로그인 기능 x => "관리자" 처리)
  • ajax
    url : 호출할 url
    type : 요청 메서드
    headers : HTTP 요청 헤더
    dataType : 서버로부터 응답받을 데이터의 타입 (text, html, xml, json, script 등)
    data : 서버에 전송할 데이터 (JSON.stringify 함수로 JSON 객체를 JSON 문자열로 변환)
    success : ajax 요청에 성공 시 콜백 함수 (정상 등록되면, 댓글 출력하는 printCommentList () 실행)
    error : ajax 요청 실패 시 실행 콜백함수

댓글 작성 결과

 

3. 댓글 삭제하기

 

  • Confirm(선택) 함수
    다시 한 번 댓글의 삭제 여부 확인
  • uri, headers
    컨트롤러의 deleteComment 메서드의 매핑 URI와 HTTP 요청 헤더 저장
  • Ajax
    성공하면 삭제된 댓글 제외한 댓글 목록 출력

 

	$(function() {
        printCommentList();
    });
    
    function printCommentList() {
        var uri = /*[[ @{/comments/} + ${board.idx} ]]*/ null;

        $.get(uri, function(response) {
            if (isEmpty(response) == false) {
                var commentsHtml = "";
                $(response).each(function(idx, comment) {
                    commentsHtml += `
                        <li>
                            <span class="name">${comment.writer}</span>
                            <span class="desc">${comment.content}</span>
                            <button type="button" onclick="deleteComment(${comment.idx})" class="btn btn-xs btn-circle"><i class="fa fa-close" aria-hidden="true"></i></button>
                        </li>
                    `;
                });

                $(".notice-list").html(commentsHtml);
            }
        }, "json");
    }

    function insertComment(boardIdx) {
        var content = document.getElementById("content");

        if (isEmpty(content.value) == true) {
            content.setAttribute("placeholder", "댓글을 입력해 주세요.");
            content.focus();
            return false;
        }

        var uri = /*[[ @{/comments} ]]*/ null;
        var headers = {"Content-Type": "application/json", "X-HTTP-Method-Override": "POST"};
        var params = {"boardIdx": boardIdx, "content": content.value, "writer": "관리자"};
        $.ajax({
            url: uri,
            type: "POST",
            headers: headers,
            dataType: "json",
            data: JSON.stringify(params),
            success: function(response) {
                if (response.result == false) {
                    alert("댓글 등록에 실패하였습니다.");
                    return false;
                }

                printCommentList();
                content.value = "";
            },
            error: function(xhr, status, error) {
                alert("에러가 발생하였습니다.");
                return false;
            }
        });
    }

    function deleteComment(idx) {
        if (!confirm('댓글을 삭제하시겠어요?')) {
            return false;
        }

        var uri = /*[[ @{/comments/} ]]*/null;
            uri += idx;
        var headers = {"Content-Type": "application/json", "X-HTTP-Method-Override": "DELETE"};

        $.ajax({
            url: uri,
            type: "DELETE",
            headers: headers,
            dataType: "json",
            success: function(response) {
                if (response.result == false) {
                    alert("댓글 삭제에 실패하였습니다.");
                    return false;
                }

                printCommentList();
            },
            error: function(xhr, status, error) {
                alert("에러가 발생하였습니다.");
                return false;
            }
        });
    }
    /*]]>*/
</script>

 

728x90