일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 코딩테스트
- 리눅스
- Elasticsearch
- Java
- Linux
- 프로그래머스
- 개발자
- IT
- 엘라스틱서치
- 백엔드
- Python
- 파이썬
- Kakao
- programmers
- 캐시
- 알고리즘
- Spring
- DPDK
- 자바
- 스프링
- 스프링부트
- 프로그래머스 #카카오 #IT #코딩테스트
- springboot
- 쿠버네티스
- 도커
- 카카오
- 네트워크
- C
- 운영체제
- docker
- Today
- Total
저고데
[Spring Boot 실전]9. 댓글을 불러오고 삭제하는 기능을 만들어보자 본문
이전 시간에는 Comment 테이블을 만들고 User, Article 테이블과 조인하여 게시글마다 댓글을 저장하는 기능을 만들었었다.
이번 시간에는 게시글의 댓글들을 모두 불러와서 뷰로 출력하고 해당 댓글을 작성자일 경우에만 댓글을 삭제하는 기능을 추가해보도록 하자.
댓글 출력하기
@Override
public Optional<Comment> findById(Long id) {
Comment comment = em.find(Comment.class, id);
return Optional.ofNullable(comment);
}
@Override
public void delete(Long id) {
Comment comment = findById(id).orElseThrow(() -> new EntityNotFoundException("Comment not found"));
em.remove(comment);
}
우선 다음과 같이 CommentRepository에 다음과 같은 코드를 추가하여, 댓글 id를 통해 해당 댓글을 찾아내고 삭제하는 메소드를 추가한다.
<div th:if="${comments.isPresent()}">
<h3>댓글 목록</h3>
<table>
<thead>
<tr>
<th>작성자</th>
<th>내용</th>
</tr>
</thead>
<tbody>
<tr th:each="comment : ${comments.get()}">
<td th:text="${comment.user.nickname}"></td>
<td th:text="${comment.content}"></td>
<td th:if="${user != null && user.id == comment.user.id}">
<a th:href="@{/comments/update/{id}(id=${comment.id})}">수정</a>
<a th:href="@{'/comments/delete/{id}(id=${comment.id})}" onclick="confirmDelete('/comments/delete/{id}(id=${comment.id})">삭제</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
<script>
function confirmDelete(deleteUrl) {
if (confirm("댓글을 삭제하시겠습니까?")) {
window.location.href = deleteUrl;
}
}
</script>
그리고 ArticleDetail.html 화면에 다음과 같은 코드를 추가한다.
해당 코드는 저장된 세션의 사용자가 댓글 작성자와 동일할 경우에만 수정, 삭제를 클릭할 수 있게 작동한다.
그리고 수정과 삭제는 모두 댓글의 아이디를 Controller에게 get 요청을 한다.
하지만 ! 여기서 문제점이 생겼다.
위의 Comment 테이블에서 보이는 것처럼 1번 게시글의 댓글은 3개이지만 단 하나의 댓글만이 보인다는 점이었다.
thymyleaf의 문법은 이상이 없었고 Repository 코드에 문제가 있다는 것을 확인하였다.
@Override
public Optional<Comment> findByArticleId(Long articleId) {
List<Comment> result = em.createQuery("select c from Comment c where c.article.id=:articleId", Comment.class)
.setParameter("articleId", articleId)
.getResultList();
return result.stream().findAny();
}
코드의 마지막 줄 리턴 부분에 findAny()가 문제였다.
findAny는 해당 List에서 임의의 단 하나의 값을 반환하기 때문에 여러 개의 값들이 있다고 해도 무조건 하나만을 반환하기 때문에 이와 같은 문제가 발생했던 것이다.
따라서 리턴 부분에 findAny 대신 모든 값들을 반환할 수 있는 메소드로 교체하기로 했다.
하지만 ... ! 그런 메소드는 없었다.
왜 그런지 알아보니 findByArticleId의 반환 타입인 Optional에서 답을 구할 수 있었다.
만약에 값이 없다면 우리는 null을 반환한다.
그러나 null을 반환하는 것 자체가 위험하기 때문에 이를 방지하고자 탄생한 것이 Optional이다. (더 자세한 내용은 따로 블로그에 작성하도록 하겠습니다.)
따라서 Optional은 주로 값이 없을 수 있는 상황에서 null을 대신하여 사용하는데, 이는 주로 단일 객체에 적용된다.
@Override
public Optional<List<Comment>> findByArticleId(Long articleId) {
List<Comment> result = em.createQuery("select c from Comment c where c.article.id=:articleId", Comment.class)
.setParameter("articleId", articleId)
.getResultList();
return Optional.of(result);
}
그렇기 때문에 타입을 Optional<Comment>에서 Optional<List<Comment>>로 수정하여 문제를 해결할 수 있었다. (List 그 자체를 하나만 반환하게끔 하여 !)
그 결과 댓글들이 잘 보이는 것을 확인할 수 있었다.
댓글 삭제 기능
다음으로 댓글 삭제 기능을 만들어보자.
앞서 articleDetail.html의 get 요청에 따라서 Comment Controller에 다음과 같은 메소드를 작성한다.
@GetMapping("/comments/delete/{id}")
public String deleteForm(@PathVariable Long id) {
commentService.delete(id);
return "redirect:/articles/";
}
물론 해당 메소드는 작동이 잘 이루어졌다.
하지만, 댓글을 삭제하고 나면 기존에 보고 있던 게시글 창으로 이동하지 않고 글 목록 화면으로 이동하는 문제점이 발생하였다.
따라서 get 요청 시, 댓글 id 뿐만 아니라 게시글의 id도 같이 전달하여 redirect를 올바르게 해주기로 했다.
<td th:if="${user != null && user.id == comment.user.id}">
<a th:href="@{/comments/update/{id}(id=${comment.id})}">수정</a>
<a th:href="@{'/comments/delete/' + ${article.id} + '-' + ${comment.id}}" onclick="confirmDelete('@{/comments/delete/ + ${article.id} + ${comment.id}}')">삭제</a>
</td>
@GetMapping("/comments/delete/{id}-{commentId}")
public String deleteForm(@PathVariable Long id, @PathVariable Long commentId) {
commentService.delete(commentId);
return "redirect:/articles/"+id;
}
댓글 삭제 후, 기존에 보고 있던 화면으로 잘 이동하는 것을 확인할 수 있었다.
그러나 "댓글을 삭제하시겠습니까?" 알림이 뜨지 않고 바로 삭제되는 문제가 발생하였다.
confirmDelete 함수의 호출 부분에서 문자열이 제대로 처리되지 않는 문제였다.
자바 스크립트의 함수 안에서 문자열을 제대로 처리하기 위해서는 더블 쿼테이션 마크로 감싸야 한다는 글을 보고 다음과 같이 수정하였다.
<td th:if="${user != null && user.id == comment.user.id}">
<a th:href="@{/comments/update/{id}(id=${comment.id})}">수정</a>
<a th:href="@{'/comments/delete/' + ${article.id} + '-' + ${comment.id}}" onclick="confirmDelete('@{"/comments/delete/" + ${article.id} + "-" + ${comment.id}}')">삭제</a>
</td>
이제 문제점이 해결되고 잘 작동하는 모습이었다.
이번 시간에는 이렇게 댓글을 불러오고 삭제하는 기능을 만들어보았다.
필자의 경우에는 완벽한 기능을 가진 게시판을 설계하고 개발을 하는 것이 아니라, 필요한 기능을 설계하고 개발하고 또 필요한 부분이 보이면 해당 부분을 설계하고 개발하는 식으로 계속해서 살을 덧대는 식으로 개발을 진행해오고 있다. (원래 계획은 CRUD 기능만을 가진 게시판을 만드는 간단한 프로젝트를 진행하는 것이였으나. 욕심이 계속 생기네요 허허)
그러면서 느낀 점은 왜 스프링 부트 설계에 있어서 Controller, Service, Repository로 굳이 나누어서 설계를 하는 지를 알 수가 있었다.
무엇보다도 유지보수가 용의하다는 것이 가장 크지 않나 싶다.
허허, 아무튼 다음 시간에는 댓글을 수정하고 각 게시글마다 추천을 할 수 있는 기능을 추가해보도록 하겠다.
'Spring Boot 실전' 카테고리의 다른 글
[Spring Boot 실전] 11. 게시판 파일 업로드 구현하기 (1) | 2024.02.02 |
---|---|
[Spring Boot 실전]10. 게시글 추천 기능을 만들어보자 (2) | 2024.01.22 |
[Spring Boot 실전]8. 두 가지 테이블 조인하기 (0) | 2024.01.20 |
[Spring Boot 실전]7. ajax를 사용하여 아이디 중복 방지 기능을 만들어보자 (0) | 2024.01.18 |
[Spring Boot 실전]6. 회원가입 기능을 추가해보자 (0) | 2024.01.18 |