게시글 목록, 상품 리스트처럼 “전체 데이터 중 일부만 잘라서 보여주는” 기능은 대부분의 서비스에서 필수다. Spring Data JPA에서는 일반적으로 Page와 Pageable을 활용해서 페이징을 구현한다.
핵심은 간단하다. 클라이언트(프론트)가 page, size, sort 같은 조건을 쿼리 파라미터로 보내면, 백엔드는 그 조건에 맞는 “해당 구간 데이터 + 메타 정보”를 내려준다. 프론트는 응답에 포함된 totalPages 같은 정보를 이용해 페이지네이션 UI를 그리거나 다음 페이지를 요청한다.
okky커뮤니티의 페이징처리 예시
일반적인 페이지 기반 페이징은 전체 데이터에서 특정 구간을 슬라이싱해서 가져오는 구조다.
예를 들어 전체 데이터가 500개이고 page size를 10으로 두면:
page=0이면 0~9번째 데이터page=2이면 20~29번째 데이터즉, page=n일 때 가져오는 범위는 대략 아래처럼 이해하면 된다.
여기서 중요한 포인트는 Spring의 기본 page 값이 (0)부터 시작한다는 점이다.
(사용자에게 1페이지부터 보여주고 싶다면 프론트에서 1 기반으로 관리하고 서버 요청 시 0 기반으로 변환하거나, 서버에서 별도 처리를 한다.)
Spring에서는 페이징 처리를 지원하는 기능으로 Pageable을 제공한다. Pageable은 클라이언트가 보낸 page, size, sort 요청 파라미터를 “페이징 조건 객체”로 바인딩해주는 역할을 한다.
page: 현재 페이지 번호(기본값 0)size: 페이지당 데이터 수(기본값 20)sort: 정렬 기준(기본값 없음)즉, 컨트롤러 메소드 파라미터로 Pageable을 받기만 하면 스프링이 알아서 요청 파라미터를 매핑해준다.
요청 예시는 보통 이런 형태다.
/posts?page=0&size=5&sort=title,ascPage<T>는 특정 페이지의 조회 결과를 “데이터(content) + 페이징 메타 정보”로 묶어서 내려주는 리턴 타입이다. 프론트 입장에서 중요한 이유는, 단순 목록뿐 아니라 페이지네이션 UI에 필요한 정보(총 페이지 수, 총 개수 등)를 함께 받을 수 있기 때문이다.
Page에서 자주 쓰는 값은 아래와 같다.
content: 실제 조회된 데이터 목록(List)totalElements: 전체 데이터 수totalPages: 전체 페이지 수number: 현재 페이지 번호(0 기반)size: 페이지당 데이터 수first, last: 첫 페이지/마지막 페이지 여부numberOfElements: 현재 페이지에 담긴 원소 수아래처럼 컨트롤러에서 Pageable을 파라미터로 받고, 서비스에서 그대로 넘겨 Page를 반환하는 방식이 가장 흔하다.
// 요청 예시: localhost:8080/posts?page=0&size=5&sort=title,asc
// Pageable 바인딩은 스프링이 자동으로 처리
@GetMapping("/posts")
public Page<PostListDto> postListDto(
@PageableDefault(size = 10, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable
) {
return postService.findAll(pageable);
}
@PageableDefault: page/size/sort 기본값을 지정size, sort 등을 주면 기본값보다 요청 값이 우선 적용리포지토리는 보통 아래처럼 Pageable을 받아 Page를 반환한다.
Page<Post> findAll(Pageable pageable);
Page를 그대로 반환하면 Spring이 아래처럼 content와 메타 데이터를 포함한 JSON을 만들어준다(프로젝트 설정에 따라 필드 구성은 조금 다를 수 있음).
{
"content": [
{
"id": 6,
"category": "politics",
"title": "tttttt",
"authorEmail": "brad@naver.com"
},
{
"id": 5,
"category": "politics",
"title": "tttttt",
"authorEmail": "brad@naver.com"
},
{
"id": 4,
"category": "politics",
"title": "tttttt",
"authorEmail": "brad@naver.com"
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 5,
"sort": {
"empty": false,
"unsorted": false,
"sorted": true
},
"offset": 0,
"unpaged": false,
"paged": true
},
"last": true,
"totalElements": 5,
"totalPages": 1,
"size": 5,
"number": 0,
"sort": {
"empty": false,
"unsorted": false,
"sorted": true
},
"first": true,
"numberOfElements": 5,
"empty": false
}
프론트는 보통 다음처럼 활용한다.
content로 목록 렌더링totalPages, number, first, last로 페이지네이션 UI 구성last가 true일 때 추가 요청 중단무한 스크롤도 결국 “다음 페이지를 계속 요청해서 이어붙이는 방식”이라 백엔드 입장에서는 동일하다. 스크롤 이벤트 감지와 “언제 다음 페이지를 요청할지”는 프론트 책임이고, 백엔드는 page/size/sort 조건에 맞는 결과를 안정적으로 내려주는 역할에 집중하면 된다.