3/27 TIL - 점프투스프링부트 따라하기 6

큰모래·2023년 3월 27일
0

점프투스프링부트 따라하기


3-15 (1) 답변 페이징과 정렬

  • 질문 글에 답변이 100개가 달린다면 이것도 페이징 처리를 해야 된다.
  • 또한 답변 정렬을 추천 및 최신 순으로 정렬한다.

AnswerRepository

  • findAllByQuestion
    • 매개변수 question 객체를 조건으로 연관되는 Answer 엔티티를 페이징하여 반환한다.
public interface AnswerRepository extends JpaRepository<Answer, Long> {

    Page<Answer> findAllByQuestion(Question question, Pageable pageable);
}

AnswerService

  • Pageable 객체에 page 번호, 페이지당 게시물 개수, 정렬 기준을 담는다.
  • 컨트롤러부터 넘겨받은 Question 객체와 서비스에서 만든 Pageable 객체를 통해 Page<Answer> 객체를 반환받는다.
  • 최종적으로 컨트롤러에 Page<Answer> 객체를 반환한다.
		public Page<Answer> getAnswerList(int page, Question question) {
        List<Sort.Order> sorts = new ArrayList<>();
        sorts.add(Sort.Order.desc("voter"));
        sorts.add(Sort.Order.desc("createDate"));
        Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
        return answerRepository.findAllByQuestion(question, pageable);
    }

QuestionController

  • 쿼리스트링으로 받는 page의 기본값을 0으로 설정
  • answerService로 부터 받은 Page<Answer>객체를 model을 통해 뷰에 넘겨준다.
		public String detail(@PathVariable Long id, Model model, AnswerForm answerForm,
                         @RequestParam(value = "page", defaultValue = "0") int page) {
        Question question = questionService.getQuestion(id);
        Page<Answer> paging = answerService.getAnswerList(page, question);
        model.addAttribute("paging", paging);
        model.addAttribute("question", question);
        return "/question/question_detail";
    }

question_detail

//이전
<div class="card my-3" th:each="answer : ${question.answerList}">

//수정
<div class="card my-3" th:each="answer : ${paging}">

//추가
		<div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
                <a class="page-link"
                   th:href="@{|?page=${paging.number - 1}|}">
                    <span>이전</span>
                </a>
            </li>
            <li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
                th:if="${page >= paging.number-5 and page <= paging.number+5}"
                th:classappend="${page == paging.number} ? 'active'"
                class="page-item">
                <a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}">"></a>
            </li>
            <li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
                <a class="page-link" th:href="@{|?page=${paging.number+1}|}">
                    <span>다음</span>
                </a>
            </li>
        </ul>
    </div>

3-15 (2) 질문에 댓글 기능

  • 질문에 댓글도 달 수 있도록 기능을 추가해보자.

Comment

  • ManyToOne 은 기본이 즉시로딩이므로, 직접 지연로딩으로 세팅해야 한다.
  • 댓글과 작성자는 ManyToOne 관계
  • 댓글과 질문글은 ManyToOne 관계
  • 댓글과 답변은 ManyToOne 관계 (아직 개발 진행 중)
@Entity
@Getter
@Setter
public class Comment {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String content;

    private LocalDateTime createDate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private SiteUser author;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "question_id")
    private Question question;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "answer_id")
    private Answer answer;

    public Long getQuestionId() {
        Long result = null;

        if (this.question.getId() != null) {
            return this.question.getId();
        }
        if (this.answer.getId() != null) {
            return this.answer.getId();
        }

        return result;
    }

}

Question - 추가

  • Comment 객체의 question 속성이 관계의 주인이다.
		@OneToMany(mappedBy = "question")
    private List<Comment> comments = new ArrayList<>();

Answer - 추가

		@OneToMany(mappedBy = "answer")
    private List<Comment> comments = new ArrayList<>();

CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long> {
}

CommentService

  • 컨트롤러로부터 Question 객체, 댓글 내용, 작성자를 매개변수로 받아온다.
  • Comment 객체를 만들어 데이터를 넣어준다.
  • commentRepository에 저장하고 comment 객체를 반환한다.
@Service
@RequiredArgsConstructor
public class CommentService {

    private final CommentRepository commentRepository;

    public Comment create(Question question, String content, SiteUser author) {
        Comment comment = new Comment();
        comment.setContent(content);
        comment.setQuestion(question);
        comment.setCreateDate(LocalDateTime.now());
        comment.setAuthor(author);
        commentRepository.save(comment);

        return comment;
    }

}

CommentForm

  • 컨트롤러와 뷰 사이에서 데이터를 전달할 폼 객체 생성
@Getter
@Setter
public class CommentForm {
    @NotEmpty(message = "내용은 필수입니다!")
    private String content;
}

CommentController

  • 댓글을 작성하는 post 요청 메서드 실행
  • 댓글 폼을 question_detail에 만들어 따로 get 요청 메서드는 만들지 않았음.
  • 경로변수 idQuestion 객체의 id이다.
  • commentServicecreate 메서드에 매개변수를 넣어 실행한다.
@Controller
@RequestMapping("/comment")
@RequiredArgsConstructor
public class CommentController {

    private final QuestionService questionService;
    private final CommentService commentService;
    private final UserService userService;

    @PostMapping("/create/{id}")
    public String create(@PathVariable Long id, @ModelAttribute @Validated CommentForm commentForm, BindingResult bindingResult, Model model, Principal principal) {
        Question question = questionService.getQuestion(id);
        SiteUser user = userService.getUser(principal.getName());

        if (bindingResult.hasErrors()) {
            model.addAttribute("question", question);
            return "question/question_detail";
        }

        commentService.create(question, commentForm.getContent(), user);

        return "redirect:/question/detail/" + question.getId();
    }

}

QuestionController - 추가

  • 매개변수로 CommentForm 객체를 추가하여 동적 데이터를 입력 받을 수 있도록 하였다.
		@GetMapping("/detail/{id}")
    public String detail(@PathVariable Long id, Model model, AnswerForm answerForm, 
													CommentForm commentForm,
                         @RequestParam(value = "page", defaultValue = "0") int page) {
        Question question = questionService.getQuestion(id);
        Page<Answer> paging = answerService.getAnswerList(page, question);
        model.addAttribute("paging", paging);
        model.addAttribute("question", question);
        return "question/question_detail";
    }

question_detail - 추가

		<div>
				<span class="border mb-4 p-1">
				<span class="comment py-2 text-muted">댓글</span>
				<span class="badge text-bg-primary" th:text="${#lists.size(question.comments)}"></span>
				</span>
		</div>
		<div class="comment py-2 text-muted" th:if="${not #lists.isEmpty(question.comments)}">
				<div th:each="comment,index : ${question.comments}" class="comment-content py-2 text-muted">
						<span class="border-bottom py-1" style="white-space: pre-line;" th:text="${comment.content}"></span>
						<span class="text-end text-success" th:text="${comment.author.username}"></span>
				</div>
		</div>

		<div>
        <h5 class="border-bottom my-3 py-2">댓글 등록</h5>
        <form class="my-3" th:object="${commentForm}" th:action="@{|/comment/create/${question.id}|}" method="post">
            <div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
                <div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
            </div>
            <textarea name="content" sec:authorize="isAnonymous()" disabled th:field="*{content}" rows="2" class="form-control"></textarea>
            <textarea name="content" sec:authorize="isAuthenticated()" th:field="*{content}" rows="2" class="form-control"></textarea>
            <input type="submit" value="댓글등록" class="btn btn-primary my-2">
        </form>
    </div>
profile
큰모래

0개의 댓글