2023.12.07 - [백엔드(Back-End)/Spring Boot] - [sts4-Spring Boot] 16. REST 방식으로 댓글 CRUD
데이터베이스와 연동하여 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 폴더 생성
- 그치만 일단 사용하지 않음
템플릿의 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의 자바스크립트
- 댓글 리스트 조회
- 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>
'백엔드(Back-End) > Spring Boot' 카테고리의 다른 글
[STS4-Spring Boot] 오디오 스토어 제작 (0) | 2024.02.04 |
---|---|
[sts4-Spring Boot] 18. 게시글 조회수 기능 구현 (0) | 2023.12.07 |
[sts4-Spring Boot] 16. REST 방식으로 댓글 CRUD (0) | 2023.12.07 |
[sts4-Spring Boot] 15. REST API 사용해보기 (0) | 2023.12.07 |
[sts4-Spring Boot] 14. 이전 페이지 정보 유지하기 (0) | 2023.12.07 |