겉바속촉

[점프투스프링부트] 3-11. 추천 본문

IT 일기 (상반기)/SPRING 기초

[점프투스프링부트] 3-11. 추천

겉바속촉 2023. 3. 15. 13:49
728x90
반응형

 

점프투스프링부트 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. 답변 추천 확인

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

 

 

 

 

 

 

728x90
반응형