[SpringBoot] 게시판 페이지네이션 구현하기[6]

Euiyeon Park·2025년 2월 10일
post-thumbnail

✨ 구현을 통한 학습 목표

  1. ✅ createdAt 컬럼 사용하기
  2. ✅ 정렬 기능
  3. ✅ 검색 기능
  4. ✅ 최대로 조회 가능한 데이터 개수 제한

✨ Pagination

  • Pagination은 대량의 데이터를 한 번에 불러오지 않고, 일정한 크기로 나눠 순차적으로 조회하는 기법

Pagination의 필요성

  1. 대량 데이터를 한 번에 불러오면 속도가 느려지고 서버 부하가 증가
  2. 필요한 데이터만 전송해 네트워크 트래픽을 줄일 수 있음
  3. 한 번에 많은 데이터를 표시하면 가독성이 저하되고 UI가 복잡해짐

✨ Spring Data JPA의 Pagination

💫 Pageable

  • Pageable은 페이징과 정렬 정보를 캡슐화하는 인터페이스
  • 클라이언트가 요청하는 페이징 정보를 담는 객체
    • page : 페이지 번호
    • size : 페이지 크기, 한 페이지에 포함된 데이터 개수
    • sort : 정렬 조건
  • 주로 구현체인 PageRequestof() 메서드를 사용해 pageable을 생성
	Pageable pageable = PageRequest.of(0, 10);

💫 Page<T>

  • Page<T>는 페이징된 데이터와 페이징 정보를 포함하는 객체
  • 단순히 데이터 리스트만 반환하는 것이 아니라 총 개수, 전체 페이지 수, 현재 페이지 번호 등의 정보를 제공
  • Pageable을 바탕으로 페이징된 데이터와 정보를 반환
	Pageable pageable = PageRequest.of(0, 10);
    Page<Item> page = itemRepository.findAll(pageable);

💡 Slice<T>

  • Page<T>인터페이스는 Slice<T>인터페이스를 상속한다.
  • Slice<T>는 데이터 일부를 나타내는 개념으로, 일반적인 페이징 기능과 유사하지만
    전체 페이지 수를 알 필요 없이 다음/이전 페이지가 있는지 여부만 확인 가능하다.
  • Slice<T> 는 전체 데이터 개수를 조회하는 추가 쿼리(COUNT(*))를 실행하지 않기 때문에 성능이 더 좋다.

💡 PagedModel

  • PagedModel<T>Page<T> 객체를 JSON으로 안정적으로 직렬화하기 위한 DTO다.
  • 컨트롤러나 서비스 레이어에서 직접 new PagedModel<>(page)를 호출하여 사용한다.

📍 PagedModel로 Response를 내려준 경우

{
    "content": [
        {
            "id": 5,
            "title": "제목5",
            "content": "내용5",
            "createdAt": "2025-02-08T21:40:53.042102"
        },
        {
            "id": 4,
            "title": "제목4",
            "content": "내용4",
            "createdAt": "2025-02-08T21:40:49.318999"
        },
        {
            "id": 3,
            "title": "제목3",
            "content": "내용3",
            "createdAt": "2025-02-08T21:40:45.312505"
        },
        {
            "id": 2,
            "title": "제목2",
            "content": "내용2",
            "createdAt": "2025-02-08T21:40:41.082873"
        },
        {
            "id": 1,
            "title": "제목1",
            "content": "내용1",
            "createdAt": "2025-02-08T21:40:31.956687"
        }
    ],
    "page": {
        "size": 15,
        "number": 0,
        "totalElements": 5,
        "totalPages": 1
    }
}

📍 Page로 Response를 내려준 경우

{
    "content": [
        {
            "id": 5,
            "title": "제목5",
            "content": "내용5",
            "createdAt": "2025-02-08T21:40:53.042102"
        },
        {
            "id": 4,
            "title": "제목4",
            "content": "내용4",
            "createdAt": "2025-02-08T21:40:49.318999"
        },
        {
            "id": 3,
            "title": "제목3",
            "content": "내용3",
            "createdAt": "2025-02-08T21:40:45.312505"
        },
        {
            "id": 2,
            "title": "제목2",
            "content": "내용2",
            "createdAt": "2025-02-08T21:40:41.082873"
        },
        {
            "id": 1,
            "title": "제목1",
            "content": "내용1",
            "createdAt": "2025-02-08T21:40:31.956687"
        }
    ],
    "pageable": {
        "pageNumber": 0,
        "pageSize": 15,
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
    },
    "last": true,
    "totalPages": 1,
    "totalElements": 5,
    "size": 15,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 5,
    "empty": false
}
  • 한 눈에 봐도 PagedMoel이 훨씬 깔끔하다. 불필요한 정보들을 걷어낸 느낌? 애용하자

✨ Pagination 구현

📂 controller.java

@GetMapping("/posts")
public PagedModel<PostResponse> getAllPosts(@RequestParam(name = "page", defaultValue = "1") int page,
                                            @RequestParam(name = "size", defaultValue = "15") int size) {
   if(size > 100){ size = 100; }
   Pageable pageable = PageRequest.of(page-1, size, Sort.by("createdAt").descending());
   return postService.getAllPosts(pageable);
}

📂 service.java

@Transactional(readOnly = true)
public PagedModel<PostResponse> getAllPosts(Pageable pageable){
   Page<PostResponse> postResponses = postRepository.findAll(pageable)
           .map(PostResponse::new);

  	return new PagedModel<>(postResponses);
}

📂 repository.java

Page<Post> findAll(Pageable pageable);

😡 역시나 코드가 띠껍다 ..!

❗ 검색 기능과 마찬가지로 페이징 처리한 코드 너무 띠껍다

왜인진 잘 모르겠으나 그냥 느낌적인 느낌 ..
그래도 곰곰히 이 불편한 느낌에 대한 이유를 꼽아보자면

  1. 이전에 토이 프로젝트에서 사용했던 페이징 처리 코드를 재사용해서 그런거 같다.
  • 뭔가 .. 페이징에 관련된 내용을 더 학습해보지 않아서 ..?
    스스로에 대한 만족감이 부족하달까
  1. 컨트롤러에서 꼭 @RequestParam으로 pagesize를 받아야 할까?
  • 이것도 1번과 일맥상통한데, 코드에 대한 고민이 없어서 그런가
  • pagesize를 따로 받지 않고 그냥 Pageable로 받아도 되지 않나?
  • 그리고 "데이터 조회 개수는 최대 100까지" 라는 요구사항으로 인해
    아래 코드를 사용한게 맘에 안든다 ..
if(size > 100){ size = 100;

❗ 결론은 리팩토링

다른 사람 코드 좀 보고싶다
더 나은게 있으면 차용 좀 하게

profile
"개발자는 해결사이자 발견자이다✨" - Michael C. Feathers

0개의 댓글