Hyeok의 웹 개발 블로그

<2025.05.19> 페이지네이션, 무한스크롤 본문

TIL/Spring

<2025.05.19> 페이지네이션, 무한스크롤

Yhyeok 2025. 5. 19. 21:55

🔎 페이지네이션(Pagination)

  • 많은 데이터를 한 번에 다 보여주면 느려지니까, 적당히 나눠서 보여주는 방법
 

 🔎 무한스크롤(Infinite Scroll)

  • 스크롤을 내릴 때마다 자동으로 데이터를 더 보여주는 방식
 

 🔎 커서 기반 페이지네이션(Cursor Pagination)

  • "어디까지 봤는지 표시"해서 그 다음 데이터를 보여주는 방식
 

 🔎 오프셋 기반

  • 페이지 번호로 데이터를 나눠 보여줌 
SELECT * FROM item ORDER BY created_at DESC LIMIT 10 OFFSET 100000000;

이런 쿼리는 앞에 1억 개 데이터를 먼저 읽고 그 다음 10개를 준다.
점점 느려진다.

 

 커서 기반

"마지막으로 본 아이디(ID)"를 기억하고, 그 다음부터 보여준다.

예:

  • 1페이지: id 1~10 → 마지막은 id 10
  • 2페이지: WHERE id > 10 → id 11부터 다시 10개 보여줌!

 즉, 이미 본 데이터는 건너뛰고 바로 다음 것만 보여줍니다. 훨씬 빠름


 Spring에서 커서 기반 무한스크롤 구현 방법

1. 헬퍼 클래스 만들기 (ScrollPaginationCollection)

public class ScrollPaginationCollection<T> {
    private final List<T> itemsWithNextCursor; // 데이터 + 다음 거 1개
    private final int countPerScroll;

    public boolean isLastScroll() {
        return this.itemsWithNextCursor.size() <= countPerScroll;
    }

    public List<T> getCurrentScrollItems() {
        return isLastScroll() ? itemsWithNextCursor : itemsWithNextCursor.subList(0, countPerScroll);
    }

    public T getNextCursor() {
        return itemsWithNextCursor.get(countPerScroll - 1);
    }
}
  •  이 클래스는 다음 스크롤이 있는지 확인하고, 현재 보여줄 데이터만 잘라주는 역할을 합니다.
 

2. 서비스 로직에서 사용

public GetFeedsResponse getFeeds(String userEmail, Long roomId, int size, Long lastFeedId) {
    PageRequest pageRequest = PageRequest.of(0, size + 1); // 다음 페이지 있는지 확인하려고 +1
    Page<Feed> page = feedRepository.findAllByRoomAndIdLessThanOrderByIdDesc(room, lastFeedId, pageRequest);

    ScrollPaginationCollection<Feed> feedsCursor = ScrollPaginationCollection.of(page.getContent(), size);
    return GetFeedsResponse.of(feedsCursor, ..., ...);
}
  •   lastFeedId는 "지금까지 본 마지막 게시글 ID"
  • size + 1은 다음 페이지가 있는지 없는지 확인하려고 하나 더 가져온 것

 

3. 클라이언트 응답용 DTO

public class GetFeedsResponse {
    private List<FeedsInfoResponse> contents;
    private long totalElements;
    private long nextCursor;

    public static GetFeedsResponse of(...) {
        if (feedsScroll.isLastScroll()) {
            return newLastScroll(...); // 다음 거 없음
        }
        return newScrollHasNext(...); // 다음 커서 포함
    }
}
  • nextCursor == -1 → 마지막 페이지라는 뜻
  • 프론트에서는 이 값 보고 "더 이상 요청 안 함"

 예시 요청

  1. 최초 요청:
GET /feed?roomId=1&size=1&lastFeedId=9223372036854775807
  1. 응답:
{
  "contents": [ { "feedId": 20, ... } ],
  "nextCursor": 20
}
  1. 다음 요청:
GET /feed?roomId=1&size=1&lastFeedId=20

→ 이런 식으로 nextCursor → lastFeedId 로 계속 요청하며 무한 스크롤이 된다.

 

'TIL > Spring' 카테고리의 다른 글

<2025.06.04> 임베딩  (0) 2025.06.04
<2025.05.08> TestCode - 단위 테스트  (0) 2025.05.08
<2025.05.07> 연관관계 N+1  (0) 2025.05.07
<2025.05.01> 영속성 컨텍스트  (2) 2025.05.01
<2025.04.22> 배달 기능 프로젝트 피드백  (1) 2025.04.22