본문 바로가기
개발 공부/Spring Boot

Spring Boot - 답변 페이징

by 깐테 2023. 5. 30.

https://wikidocs.net/book/7601

 

점프 투 스프링부트

점프 투 스프링부트는 Spring Boot Board(SBB)라는 이름의 게시판 서비스를 만들어가는 과정을 설명한 스프링부트 입문서이다. 자바 설치부터 시작하여 서비스 운…

wikidocs.net

"점프 투 스프링부트" 위키독스의 항목 중, 추가 개발 항목에서 답변 페이징 기능에 해당하는 내용이다.

 


답변 페이징

https://wikidocs.net/162028

 

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);

 

 

 

참고

https://velog.io/@jea5158/%EC%A0%90%ED%94%84%ED%88%AC%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EA%B0%9C%EC%84%A0-%EB%8B%B5%EB%B3%80-%ED%8E%98%EC%9D%B4%EC%A7%95%EA%B3%BC-%EC%A0%95%EB%A0%AC

 

점프투스프링부트 개선 | 답변 페이징과 정렬

첫번째 추가 기능인 답변 페이징 및 정렬을 구현했다. 일단 질문 목록을 페이징하는 과정과 비슷하게 답변 목록을 페이징 처리 해줬다. 간단한 기능이라 생각했는데 생각보다 쩔쩔맸다. 답변을

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