https://wikidocs.net/book/7601
점프 투 스프링부트
점프 투 스프링부트는 Spring Boot Board(SBB)라는 이름의 게시판 서비스를 만들어가는 과정을 설명한 스프링부트 입문서이다. 자바 설치부터 시작하여 서비스 운…
wikidocs.net
"점프 투 스프링부트" 위키독스의 항목 중, 추가 개발 항목에서 답변 페이징 기능에 해당하는 내용이다.
답변 페이징
3-02 페이징
* `[완성 소스]` : [https://github.com/pahkey/sbb3/tree/3-02](https://github.com/pahkey/sbb3/tree/3-02) …
wikidocs.net
해당 문서를 따라 개발을 진행하다 보면, 페이징 기능을 활용한 내용도 나온다.
기존 질문 목록 페이지는 질문 목록이 한 페이지에서 전부 처리되었기 때문에 단순히 Question Repository에 페이징 기능만 처리하면 됐었다.
하지만 답변의 경우 페이지 구성이 질문 - 질문 상세 페이지 내부에 답변들이 쭉 나열되어 처리되는 형식으로 구성되어 있기 때문에 위키독스에서 작성한 페이징 방식처럼 작성하면 제대로 동작하지 않는다.
AnswerRepository
//AnswerRepository.java
package com.example.sbb.answer;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
public interface AnswerRepository extends JpaRepository<Answer, Integer> {
Page<Answer> findAllByQuestion(Question question, Pageable pageable);
}
메서드 명을 정의할 때에는 Entity에 작성한 이름과 맞게 findBy 메서드명을 정의해야 한다.
처음에 함수 이름을 막 정의해서 사용하려다가 동작하지 않는 것을 보고 "왜 오류가 나지?" 했었는데,
알고 보니 JPA Repository에 맞게 작성하는 규칙이 존재했었다. 이 점 유의해야 할 듯 싶다.
https://joont92.github.io/jpa/Spring-Data-JPA/
[jpa] Spring Data JPA
Spring Data JPA는 스프링에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다. 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 세련된 방법으로 해결할 수 있게 해준다. CRUD 처
joont92.github.io
위 블로그에 어떤 방식으로 동작하는지 잘 기술되어 있다.
AnswerService
// AnswerService.java
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.ArrayList;
import java.util.List;
...
public Page<Answer> getList(Question question, int page) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("Voter"));
sorts.add(Sort.Order.asc("createDate"));
Pageable pageable = PageRequest.of(page,5, Sort.by(sorts)); // 5개씩 끊어서 처리
return this.answerRepository.findAllByQuestion(question, pageable);
}
- 답변 목록은 한 페이지에 최대 5개까지 표시되도록 처리
- 정렬은 추천 수 기준으로 먼저 내림차순 정렬, 동일한 추천 수인 경우 작성 일자로 오름차순 정렬.
- question, pageable 두 개의 매개변수를 받아서 처리한다.
QuestionController
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.data.domain.Page;
import com.example.sbb.answer.Answer;
import com.example.sbb.answer.AnswerService;
...
@GetMapping(value = "/detail/{id}")
public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm, @RequestParam(value="page", defaultValue = "0") int page){
Question question = this.questionService.getQuestion(id);
Page<Answer> paging = this.answerService.getList(question ,page);
model.addAttribute("paging", paging);
model.addAttribute("question", question);
return "question_detail";
}
...
}
- /question/detail에서 답변이 표시되기 때문에 해당 링크에서 처리하도록 수정
question_detail
<!-- 답변의 갯수 표시 -->
<h5 class="border-bottom my-3 py-2"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<!-- 답변 반복 시작 -->
<div class="card my-3" th:each="answer : ${paging}">
...
<!-- 답변 반복 끝 -->
<!-- 답변 작성 -->
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled th:field="*{content}" class="form-control" rows="10"></textarea>
<textarea sec:authorize="isAuthenticated()" th:field="*{content}" class="form-control" rows="10"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
<!-- 페이징처리 시작 -->
<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>
<!-- 페이징처리 끝 -->
<form th:action="@{/question/list}" method="get" id="searchForm">
<input type="hidden" id="page" name="page" th:value="${paging.number}">
</form>
....
const page_elements = document.getElementsByClassName("page-link");
Array.from(page_elements).forEach(function(element) {
element.addEventListener('click', function() {
document.getElementById('page').value = this.dataset.page;
document.getElementById('searchForm').submit();
});
});
...
- list가 정상적으로 출력되지 않거나, 500 에러의 경우 html에서 스크립트 처리 및 form이나 클래스의 이름이 정확한지 확인해야 한다.
혹시나 쿼리를 이용하여 처리하고 싶다면 아래와 같이 처리하면 된다.
AnswerRepository
@Query(value="select "
+ "distinct a.*, count(av.answer_id) as voter_count "
+ "from answer a "
+ "left outer join answer_voter av on av.answer_id=a.id "
+ "where a.question_id = :questionId "
+ "group by a.id, av.answer_id "
+ "order by voter_count desc, a.create_date desc "
, countQuery = "select count(*) from answer"
, nativeQuery = true)
Page<Answer> findAllByQuestion(@Param("questionId") Integer questionId, Pageable pageable);
참고
점프투스프링부트 개선 | 답변 페이징과 정렬
첫번째 추가 기능인 답변 페이징 및 정렬을 구현했다. 일단 질문 목록을 페이징하는 과정과 비슷하게 답변 목록을 페이징 처리 해줬다. 간단한 기능이라 생각했는데 생각보다 쩔쩔맸다. 답변을
velog.io
https://pybo.kr/pybo/question/detail/1369/
[점프 투 스프링부트] 답변에 페이징이 쉽지가 않습니다. - 파이보
1번 방법. AnswerList와 Repository를 사용하는 방법 실패 사유 : Answer 엔티티에서 Question 엔티티를 참조한 속성명이므로 QuestionRepository에서 사용 불가 2번 방법. Answer 엔티티에서 Page 기능 삽입 실패 사
pybo.kr
https://pybo.kr/pybo/question/detail/1531/
답변 페이징 후에 앵커 질문드립니다. - 파이보
답변 페이징을 하고 난 후에 앵커가 페이지 별로 되질 않아서 혼자 여러방법 시도해보다가 성공했는데 제가 보기엔 편법처럼 보여서 방법이 별로인지 질문드립니다. 만약 별로라면 자그마한 힌
pybo.kr
'개발 공부 > Spring Boot' 카테고리의 다른 글
Spring Boot - 댓글 (0) | 2023.06.13 |
---|---|
Spring Boot - VS Code로 개발환경 설정하기 (1) | 2023.05.05 |