본문 바로가기

Spring

[Spring]기능 - 페이징 기능

페이징은 아주 많은 글이 있을 경우, ?개씩 묶어 출력시켜주는 기능을 얘기한다.

 

페이징 기능을 구현해보자.

 

1. 테스트 데이터 생성

많은 데이터가 필요하기에 테스트 데이터를 만들어보자.

 

이미 Test를 위한 데이터를 만드는 부분이 있으니 이를 이용해보자.

@Test
void 저장() {
    int lastId = 300;

    IntStream.rangeClosed(3, lastId).forEach(id -> {
        Question q = new Question();
        q.setSubject("%d번 질문".formatted(id));
        q.setContent("%d번 질문의 내용".formatted(id));
        q.setCreateDate(LocalDateTime.now());
        questionRepository.save(q);
    });
}

Stream을 사용해 테스트데이터 300개를 만들었다. 3번째부터 시작하는 이유는 이미 BeforeEach로 테스트데이터 2개가 새로 생성되었기 때문이다.

 

 

2. 10개씩 출력하기

이제 10개씩으로 묶어 출력할 수 있도록 모든 글들을 10개씩 묶어보자.

 

  (1) Page객체 사용 - QuestionRepository

Page<Question> findAll(Pageable pageable);

Page객체는 원하는 갯수의 Page를 묶어서 저장할 수 있는 객체이다.

 

 

  (2) Page객체 사용 - QuestionService

public Page<Question> getList(int page) {
    Pageable pageable = PageRequest.of(page, 10);
    return this.questionRepository.findAll(pageable);
}

기능을 위해 Service에 넣어준다.

 

PageRequest.of 에서 page는 조회할 페이지 번호, 10은 한 페이지마다 보여줄 게시물의 수를 의미한다. 그리고 Pageable 객체에 담아준다.

 

 

  (3) Page객체 사용 - QuestionController

@RequestMapping("/list")
public String list(Model model, @RequestParam(value="page", defaultValue="0") int page) {
    Page<Question> paging = this.questionService.getList(page);
    model.addAttribute("paging", paging);
    return "question_list";
}

QuestionService의 getList가 변경됨으로 인해 Controller도 변경해준다.

 

http://localhost:8080/question/list?page=0 에서 page값을 가져오기 위해 @RequestParam(value="page", defaultValue="0") int page 을 사용했다.

addAttribute를 사용해 paging된 객체를 question_list.html로 보내준다.

 

++ Page객체 속성

 

 

  (4) question_list.html 수정

addAttribute로 받아온 값이 변경되어 paging으로만 수정하면 된다.

<tr th:each="question, loop : ${paging}">

 

결과화면

 

 

3. Page 이동기능 생성

page를 이동할 수 있는 버튼이 있으면 좋지 않을까?

 

이를 위해 아래 코드를 삽입한다.

<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: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>
  • th:if : 조건문을 의미하며 addAttribute로 받아온 Page객체가 비어있는지 아닌지 확인힌다.
  • th:classappend : class를 추가하는 속성으로 ${!paging.hasPrevious} ? 를 사용해 이전 페이지의 유무를 확인하고, 없을 경우 class에 disabled 속성을 추가한다.
  • "@{|?page=${paging.number-1}|}" : 연결을 위한 것이다. 하지만, Link를 위한 String과 객체의 값을 함께 사용하고 싶을 경우 ‘|’ 를 양 옆에 추가한다.
  • ${#numbers.sequence(0, paging.totalPages-1)} : numbers.sequence(a, b)는 a부터 b까지의 모든 값들을 반복한다.

 

결과화면

 

 

4. 이동가능한 번호들을 전 후로 5개까지만 출력

이동가능한 page들이 너무 많아 보기가 굉장히 불편하다.

 

해결하기 위해 thymeleaf의 if문을 사용하여 page가 paging.numer의 5보다 작고 5보다 크거나 같은 값을 출력시켜주면 되지 않을까? 해서 나온 코드이다.

th:if="${page >= paging.number-5 and page <= paging.number+5}"

이 부분을 추가하면 깔끔하게 출력된다.

 

 

5. 역순 조회(늦게만든 게시물이 가장 위로)

게시물을 만들면 가장 아래로 내려간다. 이유는 findAll 메서드에서 select를 진행할 때 id순으로 정렬하기 때문이다.

이를 만들어진 순서로 조회한다면 가장 마지막에 만들어진 게시물이 가장 위로 올라가지 않을까?

public Page<Question> getList(int page) {
    List<Sort.Order> sorts = new ArrayList<>();
    sorts.add(Sort.Order.desc("createDate"));
    Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
    return this.questionRepository.findAll(pageable);
}

QuestionService의 getList 메서드를 수정한 것이다. 이는 정렬된 내용을 List에 담아 사용하는데, Sort.Order 객체를 사용한다. sorts.add(Sort.Order.desc("createDate")); 에서 createDate로 정렬된 Sort.Order 객체를 List에 넣고, PageRequest.of 메서드의 세번째 파라미터로 값을 넣어준다.

 

결과 화면