본문 바로가기

SpringBoot

[Do it] 3장 스프링 부트 고급 기능 익히기(5)

1. 추천 기능 추가하기 

 

- [추천] 버튼을 통해 질문이나 답변을 본 다른 사용자들이 반응을 남길 수 있도록 '추천' 기능을 구현 

 

1) 엔티티에 속성 추가 

 

- 답변을 추천한 사용자(SiteUser)가 DB에 저장될 수 있도록 관련 속성을 질문, 답변 엔티티에 추가 

- 하나의 질문에 여러 사람이 추천할 수 있고 한 사람이 여러 개의 질문을 추천할 수 있기 때문에 @ManyToMany 사용 

 

Question.java

package com.example.demo.question;

import java.util.Set;
....
import jakarta.persistence.ManyToMany;

@Getter
@Setter
@Entity
public class Question {
	....
	@ManyToMany
	Set<SiteUser> voter;
}

 

- @ManyToMany와 함께 Set<SiteUser> voter를 작성해 voter 속성을 다대다 관계로 설정하여 질문 엔티티에 추가 

- set 자료형을 쓴 이유는 voter 속성값이 서로 중복되지 않도록 하기 위해서임

 

Answer.java

package com.example.demo.answer;

import java.util.Set;
...
import jakarta.persistence.ManyToMany; //다대다 관계 

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Answer {
...
	
	@ManyToMany
	Set<SiteUser>voter;
	
}

 

- @ManyToMany를 사용해 다대 관계로 석성을 생성하면 새로운 테이블을 만들어 관련 데이터를 관리 

 

 

2) 질문 추천 기능 생성 

 

templates/question_detail.html

...
<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>
...

 

- lists.size 메서드에 question.voter를 사용하여 추천 수도 함께 보이게 함

- class 속성에 recommend를 적용해 자바스크립트로 data-uri에 정의된 URL이 호출되도록 함 

(@{|/question/vote/${question.id}|}이 호출됨)

- [추천] 버튼을 눌렀을 때 메시지가 적힌 팝업 창을 통해 추천을 진행 

 

question_detail.html

...
const recommend_elements = document.getElementByClassName("recommend");
Array.from(recomment_elements).forEach(function(element){
	element.addEventListener('click', function(){
		if(confirm("정말로 추천하시겠습니까?")){
			location.href = this.dataset.uri;
		};
	});
});
..

 

 

QuestionService.java

@RequiredArgsConstructor
@Service
public class QuestionService {
...
	//추천인 저장
	public void vote(Question question, SiteUser siteUser) {
		question.getVoter().add(siteUser);
		this.questionRepository.save(question);
	}
	
	
}

 

- 로그인한 사용자를 질문 엔티티에 추천인으로 저장하기 위해 vote 메서드 추가 

 

QuestionController.java

@RequestMapping("/question")
@RequiredArgsConstructor
@Controller
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);
	}	
}

 

- GET 방식으로 호출되는 @{|/question/vote/${question.id}|} URL을 처리하기 위해 questionVote 메서드 추가 

- 로그인한 사람만 사용할 수 있도록 @PreAuthorize("isAuthenticated()") 적용 

- QuestionService의 vote 메서드를 호출하여 사용자(SiteUser)를 추천인(voter)으로 저장 

 

 

 

3) 답변 추천 기능 생성하기 

 

templates/question_detail.html

- 답변의 추천 수를 표시하고, 답변을 추천할 수 있는 버튼 생성 

...
<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>
...

 

AnswerService.java

- 답변을 추천한 사람을 저장하는 vote 메서드 추가  

@RequiredArgsConstructor
@Service
public class AnswerService {

...
	
	//추천인 저장
	public void vote(Answer answer, SiteUser siteUser) {
		answer.getVoter().add(siteUser);
		this.answerRepository.save(answer);
	}
}

 

 

AnswerContorller.java

- GET 방식으로 호출되는  @{|/answer/vote/${answer.id}|} URL을 처리하기 위해 answerVote 메서드 추가 

- 로그인한 사람만 가능해야 하므로 @PreAuthorize("isAuthenticated()")를 적용 

- AnswerService의 vote 메서드를 호출하여 추천인을 저장 

@RequestMapping("/answer")
@RequiredArgsConstructor
@Controller
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());
		
	}
}

 

 

 

 

2. 앵커 기능 추가하기 

 

- 현재 답변을 작성하거나 수정하면 페이지 상단으로 스크롤이 이동해서 자신이 작성한 답변을 확인하려면 다시 스크롤을 내려서 확인해야 함

 

- HTML에는 URL 호출 시 원하는 위치로 이동해 주는 앵커(anchor) 태그 <a> 태그가 있는데, 이를 활용하면 답변 등록, 답변 수정, 답변 추천 시 앵커 태그를 이용하여 원하는 위치로 이동할 수 있음 

 

- <a> 태그를 이용하여 다른 웹 페이지로 이동하거나 동일한 페이지 내에서 특정 위치로 스크롤하도록 만들 수 있음 

 

 

1) 답변 앵커 추가하기 

 

templates/question_detail.html

- 답변 작성, 수정 시에 이동해야 할 앵커 태그 추가 

- 앵커 태그의 id 속성은 유일한 값이어야 하므로 답변의 id값을 사용 

 

 

리다이렉트 수정

 

- 기존 URL 리다이렉트 코드

return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());

 

- 리다이렉트되는 질문 상세 페이지 URL에 #answer_%s 삽입 

- 답변 등록, 수정, 추천 부분에 추가 

return String.format("redirect:/question/detail/%s#answer_%s", 
answer.getQuestion().getId(), answer.getId());

 

 

2) 답변 서비스 수정하기 

 

- 답변 컨트롤러에서 답변이 등록된 위치로 이동하려면 반드시 답변 객체 (Answer 객체)가 필요 

 

AnswerService.java

...
public Answer create(Question question, String content, SiteUser author) {
        Answer answer = new Answer();
        answer.setContent(content);
        answer.setCreateDate(LocalDateTime.now());
        answer.setQuestion(question);
        answer.setAuthor(author);
        this.answerRepository.save(answer);
        return answer;
    }
...

 

 

3) 답변 컨트롤러 수정하기 

 

AnswerController.java