사이먼's 코딩노트
[SpringBoot] 질문/답변 추천 본문
[질문 추천 기능 구현하기]
- 보통 SNS에서 마음에 드는 게시물이나 컨텐츠에 '좋아요'와 같은 표시를 남기기 마련이다.
- SBB 프로그램에도 '좋아요' 버튼을 통해 질문이나 답변을 본 다른 사용자들이 반응을 남길 수 있도록 추천 기능을 구현해봅시다.
- 질문에서 추천 기능을 구현하려면 질문을 추천한 사용자가 DB에 저장될 수 있도록 관련된 속성을 Question 엔티티에 추가해야 한다.
- Question 엔티티에 추천인을 저장하기 위한 voters라는 이름의 속성을 추가해봅시다.
@ManyToMany
Set<SiteUser> voters = new LinkedHashSet<>();
public void addVoter(SiteUser voter) {
voters.add(voter);
}
- @ManyToMany 어노테이션을 사용한 이유는 한 명의 사용자가 여러개의 질문에 대해서 추천을 할 수도 있고, 하나의 질문에 대해서 여러 사용자가 추천을 할 수 있기 때문에 다대다 관계가 성립된다.
- Set은 List 형식과 동일한데, 차이점이 있다면 중복 금지의 의미가 포함되어있다.
- addVoter() 메서드는 생성된 voters에 추천인을 저장하는 역할을 담당한다.
- Question.java 클래스에 위와 같은 코드를 작성하고 DB를 확인하면 question_voters라는 테이블이 새로 생성되는 것을 확인할 수 있다.
[question_detail.html 템플릿 수정]
- 질문 엔티티에 voters 속성을 추가했다면 질문 추천 기능을 구현봅시다.
- 질문에 좋아요라는 버튼의 위치는 질문 상세 화면에 넣는 것이 적절하기 때문에 아래와 같이 question_detail.html에 코드를 추가 작성해봅시다.
<div class="my-3">
<a onclick="return confirm('추천하시겠습니까?');" class="btn btn-sm btn-outline-secondary"
th:href="@{|/question/vote/${question.id}|}">
좋아요
<i class="fa-regular fa-thumbs-up"></i>
<span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voters)}"></span>
</a>
<a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
sec:authorize="isAuthenticated()"
th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
th:text="수정"></a>
<a onclick="return confirm('정말로 삭제하시겠습니까?');" th:href="@{|/question/delete/${question.id}|}"
class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
th:text="삭제"></a>
</div>
- '좋아요' 버튼의 위치는 수정, 삭제 버튼을 기준으로 가장 왼쪽에 배치하였다.
- lists.size() 메서드에 question.voters를 인자로 받아 좋아요의 개수도 함께 보이도록 하였다.
- '좋아요' 버튼을 클릭하면 GET 방식으로 /question/vote/{id} URL을 요청하게 된다.
- 추가로 onclick="return confirm('추천하시겠습니까?')" 를 속성에 추가하여 '좋아요' 버튼을 클릭했을 때 한 번 더 확인할 수 있도록 alert 알림창이 나오도록 하였다.
[Service 수정]
- 다음은 추천인을 저장할 수 있도록 추천 기능을 아래와 같이 QuestionService.java 클래스에 코드를 추가해봅시다.
public void vote(Question question, SiteUser voter) {
question.addVoter(voter);
questionRepository.save(question);
}
- 위와 같이 로그인한 사용자를 질문 엔티티에 추천인으로 저장하기 위해 vote() 메서드를 추가하였다.
[Controller 수정]
- '좋아요' 버튼을 눌렀을 때 GET 방식으로 호출되는 /question/vote/{id} URL을 처리하기 위해 아래와 같이 QuestionController.java에 코드를 추가해봅시다.
@PreAuthorize("isAuthenticated()")
@GetMapping("/vote/{id}")
public String questionVote(Principal principal, @PathVariable("id") Integer id) {
Question question = this.questionService.getQuestion(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
this.questionService.vote(question, siteUser);
return "redirect:/question/detail/%s".formatted(id);
}
- 다른 기능과 마찬가지로 추천 기능도 로그인한 사람만 사용할 수 있도록 @PreAuthorize("isAuthenticated()") 어노테이션을 적용하였다.
- QuestionService.java에서 작성한 vote() 메서드를 호출하여 사용자와 추천인으로 저장했다.
- 에러가 없다면 추천인을 저장한 후 질문 상세 페이지로 리다이렉트한다.
[답변 추천 기능 구현하기]
- 질문 추천 기능과 동일하게 이번에는 답변 추천 기능을 구현해봅시다.
- 답변에서 추천 기능을 구현하려면 답변을 추천한 사용자가 DB에 저장될 수 있도록 관련된 속성을 Answer 엔티티에 추가해야 한다.
- Answer 엔티티에 추천인을 저장하기 위해 voters라는 이름의 속성을 추가해봅시다.
@ManyToMany
Set<SiteUser> voters = new LinkedHashSet<>();
public void addVoter(SiteUser voter) {
voters.add(voter);
}
- @ManyToMany 어노테이션을 사용하여 답변과 사용자 간의 다대다 관계를 가지도록 하였다.
- 질문 추천 기능 때와 마찬가지로 Set 형식으로 voters라는 리스트 형식의 테이블을 생성하였다.
- addVoter() 메서드는 생성된 voters에 추천인을 저장하는 역할을 담당한다.
- Answer.java 클래스에 위와같은 코드를 작성하고 DB를 확인하면 answer_voters라는 테이블이 새로 생성되는 것을 확인할 수 있다.
[question_detail.html 템플릿 수정]
- 답변 엔티티에 voters 속성을 추가했다면 답변 추천 기능을 구현해봅시다.
- 답변에 좋아요라는 버튼의 위치는 질문 상세 화면에서 답변을 보여주는 곳에 넣는 것이 적절하기 때문에 아래와 같이 question_detail.html에 코드를 추가 작성해봅시다.
<div class="my-3">
<a onclick="return confirm('추천하시겠습니까?');" class="btn btn-sm btn-outline-secondary"
th:href="@{|/answer/vote/${answer.id}|}">
좋아요
<i class="fa-regular fa-thumbs-up"></i>
<span class="badge rounded-pill bg-success" th:text="${#lists.size(answer.voters)}"></span>
</a>
<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
sec:authorize="isAuthenticated()"
th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
th:text="수정"></a>
<a onclick="return confirm('정말로 삭제하시겠습니까?')" th:href="@{|/answer/delete/${answer.id}|}"
class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
th:text="삭제"></a>
</div>
- '좋아요' 버튼의 위치는 수정, 삭제 버튼을 기준으로 가장 왼쪽에 배치하였고, #lists.size(answer.voters)를 좋아요의 개수도 함께 보이도록 하였다.
- '좋아요' 버튼을 클릭하면 GET 방식으로 /answer/vote/{id} URL을 요청하게 된다.
- 추가로 onclick="return confirm('추천하시겠습니까?')" 를 속성에 추가하여 '좋아요' 버튼을 클릭했을 때 한 번 더 확인할 수 있도록 alert 알림창이 나오도록 하였다.
[Service 수정]
- 답변을 추천한 사람을 저장하기 위해 아래와 같이 AnswerService.java 클래스에 코드를 추가해봅시다.
public void vote(Answer answer, SiteUser voter) {
answer.addVoter(voter);
answerRepository.save(answer);
}
- 위와 같이 로그인한 사용자를 답변 엔티티에 추천인으로 저장하기 위해 vote() 메서드를 추가하였다.
[Controller 수정]
- '좋아요' 버튼을 눌렀을 때 GET 방식으로 호출되는 /answer/vote/{id} URL을 처리하기 위해 아래와 같이 AnswerController.java에 코드를 추가해봅시다.
@PreAuthorize("isAuthenticated()")
@GetMapping("/vote/{id}")
public String answerVote(Principal principal, @PathVariable("id") Integer id) {
Answer answer = this.answerService.getAnswer(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
this.answerService.vote(answer, siteUser);
return "redirect:/question/detail/%d#answer_%d".formatted(answer.getQuestion().getId(), id);
}
- 다른 기능과 마찬가지로 로그인한 사람만 사용할 수 있도록 @PreAuthorize 어노테이션을 적용하고 AnswerService.java에서 작성한 vote() 메서드를 호출하여 사용자와 추천인으로 저장하였다.
- 에러가 없다면 추천인을 저장한 후 질문 상세 페이지로 리다이렉트한다.
- 위에서 설명한 모든 코드를 작성하고 로컬 서버를 다시 실행하면 localhost:8090/question/detail 페이지에서 각 질문과 답변에 대해서 '좋아요' 버튼이 생성된 것과 실제로 버튼을 눌렀을 때 해당 기능이 동작하여 숫자가 카운팅되는 것을 확인할 수 있다.
반응형
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] 앵커 이동 / 마크다운 적용 (0) | 2024.05.16 |
---|---|
[SpringBoot] 앵커 이동 / 마크다운 적용 (0) | 2024.05.16 |
[SpringBoot] 답변 수정 및 삭제 (0) | 2024.05.15 |
[SpringBoot] 질문 수정 및 삭제 (0) | 2024.05.15 |
[SpringBoot] 로그아웃 / 작성자 추가 (0) | 2024.05.14 |