본문 바로가기

Back-End 공부/Spring

[스프링] Paging 기능 구현

[ PostRepository.java ]

findAll메서드에 반환타입, 매개변수 추가해 Paging 처리를 시작하자.

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    //List<Post> findAllByOrderByCreatedTimeDesc();
    //Pageable 객체 : pageNumber(page=1), page마다 개수(size=10), 정렬(sort=createdTime, desc)
    //Page : List<Post> + 해당 Page의 각종 정보
    Page<Post> findAll(Pageable pageable);
}

 

 

이때, Pageable 객체는 아래와 같은 정보들을 계층구조로 가진다. ⭐

pageable : Page 객체를 조회하기 위한 매개변수

"pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 3,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": true,
    "totalElements": 3,
    "totalPages": 1,
    "size": 3,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 3,
    "empty": false

 

 

Page 객체는 위 Pageable의 정보들에 실제 데이터들의 정보가 합쳐진 것이다. ⭐

content와 pageable, last, totalElements, totalPages, size, number, sort는 같은 level이라는 것을 기억해두자.

{
    "content": [
        {
            "id": 17,
            "title": "test1이 쓰는 글",
            "author_email": "test1@naver.com"
        },
        {
            "id": 7,
            "title": "cascade test",
            "author_email": "익명유저"
        },
        {
            "id": 1,
            "title": "hi",
            "author_email": "익명유저"
        }
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 3,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": true,
    "totalElements": 3,
    "totalPages": 1,
    "size": 3,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 3,
    "empty": false
}

 

 

 

[ PostController.java ]

@PageableDefault의 size, sort, direction 옵션을 통해 한 페이지에 나오는 게시글 수와 정렬 기준을 정할 수 있다.

model에 findAllPaging()에서 반환된 Page<PostListResDto> 객체를 함께 넘겨줘야 한다!

@GetMapping("post/list")
public String postList(Model model, @PageableDefault(size=5, sort="createdTime", direction = Sort.Direction.DESC) Pageable pageable) {
    model.addAttribute("postList", postService.findAllPaging(pageable));
    return "post/post-list";
}

 

 

 

[ PostService.java ]

기존 findAll()의 반환타입이었던 List<PostListResDto>를 Page<PostListResDto>로 변경해주면서 발생하는 순환참조 문제를 풀어줘야 한다. 

하지만 Page<PostListResDto>는 for문 돌려서 처리하기가 힘들기 때문에 .map 메서드를 이용해 처리해줘야 한다!!

public Page<PostListResDto> findAllPaging(Pageable pageable){
    Page<Post> posts = postRepository.findAll(pageable);
    Page<PostListResDto> PostListResDtos
            = posts.map(post -> new PostListResDto(post.getId(), post.getTitle(), post.getAuthor()==null?"익명유저":post.getAuthor().getEmail()));
    return PostListResDtos;
}

 

 

 

[ post-list.html ]

테이블 밑에 페이지 번호 출력하는 부분 추가

<table class="table">
    <thead>
    <tr>
        <th>id</th>
        <th>title</th>
        <th>작성자 EMAIL</th>
        <th>#</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each = "post : ${postList}">
        <td th:text="${post.id}"></td>
        <td th:text="${post.title}"></td>
        <td th:text="${post.author_email}"></td>
        <td><a th:href="@{|/post/detail/${post.id}|}">상세보기</a></td>
    </tr>
    </tbody>
</table>
<ul class="pagination justify-content-center">
    <li class="page-item" th:each="pageNum : ${#numbers.sequence(0, postList.totalPages-1)}">
        <a class="page-link" th:href="@{/post/list(page=${pageNum})}" th:text="${pageNum+1}"> </a>
    </li>
</ul>

 

 

 

 

 

post-list.html에 한 줄 추가하면 사용자가 누른 페이지 번호에만 불이 들어온다.

<li class="page-item" th:each="pageNum : ${#numbers.sequence(0, postList.totalPages-1)}"
th:classappend="${pageNum == postList.pageable.pageNumber} ? 'active'">

 

 

 

 

1page를 눌렀을 때와, 2page를 눌렀을 때 limit쿼리를 비교해보자.

1page는 처음부터 size 개수만큼만 가져오면 되기 때문에 ?에 개수만 담아 넘기면

0번째 값부터 ?개만큼 가져와 1page에 띄워준다.

 

 

아래 5개 쿼리들은 실제 DB에서 가져와야 할 값이 몇 개인지 세고, 실제로 값을 가져오는 쿼리.

 

 

맨 마지막줄에 출력된 쿼리는 2page를 눌렀을 때 발생하는 쿼리이다.

?, ? 이 의미하는 것은 시작행, 개수이며, 1page에서 출력된 데이터 다음 값부터 size 개수만큼 가져온다.

Hibernate: select post0_.id as id1_1_, post0_.author_id as author_i6_1_, post0_.contents as contents2_1_, post0_.created_time as created_3_1_, post0_.title as title4_1_, post0_.updated_time as updated_5_1_ from post post0_ order by post0_.created_time desc limit ?
Hibernate: select count(post0_.id) as col_0_0_ from post post0_
Hibernate: select author0_.id as id1_0_0_, author0_.created_time as created_2_0_0_, author0_.email as email3_0_0_, author0_.name as name4_0_0_, author0_.password as password5_0_0_, author0_.role as role6_0_0_, author0_.updated_time as updated_7_0_0_ from author author0_ where author0_.id=?
Hibernate: select author0_.id as id1_0_0_, author0_.created_time as created_2_0_0_, author0_.email as email3_0_0_, author0_.name as name4_0_0_, author0_.password as password5_0_0_, author0_.role as role6_0_0_, author0_.updated_time as updated_7_0_0_ from author author0_ where author0_.id=?
Hibernate: select author0_.id as id1_0_0_, author0_.created_time as created_2_0_0_, author0_.email as email3_0_0_, author0_.name as name4_0_0_, author0_.password as password5_0_0_, author0_.role as role6_0_0_, author0_.updated_time as updated_7_0_0_ from author author0_ where author0_.id=?
Hibernate: select author0_.id as id1_0_0_, author0_.created_time as created_2_0_0_, author0_.email as email3_0_0_, author0_.name as name4_0_0_, author0_.password as password5_0_0_, author0_.role as role6_0_0_, author0_.updated_time as updated_7_0_0_ from author author0_ where author0_.id=?
Hibernate: select post0_.id as id1_1_, post0_.author_id as author_i6_1_, post0_.contents as contents2_1_, post0_.created_time as created_3_1_, post0_.title as title4_1_, post0_.updated_time as updated_5_1_ from post post0_ order by post0_.created_time desc limit ?, ?