사랑하애오
article thumbnail

1. 엔티티 변경

질문, 답변의 추천은 추천한 사람(SiteUser 객체)을 질문, 답변 엔티티에 추가해야 한다.

1.1.  

2. Question

우선 Question 엔티티에 추천인(voter) 속성을 추가해 보자.

하나의 질문에 여러사람이 추천할 수 있고 한 사람이 여러 개의 질문을 추천할 수 있다.
이렇듯 질문과 추천인은 부모와 자식의 관계가 아니고 대등한 관계이기 때문에 @ManyToMany를 사용해야 한다.

참고 : https://docs.oracle.com/javaee/7/api/javax/persistence/ManyToMany.html

 

ManyToMany (Java(TM) EE 7 Specification APIs)

Specifies a many-valued association with many-to-many multiplicity. Every many-to-many association has two sides, the owning side and the non-owning, or inverse, side. The join table is specified on the owning side. If the association is bidirectional, eit

docs.oracle.com

[파일명:/sbb/src/main/java/com/mysite/sbb/question/Question.java]

<code />
(... 생략 ...) import java.util.Set; import javax.persistence.ManyToMany; (... 생략 ...) public class Question { (... 생략 ...) @ManyToMany Set<SiteUser> voter; }

Set<SiteUser> voter 처럼 추천인(voter)을 @ManyToMany 관계로 추가했다.
List가 아닌 Set으로 한 이유는 추천인은 중복되면 안되기 때문이다.

Set은 중복을 허용하지 않는 자료형이다.

2.1.  

3. Answer

Answer 엔티티 역시 마찬가지 방법으로 추천인(voter) 속성을 추가하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/Answer.java]

<code />
(... 생략 ...) import java.util.Set; import javax.persistence.ManyToMany; (... 생략 ...) public class Answer { (... 생략 ...) @ManyToMany Set<SiteUser> voter; }

3.1.  

4. 테이블 확인

질문과 답변 엔티티에 voter 속성을 추가한후 H2 콘솔을 확인해 보자.

QUESTION_VOTER, ANSWER_VOTER 테이블이 생성된 것을 확인할 수 있다.
이렇게 @ManyToMany 관계로 속성을 생성하면 새로운 테이블을 생성하여 데이터를 관리한다.
테이블에는 서로 연관된 엔티티의 고유번호(id) 2개가 프라이머리 키로 되어 있기 때문에
다대다(N:N) 관계가 성립하는 구조이다.

5.  

6. 질문 추천

Question 엔티티에 추천인 속성을 추가 했으니 이제 질문 추천 기능을 만들어 보자.

6.1.  

7. 질문 추천 버튼

질문을 추천할 수 있는 버튼의 위치는 어디가 좋을까?
그렇다. 질문 상세 화면이다.
질문 상세 템플릿을 다음과 같이 수정하자.

[파일명: /sbb/src/main/resources/templates/question_detail.html]

<code />
(... 생략 ...) <!-- 질문 --> <h2 class="border-bottom py-2" th:text="${question.subject}"></h2> <div class="card my-3"> <div class="card-body"> (... 생략 ...) <div class="my-3"> <a href="javascript:void(0);" class="recommend btn btn-sm btn-outline-secondary" th:data-uri="@{|/question/vote/${question.id}|}"> 추천 <span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></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 href="javascript:void(0);" th:data-uri="@{|/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> </div> </div> (... 생략 ...)

질문의 추천 버튼을 질문의 수정 버튼 좌측에 추가했다.
그리고 버튼에는 추천수도 함께 보이도록 했다.
추천 버튼을 클릭하면 href의 속성이 javascript:void(0)으로 되어 있기 때문에 아무런 동작도 하지 않는다.
하지만 class 속성에 "recommend"를 추가하여 자바스크립트를 사용하여 data-uri에 정의된 URL이 호출되도록 할 것이다. 이와 같은 방법을 사용하는 이유는 "추천" 버튼을 눌렀을때 확인창을 통해 사용자의 확인을 구하기 위함이다.

7.1.  

8. 추천 버튼 확인 창

이어서 <추천> 버튼을 클릭했을 때 '정말로 추천하시겠습니까?'라는 확인 창이 나타나야 하므로 다음 코드를 추가하자.

[파일명:/sbb/src/main/resources/templates/question_detail.html]

<code />
(... 생략 ...) <script layout:fragment="script" type='text/javascript'> const delete_elements = document.getElementsByClassName("delete"); Array.from(delete_elements).forEach(function(element) { element.addEventListener('click', function() { if(confirm("정말로 추천하시겠습니까?")) { location.href = this.dataset.uri; }; }); }); const recommend_elements = document.getElementsByClassName("recommend"); Array.from(recommend_elements).forEach(function(element) { element.addEventListener('click', function() { if(confirm("정말로 추천하시겠습니까?")) { location.href = this.dataset.uri; }; }); }); </script> </html>

추천 버튼에 class="recommend"가 적용되어 있으므로 추천 버튼을 클릭하면
"정말로 추천하시겠습니까?"라는 질문이 나타나고 "확인"을 선택하면 data-uri 속성에 정의한 URL이 호출될 것이다.

8.1.  

9. QuestionService

그리고 추천인을 저장하기 위해 다음과 같이 QuestionSerivce를 수정하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/question/QuestionService.java]

<code />
(... 생략 ...) public class QuestionService { (... 생략 ...) public void vote(Question question, SiteUser siteUser) { question.getVoter().add(siteUser); this.questionRepository.save(question); } }

Question 엔티티에 사용자를 추천인으로 저장하는 vote 메서드를 추가했다.

9.1.  

10. QuestionController

이제 추천 버튼을 눌렀을때 호출되는 URL을 처리하기 위해 다음과 같이 QuestionController를 수정하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/question/QuestionController.java]

<code />
(... 생략 ...) public class QuestionController { (... 생략 ...) @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 String.format("redirect:/question/detail/%s", id); } }

위와 같이 questionVote 메서드를 추가했다.
추천은 로그인한 사람만 가능해야 하므로 @PreAuthorize("isAuthenticated()") 어노테이션이 적용되었다.
그리고 위에서 작성한 QuestionService의 vote 메서드를 호출하여 추천인을 저장했다.
오류가 없다면 질문 상세화면으로 리다이렉트 한다.

10.1.  

11. 질문 추천 확인

질문 상세 화면의 본문 상단을 보면 <추천> 버튼이 생겼을 것이다.
버튼이 잘 작동하는지 확인하자.

12.  

13. 답변 추천

답변 추천 기능은 질문 추천 기능과 동일하므로 빠르게 작성해 보자.

14. 답변 추천 버튼

답변의 추천수를 표시하고, 답변을 추천할 수있는 버튼을 질문 상세 템플릿에 다음과 같이 추가하자.

[파일명:/sbb/src/main/resources/templates/question_detail.html]

<code />
(... 생략 ...) <!-- 답변 반복 시작 --> <div class="card my-3" th:each="answer : ${question.answerList}"> <div class="card-body"> (... 생략 ...) <div class="my-3"> <a href="javascript:void(0);" class="recommend btn btn-sm btn-outline-secondary" th:data-uri="@{|/answer/vote/${answer.id}|}"> 추천 <span class="badge rounded-pill bg-success" th:text="${#lists.size(answer.voter)}"></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 href="javascript:void(0);" th:data-uri="@{|/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> </div> </div> <!-- 답변 반복 끝 --> (... 생략 ...)

질문과 마찬가지로 답변 영역의 상단에 답변을 추천할 수 있는 버튼을 생성했다.
이 역시 추천 버튼에 class="recommend"가 적용되어 있으므로 추천 버튼을 클릭하면
"정말로 추천하시겠습니까?"라는 질문이 나타나고 "확인"을 선택하면 data-uri 속성에 정의한 URL이 호출될 것이다.

14.1.  

15. AnswerService

그리고 답변에 추천인을 저장하기 위해 다음과 같이 AnswerService를 수정하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java]

<code />
(... 생략 ...) public class AnswerService { (... 생략 ...) public void vote(Answer answer, SiteUser siteUser) { answer.getVoter().add(siteUser); this.answerRepository.save(answer); } }

Answer 엔티티에 사용자를 추천인으로 저장하는 vote 메서드를 추가했다.

15.1.  

16. AnswerController

이제 답변 추천 버튼을 눌렀을때 호출되는 URL을 처리하기 위해 다음과 같이 AnswerController를 수정하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java]

<code />
(... 생략 ...) public class AnswerController { (... 생략 ...) @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 String.format("redirect:/question/detail/%s", answer.getQuestion().getId()); } }

위와 같이 answerVote 메소드를 추가했다.
추천은 로그인한 사람만 가능해야 하므로 @PreAuthorize("isAuthenticated()") 어노테이션이 적용되었다.
그리고 위에서 작성한 AnswerService의 vote 메소드를 호출하여 추천인을 저장한다.
오류가 없다면 질문 상세화면으로 리다이렉트 한다.

17. 답변 추천 확인

이와 같이 수정 후 답변 추천 기능도 확인해 보자.

profile

사랑하애오

@사랑하애

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!