[Spring Data JPA] 페이징과 정렬

Hayoon·2023년 7월 18일
0

Spring 정리

목록 보기
6/11

과거 프로젝트를 진행했을 때, 클라이언트 단에 페이지네이션을 위해 Page 객체를 직접 만들어 구현했었다.
다음은 내 프로젝트 Page 객체 코드이다. (직접 구현해보았지만, 원하는 대로 오프셋이 맞추어지지 않고 페이지 크기가 제멋대로였다.)

public class Pagination {

    private int totalRecordCount; // 전체 데이터 수
    private int totalPageCount; // 전체 페이지 수
    private int startPage; // 첫 페이지 번호
    private int endPage; // 끝 페이지 번호
    private int limitStart; // LIMIT 시작 위치
    private boolean existPrevPage; // 이전 페이지 존재여부
    private boolean existNextPage; // 다음 페이지 존재여부

    public Pagination(int totalRecordCount, SearchDto params) {
        if(totalRecordCount > 0) {
            this.totalRecordCount = totalRecordCount;
            calculation(params);
        }
    }

    private void calculation(SearchDto params) {
        // 전체 페이지 수 계산
        totalPageCount = ((totalRecordCount - 1) / params.getRecordSize()) + 1;
        if(params.getPage() > totalPageCount) {
            params.setPage(totalPageCount);
        }
        // 첫 페이지 번호 계산
        startPage = ((params.getPage() - 1) / params.getPageSize()) * params.getPageSize() + 1;

        // 끝 페이지 번호 계산
        endPage = startPage + params.getPageSize() - 1;

        // 끝 페이지가 전체 페이지 수보다 큰 경우, 끝 페이지 전체 페이지 수 저장
        if(endPage > totalPageCount) {
            endPage = totalPageCount;
        }

        // LIMIT 시작 위치 계산
        limitStart = (params.getPage() - 1) * params.getRecordSize();

        // 이전 페이지 존재 여부 확인
        existPrevPage = startPage != 1;

        // 다음 페이지 존재 여부 확인
        existNextPage = (endPage * params.getRecordSize()) < totalRecordCount;
    }
}

전체 데이터 수(totalElements), 전체 페이지 수(totalPages), 페이지 번호(pageNumber), 페이지 크기(pageSize) 등 구글링을 통해, 고대 개발자들의 유산을 재활용 중이었다. 이전, 다음페이지의 오프셋을 일일이 계산해야 하는 선배 개발자들의 피땀눈물이다.

그러던 중, 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있다는 걸 알게 되었다.

페이징과 정렬 예제

@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    return page;
}

API EndPoint에는 파라미터를 매핑하는 쿼리나 Value가 전혀 없다. 하지만 Pageable이 PageRequest 객체를 생성한다.

그래서 어떻게 쓰는데?

Test 하기 전 Member 100명을 DB에 Insert 하고 다음 메서드 실행.

http://localhost:8080/members?page=0&size=3&sort=id // Postman에서 get 메서드 "send"

페이지 번호 2, 페이지 별 노출할 데이터 건수 3, member_id 오름차순 정렬로 설정했다.
page: 현재 페이지, 0부터 시작한다.

{
    "content": [
        {
            "id": 7,
            "username": "user6",
            "teamName": null
        },
        {
            "id": 8,
            "username": "user7",
            "teamName": null
        },
        {
            "id": 9,
            "username": "user8",
            "teamName": null
        }
    ],
    "pageable": {
        "sort": {
            "unsorted": false,
            "sorted": true,
            "empty": false
        },
        "pageNumber": 2,
        "pageSize": 3,
        "offset": 6,
        "paged": true,
        "unpaged": false
    },
    "totalPages": 34,
    "totalElements": 100,
    "last": false,
    "numberOfElements": 3,
    "size": 3,
    "number": 2,
    "sort": {
        "unsorted": false,
        "sorted": true,
        "empty": false
    },
    "first": false,
    "empty": false
}

Page 관련 모든 정보를 볼 수 있다. (여태 힘들게 다 구현했었는데...)
쿼리 조건으로 하지 않고, @PageableDefalut를 사용하여 파라미터 안에서 설정도 가능하다.
부가적으로 .map()을 활용하여 엔티티를 직접 노출하지 않고, 엔티티를 Dto로 감싸야 한다. 외부에 노출 시 중요한 데이터 노출 및 도메인 스펙 자체가 바뀔 수 있기 때문이다.

@GetMapping("/members")
    public Page<MemberDto> list(@PageableDefault(size = 4, sort = "username") Pageable pageable) {
        return memberRepository.findAll(pageable)
                .map(MemberDto::new);
    }

시작 Page를 0이 아닌 1로 하고 싶으면 PageRequest class를 오버라이딩하여 사용해야 한다.
PageRequest는 정적 팩토리 메서드 of를 사용하여 인스턴스를 생성할 수 있다.

page : 0부터 시작하는 페이지 인덱스 번호
size : 한 페이지에 반환할 데이터의 개수
sort : 정렬 방식

참고

  • 인프런 김영한, 실전! 스프링 데이터 JPA
profile
Junior Developer

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

글이 많은 도움이 되었습니다, 감사합니다.

답글 달기