겉바속촉
[점프투스프링부트] 3-11. 추천 본문
점프투스프링부트 3-11
목표 : 질문과 답변에 "추천(좋아요)" 기능 만들기
1. 엔티티 변경
질문, 답변의 추천은 추천한 사람(SiteUser 객체)을 질문, 답변 엔티티에 추가해야 한다.
1-1. Question
우선 Question 엔티티에 추천인(voter) 속성을 추가해 보자.
하나의 질문에 여러사람이 추천할 수 있고 한 사람이 여러 개의 질문을 추천할 수 있다.
이렇듯 질문과 추천인은 부모와 자식의 관계가 아니고 대등한 관계이기 때문에 @ManyToMany를 사용해야 한다.
참고 : https://docs.oracle.com/javaee/7/api/jakarta/persistence/ManyToMany.html
[파일명:/sbb/src/main/java/com/mysite/sbb/question/Question.java]
(... 생략 ...)
import java.util.Set;
import jakarta.persistence.ManyToMany;
(... 생략 ...)
public class Question {
(... 생략 ...)
@ManyToMany
Set<SiteUser> voter;
}
Set<SiteUser> voter 처럼 추천인(voter)을 @ManyToMany 관계로 추가했다.
List가 아닌 Set으로 한 이유는 추천인은 중복되면 안되기 때문이다.
Set은 중복을 허용하지 않는 자료형이다.
1-2. Answer
Answer 엔티티 역시 마찬가지 방법으로 추천인(voter) 속성을 추가하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/answer/Answer.java]
(... 생략 ...)
import java.util.Set;
import jakarta.persistence.ManyToMany;
(... 생략 ...)
public class Answer {
(... 생략 ...)
@ManyToMany
Set<SiteUser> voter;
}
1-3. 테이블 확인
질문과 답변 엔티티에 voter 속성을 추가한후 H2 콘솔을 확인해 보자.
QUESTION_VOTER, ANSWER_VOTER 테이블이 생성된 것을 확인할 수 있다.
이렇게 @ManyToMany 관계로 속성을 생성하면 새로운 테이블을 생성하여 데이터를 관리한다.
테이블에는 서로 연관된 엔티티의 고유번호(id) 2개가 프라이머리 키로 되어 있기 때문에 다대다(N:N) 관계가 성립하는 구조이다.
2. 질문 추천
Question 엔티티에 추천인 속성을 추가 했으니 이제 질문 추천 기능을 만들어 보자.
2-1. 질문 추천 버튼
질문을 추천할 수 있는 버튼의 위치는 어디가 좋을까?
그렇다.
질문 상세 화면이다.
질문 상세 템플릿을 다음과 같이 수정하자.
[파일명: /sbb/src/main/resources/templates/question_detail.html]
(... 생략 ...)
<!-- 질문 -->
<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이 호출되도록 할 것이다.
이와 같은 방법을 사용하는 이유는 "추천" 버튼을 눌렀을때 확인창을 통해 사용자의 확인을 구하기 위함이다.
2-2. 추천 버튼 확인 창
이어서 <추천> 버튼을 클릭했을 때 '정말로 추천하시겠습니까?'라는 확인 창이 나타나야 하므로 다음 코드를 추가하자.
[파일명:/sbb/src/main/resources/templates/question_detail.html]
(... 생략 ...)
<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이 호출될 것이다.
2-3. QuestionService
추천인을 저장하기 위해 다음과 같이 QuestionSerivce를 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/question/QuestionService.java]
(... 생략 ...)
public class QuestionService {
(... 생략 ...)
public void vote(Question question, SiteUser siteUser) {
question.getVoter().add(siteUser);
this.questionRepository.save(question);
}
}
Question 엔티티에 사용자를 추천인으로 저장하는 vote 메서드를 추가했다.
2-4. QuestionController
이제 추천 버튼을 눌렀을때 호출되는 URL을 처리하기 위해 다음과 같이 QuestionController를 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/question/QuestionController.java]
(... 생략 ...)
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 메서드를 호출하여 추천인을 저장했다.
오류가 없다면 질문 상세화면으로 리다이렉트 한다.
2-5. 질문 추천 확인
질문 상세 화면의 본문 상단을 보면 <추천> 버튼이 생겼을 것이다.
버튼이 잘 작동하는지 확인하자.
3. 답변 추천
답변 추천 기능은 질문 추천 기능과 동일하므로 빠르게 작성해 보자.
3-1. 답변 추천 버튼
답변의 추천수를 표시하고, 답변을 추천할 수있는 버튼을 질문 상세 템플릿에 다음과 같이 추가하자.
[파일명:/sbb/src/main/resources/templates/question_detail.html]
(... 생략 ...)
<!-- 답변 반복 시작 -->
<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이 호출될 것이다.
3-2. AnswerService
그리고 답변에 추천인을 저장하기 위해 다음과 같이 AnswerService를 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java]
(... 생략 ...)
public class AnswerService {
(... 생략 ...)
public void vote(Answer answer, SiteUser siteUser) {
answer.getVoter().add(siteUser);
this.answerRepository.save(answer);
}
}
Answer 엔티티에 사용자를 추천인으로 저장하는 vote 메서드를 추가했다.
3-3. AnswerController
이제 답변 추천 버튼을 눌렀을때 호출되는 URL을 처리하기 위해 다음과 같이 AnswerController를 수정하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java]
(... 생략 ...)
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 메서드를 호출하여 추천인을 저장한다.
오류가 없다면 질문 상세화면으로 리다이렉트 한다.
3-4. 답변 추천 확인
이와 같이 수정 후 답변 추천 기능도 확인해 보자.
'IT 일기 (상반기) > SPRING 기초' 카테고리의 다른 글
Error. MapStruct 사용 후 빌드 에러 (0) | 2023.03.21 |
---|---|
[점프투스프링부트] 3-10. 수정과 삭제2 (2) | 2023.03.15 |
[점프투스프링부트] 3-10. 수정과 삭제 1 (0) | 2023.03.14 |
[점프투스프링부트] 3-09. 글쓴이 표시 (0) | 2023.03.13 |
[점프투스프링부트] 3-08. 엔티티 변경 (0) | 2023.03.13 |