본문 바로가기
Spring Boot

스프링 부트(Spring Boot) 게시판 - REST API 방식으로 댓글 등록 기능 구현하기 [Thymeleaf, MariaDB, IntelliJ, Gradle, MyBatis]

by 도뎡 2023. 4. 18.
반응형
  • 본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다.
  • DBMS 툴은 DBeaver를 이용하며, DB는 MariaDB를 이용합니다. (MariaDB 설치하기)
  • 화면 처리는 HTML5 기반의 자바 템플릿 엔진인 타임리프(Thymeleaf)를 사용합니다.

이전 글에서는 댓글 데이터를 관리할 tb_comment 테이블을 생성하고, 매퍼(Mapper)와 서비스(Service) 영역까지 댓글 CRUD 기능을 구현해 보았습니다.

이번에는 댓글 등록 기능을 구현해 볼 건데요. @RestController 어노테이션과, jQuery의 Ajax를 이용해서 화면의 움직임(이동 또는 새로고침) 없이 데이터를 주고받는 비동기 처리에 대해 알아보겠습니다.

 

1. REST API 설계 규칙 알아보기

우선, REST 방식에서 사용할 수 있는 대표적인 HTTP 요청 메서드를 알아보겠습니다.

HTTP 요청 메서드 역할
POST 자원(Resource)을 생성합니다.
GET 자원(Resource)을 조회합니다.
PUT 자원(Resource)을 수정합니다.
PATCH 자원(Resource)을 수정합니다.
DELETE 자원(Resource)을 삭제합니다.

 

리소스(Resource)

리소스는 서비스를 제공하는 시스템의 자원을 의미합니다. REST API에서 URI는 다음의 표와 같이 명사를 사용해서 자원만을 표현해야 합니다. insert, update와 같은 동사가 사용되면 REST 방식이라고 볼 수 없으며, 리소스의 행위는 무조건 HTTP 요청 메서드로 정의합니다.

유형 메서드 올바른 표현 잘못된 표현
댓글 등록 POST /comments /comments/insert
댓글 상세정보 조회 GET /comments/1 /comments/select/1
댓글 수정 PUT or PATCH /comments/1 /comments/update/1
댓글 삭제 DELETE /comments/1 /comments/delete/1
댓글 리스트 조회 GET /comments /comments/

 

계층 관계

URI에 슬래시( / )를 사용해서 계층 관계를 나타냅니다. URI의 끝에 슬래시( / )가 포함되면 다음 계층이 존재하는 것으로 오해할 수 있기 때문에 URI는 절대로 슬래시( / )로 끝나서는 안되며, 리소스는 다음과 같이 표현할 수 있습니다.

자원(Resource) 메서드 설명
/members/{gildong} GET 아이디가 "gildong"인 회원을 조회
/members/{gildong}/games GET 아이디가 "gildong"인 회원이 가지고 있는 모든 게임을 조회
/members/{gildong}/games POST 아이디가 "gildong"인 회원의 게임을 추가(등록)
/members/{gildong} PATCH 아이디가 "gildong"인 회원의 정보를 수정
/members/{gildong}/games/{3} DELETE 아이디가 "gildong"인 회원의 게임 중, key(고유 값)가 3번인 게임을 삭제
/members/{gildong}/games/ GET URI가 슬래시( / )로 끝나기 때문에 잘못된 표현

 

가독성

URI는 알파벳 소문자만으로 작성되어야 합니다. 물론 대문자를 사용해도 URI 호출에는 영향을 끼치지 않지만, 대문자가 포함되면 URI를 기억하기 어렵고, 잘못된 URI를 호출할 가능성이 높습니다. 혹시라도 URI가 길어지는 상황이라면 하이픈( - )을 사용할 수 있으며, 언더바( _ )는 절대로 사용해서는 안됩니다.

 

컬렉션(Collection)과 다큐먼트(Document)

컬렉션과 다큐먼트는 리소스 안에 포함됩니다. 컬렉션은 여러 객체가 모인 것(복수), 다큐먼트는 하나의 객체(단수)로 생각할 수 있으며, 컬렉션과 다큐먼트는 다음과 같이 표현할 수 있습니다.

자원(Resource) 메서드 유형 설명
/libraries/bukgu GET Document "libraries" 컬렉션과 "bukgu" 다큐먼트로 조합된 리소스로, 도서관 중 북구 도서관을 조회
/libraries/bukgu/books GET Collection "libraries", "books" 컬렉션과 "bukgu" 다큐먼트로 조합된 리소스로, 도서관 중 북구 도서관의 모든 서적을 조회
bukgu/books/역행자 GET Document "books" 컬렉션과 "bukgu", "역행자" 다큐먼트로 조합된 리소스로, 북구 도서관의 모든 서적 중 역행자를 조회

 

2. 댓글 컨트롤러(Controller) 클래스 생성하기

REST API 알아보기에서 말씀드렸듯이, REST Controller는 화면(HTML)이 아닌 데이터 자체를 리턴합니다. 댓글 데이터의 CRUD는 전부 게시글 상세 페이지에서 이루어지기 때문에 화면을 따로 구성할 필요 없이 데이터만 주고받으면 됩니다.

java 디렉터리에 다음의 클래스를 생성하고, 소스 코드를 작성해 주세요.

package com.study.domain.comment;

import lombok.RequiredArgsConstructor;
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;

@RestController
@RequiredArgsConstructor
public class CommentApiController {

    private final CommentService commentService;

    // 신규 댓글 생성
    @PostMapping("/posts/{postId}/comments")
    public CommentResponse saveComment(@PathVariable final Long postId, @RequestBody final CommentRequest params) {
        Long id = commentService.saveComment(params);
        return commentService.findCommentById(id);
    }

}

 

구성요소 설명
@RestController 컨트롤러의 모든 메서드에 @ResponseBody가 적용되며, 응답으로 페이지(HTML)가 아닌 리턴 타입에 해당하는 데이터(객체) 자체를 리턴합니다.
@PathVariable REST API에서 리소스를 표현하는 데 사용됩니다. 해당 어노테이션을 이용하면 URI에서 템플릿 형태로 파라미터(변수)를 전달받을 수 있는데요. saveComment( )는 URI에서 게시글 번호(postId)를 수집하며, postId는 @PathVariable로 선언한 Long 타입의 postId에 매핑(바인딩)됩니다.
@RequestBody 일반적으로 데이터를 생성(POST) 또는 수정(PUT or PATCH)하는 경우에 사용됩니다. 저장할 데이터를 JSON 포맷으로 해서 서버에 요청을 보내면, key-value 구조로 이루어진 각각의 데이터가 Java 객체와 매핑(바인딩)됩니다.

이는 자바스크립트 코드를 작성한 후 다시 설명드리겠습니다.
saveComment( ) 새로운 댓글을 생성한 후 생성된 댓글의 상세정보(응답 객체)를 리턴합니다. 마찬가지로 자바스크립트 코드를 작성한 후 다시 설명드리겠습니다.

 

3. 상세 페이지 - 댓글 작성 영역 추가하기

댓글 CRUD는 모두 상세 페이지에서 처리되기 때문에 view.html에 댓글 영역을 추가해야 합니다. 우선 view.html의 콘텐츠(content) 영역에서 수정/삭제/뒤로 버튼을 감싸고 있는 <p></p> 태그 뒤에 다음의 코드를 추가해 주세요.

    <!--/* 댓글 작성 */-->
    <div class="cm_write">
        <fieldset>
            <legend class="skipinfo">댓글 입력</legend>
            <div class="cm_input">
                <p><textarea id="content" name="content" onkeyup="countingLength(this);" cols="90" rows="4" placeholder="댓글을 입력해 주세요."></textarea></p>
                <span><button type="button" class="btns" onclick="saveComment();">등 록</button> <i id="counter">0/300자</i></span>
            </div>
        </fieldset>
    </div>

 

4. 상세 페이지 - JS 함수 작성하기

다음으로 댓글 내용과 등록 버튼에 선언된 이벤트를 처리할 함수를 작성해 보겠습니다. view.html의 JS(script) 영역에 다음의 함수를 추가해 주세요.

    // 댓글 길이 카운팅
    function countingLength(content) {
        if (content.value.length > 300) {
            alert('댓글을 300자 이하로 입력해 주세요.');
            content.value = content.value.substring(0, 300);
            content.focus();
        }
        document.getElementById('counter').innerText = content.value.length + '/300자';
    }


    // 댓글 저장
    function saveComment() {

        const content = document.getElementById('content');
        isValid(content, '댓글');

        const postId = [[ ${post.id} ]];
        const params = {
            postId : postId,
            content : content.value,
            writer : '홍길동'
        }

        $.ajax({
            url : `/posts/${postId}/comments`,
            type : 'post',
            contentType : 'application/json; charset=utf-8',
            dataType : 'json',
            data : JSON.stringify(params),
            async : false,
            success : function (response) {
                console.log(response);
            },
            error : function (request, status, error) {
                console.log(error)
            }
        })
    }

 

함수 설명
countingLength( ) 댓글에 입력된 글자 수를 카운팅합니다. 글자 수가 300자를 초과하는 경우, 입력된 전체 내용에서 1 ~ 300번째까지의 문자열을 추출해서 댓글 내용을 다시 세팅합니다.
saveComment( ) DB에 댓글을 저장합니다. 댓글 내용의 유효성을 검사한 후 게시글 번호(id), 댓글 내용(content), 작성자(writer) 정보를 서버로 전달합니다.

 

ajax 구성요소 설명
url HTTP 요청 URI를 의미합니다.
type HTTP 요청 Method를 의미합니다.
contentType 서버로 전송할 데이터 형식을 의미합니다. 우리는 서버로 데이터를 전송할 때 JSON 포맷으로 보내기 때문에 "application/json"을 선언했으며, 해당 옵션의 기본 값은 "application/x-www-form-urlencoded"입니다.
dataType 서버에서 응답으로 내려받을 데이터 형식(text, html, xml, json 등)을 의미합니다. 
data 서버로 전송할 데이터(파라미터)를 의미합니다. 우리는 생성할 댓글 정보를 params라는 이름의 객체(JSON)에 담아 서버로 전송하는데요. JSON.stringify( ) 함수로 params 객체를 JSON 문자열로 변환해서 전송합니다.

자세한 내용은 테스트 과정에서 설명드리도록 하겠습니다.
async Ajax는 비동기 처리 방식이기 때문에 로직의 실행 순서를 보장할 수 없습니다. 해당 옵션의 기본 값은 "true"인데요. async를 "false"로 설정해주면  Ajax 내의 로직을 순차적으로 실행할 수 있습니다.

쉽게 말해, true인 경우에는 서버에서 응답이 내려오지 않아도 다음 로직이 실행되고, false인 경우에는 서버에서 응답이 내려온 후에 다음 로직이 실행됩니다.
success Ajax 요청에 성공했을 때 실행되는 콜백 함수입니다. 댓글 생성이 완료되면 생성된 댓글 정보를 콘솔에 출력합니다.
error Ajax 요청에 실패했을 때 실행되는 콜백 함수입니다. 서버에서 에러가 발생하면 해당 함수 안의 로직이 실행됩니다.

 

5. 댓글 등록 테스트 해보기

리스트 1페이지 최상단에 위치한 32,000번 게시글에 댓글을 등록해 보겠습니다.

게시글 상세 페이지 - (댓글 입력 및 등록)

 

다음은 view.html의 saveComment( )의 params와 JSON.stringify(params)를 브라우저 개발자 도구(F12)의 콘솔(Console)에 출력해 본 결과입니다. 앞에서 말씀드렸듯이, 서버로 데이터를 전송하는 시점에 JSON.stringify( )를 이용해서 객체(params)를 JSON 문자열로 변경합니다.

params, JSON.stringify(params) 출력 결과

 

서버에 요청을 보내면, @PathVariable 파라미터인 postId는 게시글의 PK(32356)를 수집하고, JSON 문자열로 넘어온 댓글 정보는 @RequestBody에 의해 CommentRequest 클래스의 객체인 params에 매핑됩니다. 이때 JSON 문자열의 각 key와 클래스의 멤버 변수명은 동일해야 합니다.

CommentApiController - saveComment( ) 디버깅 모드

 

서버의 모든 로직이 실행되면 Ajax의 success( ) 함수가 실행되며, 생성된 게시글 정보가 브라우저 콘솔에 출력됩니다.

생성된 게시글 정보 (브라우저 콘솔)

 

테이블에도 정상적으로 저장되었습니다.

tb_comment 테이블 SELECT 결과

 

마치며

이번에는 댓글 등록 기능을 구현해 보았습니다. 비동기 처리의 포인트는 서버(Server)와 클라이언트(Client)가 화면의 이동 없이 데이터를 주고받는 겁니다. 아직 Ajax가 생소하신 분들도 계실 텐데요. 비동기 방식으로 데이터를 주고받는 연습을 반복적으로 하시다 보면 금방 익숙해지실 수 있습니다.

다음 글에서는 게시글 상세 페이지에 댓글 목록을 출력하는 기능을 구현해 보겠습니다.

오늘도 방문해 주신 여러분께 진심으로 감사드립니다. 좋은 하루 보내세요 :)

Board.zip
0.86MB

반응형

댓글