@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class) // + enableJpaAuditing => JPA Auditing 활성
// JPA Auditing : 시간에 대해 자동으로 값을 넣어주는 기능
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime modifyDate;
@ManyToOne // 부모 : question
private Question question;
@ManyToOne
private SiteUser author;
@ManyToMany
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<SiteUser> voters = new LinkedHashSet<>();
@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class) // + enableJpaAuditing => JPA Auditing 활성
// JPA Auditing : 시간에 대해 자동으로 값을 넣어주는 기능
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime modifyDate;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
@LazyCollection(LazyCollectionOption.EXTRA) // answerList.size();
@ManyToOne
private SiteUser author;
@ManyToMany
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<SiteUser> voters = new LinkedHashSet<>();
private int view = 0;
public void updateView() {
this.view++;
}
/* 게시판 분류
0 : 질문답변
1 : 강좌
2 : 자유게시판
*/
private int category;
public QuestionEnum getCategoryAsEnum() {
switch (this.category) {
case 0:
return QuestionEnum.QNA;
case 1:
return QuestionEnum.FREE;
case 2:
return QuestionEnum.BUG;
default:
throw new RuntimeException("올바르지 않은 접근입니다.");
}
}
public String getCategoryAsString() {
switch (this.category) {
case 0:
return "질문과답변";
case 1:
return "자유게시판";
case 2:
return "버그및건의";
default:
throw new RuntimeException("올바르지 않은 접근입니다.");
}
}
@OneToMany(mappedBy = "question", cascade = {CascadeType.REMOVE})
@ToString.Exclude
@LazyCollection(LazyCollectionOption.EXTRA) // commentList.size(); 함수가 실행될 때 SELECT COUNT 실행
private List<Comment> comments = new ArrayList<>();
}
@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class) // + enableJpaAuditing => JPA Auditing 활성
// JPA Auditing : 시간에 대해 자동으로 값을 넣어주는 기능
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 200)
private String subject;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime modifyDate;
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
@LazyCollection(LazyCollectionOption.EXTRA) // answerList.size(); 함수가 실행될 때 SELECT COUNT 실행
// N+1 문제는 발생하지만, 한 페이지에 보여주는 10개의 게시물의 정보를 가져와서 개수를 표기하는 것 보다는 덜 부담
private List<Answer> answerList = new ArrayList<>();
@ManyToOne
private SiteUser author;
@OneToMany
@LazyCollection(LazyCollectionOption.EXTRA)
// 변경!!
private Set<QuestionVoter> voters = new HashSet<>();
}
@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class)
public class QuestionVoter {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Question question;
@ManyToOne
private SiteUser voter;
@CreatedDate
private LocalDateTime createdAt;
}
@Transactional
public void vote(Question question, SiteUser siteUser) {
QuestionVoter questionVoter = questionVoterRepository.findByQuestionAndVoter(question, siteUser);
// 이미 추천했다면 삭제
if(questionVoter != null) {
// answer에 Set 갱신
question.getVoters().remove(questionVoter);
// 연관관계 주인도 업데이트
questionVoterRepository.delete(questionVoter);
return;
}
// 추천 안했다면 새로 추천 처리
QuestionVoter newQuestionVoter = new QuestionVoter();
newQuestionVoter.setQuestion(question);
newQuestionVoter.setVoter(siteUser);
// 연관관계 주인 및 Set 저장
QuestionVoter saveQuestionVoter = questionVoterRepository.save(newQuestionVoter);
question.getVoters().add(saveQuestionVoter);
// 아래도 되긴하네요
// question.getVoters().add(newQuestionVoter);
}
question.getVoters().add(newQuestionVoter);
해도 되네유@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class) // + enableJpaAuditing => JPA Auditing 활성
// JPA Auditing : 시간에 대해 자동으로 값을 넣어주는 기능
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime modifyDate;
@ManyToOne // 부모 : question
private Question question;
@ManyToOne
private SiteUser author;
@OneToMany(fetch = FetchType.LAZY)
@LazyCollection(LazyCollectionOption.EXTRA)
// 변경!!
private Set<AnswerVoter> voters = new HashSet<>();
@OneToMany(mappedBy = "answer", cascade = {CascadeType.REMOVE})
@ToString.Exclude
@LazyCollection(LazyCollectionOption.EXTRA) // commentList.size(); 함수가 실행될 때 SELECT COUNT 실행
private List<Comment> comments = new ArrayList<>();
}
@Getter
@Setter
@Entity
@EntityListeners(AuditingEntityListener.class)
public class AnswerVoter {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Answer answer;
@ManyToOne
private SiteUser voter;
@CreatedDate
private LocalDateTime createdAt;
}
@Transactional
public void vote(Answer answer, SiteUser siteUser) {
AnswerVoter answerVoter = answerVoterRepository.findByAnswerAndVoter(answer, siteUser);
// 이미 추천했다면 삭제
if(answerVoter != null) {
// answer에 Set 갱신
answer.getVoters().remove(answerVoter);
// 연관관계 주인도 업데이트
answerVoterRepository.delete(answerVoter);
return;
}
// 추천 안했다면 새로 추천 처리
AnswerVoter newAnswerVote = new AnswerVoter();
newAnswerVote.setAnswer(answer);
newAnswerVote.setVoter(siteUser);
AnswerVoter saveAnswerVote = answerVoterRepository.save(newAnswerVote);
answer.getVoters().add(saveAnswerVote);
// 아래도 되긴 하네유
// answer.getVoters().add(newAnswerVote);
}
QuestionController
@PreAuthorize("isAuthenticated()")
@PostMapping("/vote/{id}")
public String questionVote(Principal principal, @PathVariable("id") Long id) {
Question question = questionService.getQuestion(id);
SiteUser siteUser = userService.getUser(principal.getName());
questionService.vote(question, siteUser);
return String.format("redirect:/question/detail/%s", id);
}
@GetMapping("/detail/{id}")
public String detail(Model model, @PathVariable Long id, AnswerForm answerForm,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "") String sort, Principal principal) {
Question question = questionService.getQuestion(id);
model.addAttribute("question", question);
Page<Answer> paging = answerService.getAnswerPage(question, page, sort);
model.addAttribute("paging", paging);
// 사용자가 로그인 했다면
if (principal != null && principal.getName() != null) {
// 질문에 대한 추천 여부 확인
SiteUser currentUser = userService.getUser(principal.getName());
boolean hasQuestionVoted = question.getVoters().stream()
.anyMatch(v -> v.getVoter().equals(currentUser));
model.addAttribute("hasQuestionVoted", hasQuestionVoted);
// 답변에 대한 추천 여부 확인
List<Boolean> hasAnsweredVoted = paging.getContent().stream()
.map(answer -> answer.getVoters().stream()
.anyMatch(v -> v.getVoter().equals(currentUser)))
.collect(Collectors.toList());
model.addAttribute("hasAnsweredVoted", hasAnsweredVoted);
} else {
model.addAttribute("hasQuestionVoted", false);
model.addAttribute("hasAnsweredVoted", Collections.emptyList());
}
return "question/question_detail";
}
----------
AnswerController
@PreAuthorize("isAuthenticated()")
@PostMapping("/vote/{id}")
public String answerVote(Principal principal, @PathVariable("id") Long id) {
Answer answer = answerService.getAnswer(id);
SiteUser siteUser = userService.getUser(principal.getName());
answerService.vote(answer, siteUser);
return String.format("redirect:/question/detail/%s#answer_%s",
answer.getQuestion().getId(), answer.getId());
}
// Question 부분
<!-- 로그인 하지 않은 경우 기존꺼 그대로 노출-->
<a sec:authorize="isAnonymous()" class="btn btn-sm btn-outline-secondary" th:href="@{/user/login}">
추천(로그인필요)
<span class="badge rounded-pill bg-success" th:text="${#sets.size(question.voters)}"></span>
</a>
<!-- 로그인한 경우 -->
<form sec:authorize="isAuthenticated()" th:if="${hasQuestionVoted}" th:action="@{|/question/vote/${question.id}|}" method="post">
<button type="submit" onclick="return confirm('추천을 취소하시겠습니까?');" class="btn btn-sm active btn-outline-secondary">
추천
<span class="badge rounded-pill bg-success" th:text="${#sets.size(question.voters)}"></span>
</button>
</form>
<form sec:authorize="isAuthenticated()" th:if="${!hasQuestionVoted}" th:action="@{|/question/vote/${question.id}|}" method="post">
<button type="submit" onclick="return confirm('추천하시겠습니까?');" class="recommend btn btn-sm btn-outline-secondary">
추천
<span class="badge rounded-pill bg-success" th:text="${#sets.size(question.voters)}"></span>
</button>
</form>
// Answer 부분
<!-- 로그인한 경우 -->
<form sec:authorize="isAuthenticated()" th:if="${hasAnsweredVoted[stat.index]}" th:action="@{|/answer/vote/${answer.id}|}" method="post">
<button type="submit" onclick="return confirm('추천을 취소하시겠습니까?');" class="btn btn-sm active btn-outline-secondary">
추천
<span class="badge rounded-pill bg-success" th:text="${#sets.size(answer.voters)}"></span>
</button>
</form>
<form sec:authorize="isAuthenticated()" th:if="${!hasAnsweredVoted[stat.index]}" th:action="@{|/answer/vote/${answer.id}|}" method="post">
<button type="submit" onclick="return confirm('추천하시겠습니까?');" class="recommend btn btn-sm btn-outline-secondary">
추천
<span class="badge rounded-pill bg-success" th:text="${#sets.size(answer.voters)}"></span>
</button>
</form>
<!-- 로그인 하지 않은 경우 기존꺼 그대로 노출-->
<a sec:authorize="isAnonymous()" class="btn btn-sm btn-outline-secondary" th:href="@{/user/login}">
추천(로그인필요)
<span class="badge rounded-pill bg-success" th:text="${#sets.size(answer.voters)}"></span>
</a>
@Transactional
public void vote(Question question, SiteUser siteUser) {
QuestionVoter questionVoter = questionVoterRepository.findByQuestionAndVoter(question, siteUser);
// 이미 추천했다면 삭제
if(questionVoter != null) {
// answer에 Set 갱신
question.getVoters().remove(questionVoter);
// 연관관계 주인도 업데이트
questionVoterRepository.delete(questionVoter);
return;
}
// 추천 안했다면 새로 추천 처리
QuestionVoter newQuestionVoter = new QuestionVoter();
newQuestionVoter.setQuestion(question);
newQuestionVoter.setVoter(siteUser);
// 연관관계 주인 및 Set 저장
QuestionVoter saveQuestionVoter = questionVoterRepository.save(newQuestionVoter);
question.getVoters().add(saveQuestionVoter);
// 아래도 되긴하네요
// question.getVoters().add(newQuestionVoter);
}