IT/Spring

Spring Data JPA 페이지네이션(Pagination) 처리

iamhyeon 2025. 7. 30. 15:55

 

웹 애플리케이션에서 데이터를 조회할 때, 수만 건 이상의 데이터를 한 번에 내려받는 것은 매우 비효율적이다.
이런 경우에 사용하는 대표적인 기법이 바로 페이지네이션(Pagination)이다.


페이지네이션이란?

 

페이지네이션은 대량의 데이터를 페이지 단위로 나누어 사용자에게 제공하는 방식이다.
예를 들어, 데이터베이스에 10,000건의 로그가 있을 때, 이를 한 번에 모두 내려받기보다 20개씩 나눠서 보여주는 방식이다.
이를 통해 메모리 사용을 줄이고, 사용자 경험을 개선하며, 네트워크 비용도 줄일 수 있다.


Spring Data JPA에서 페이지네이션 구성 요소

Spring Data JPA는 페이지네이션을 위해 여러 유틸리티를 기본 제공한다. 대표적으로 아래 3가지가 있다.

 

1. Pageable 인터페이스

페이지 요청 정보를 담는 인터페이스이다. 주요 메소드는 다음과 같다:

  • getPageNumber() : 요청한 페이지 번호 (0부터 시작)
  • getPageSize() : 페이지당 항목 수
  • getSort() : 정렬 정보
  • getOffset() : 건너뛸 데이터 수 (pageNumber * pageSize)

2. PageRequest 클래스

Pageable의 구현체로, 페이지 번호와 크기, 정렬 정보를 지정할 수 있다. 주로 of() 정적 메소드를 사용한다.

// 기본 페이지 요청
PageRequest.of(0, 10);

// 정렬 포함
PageRequest.of(0, 10, Sort.by("createdAt").descending());

// 복합 정렬
PageRequest.of(0, 10, Sort.by("createdAt").ascending().and(Sort.by("id").descending()));

3. Page<T> 인터페이스

쿼리 결과를 담는 객체로, 단순히 데이터만 담는 것이 아니라 페이지 정보도 함께 포함한다.

  • getContent() : 실제 데이터 리스트
  • getTotalElements() : 전체 데이터 개수
  • getTotalPages() : 전체 페이지 수
  • getNumber() : 현재 페이지 번호
  • getSize() : 페이지 크기
  • isFirst(), isLast() : 첫/마지막 페이지 여부
  • hasNext(), hasPrevious() : 다음/이전 페이지 여부

Pageable 생성 로직

// 전체 조회
PageRequest.of(0, Integer.MAX_VALUE, Sort.by("id").descending());

// 일반 페이지 조회
PageRequest.of(pageNumber, pageSize, Sort.by("id").descending());
  • Integer.MAX_VALUE를 사용하면 사실상 "모든 데이터를 한 페이지로 가져온다"는 뜻이다.
  • 물론 DB 성능상 위험할 수 있으므로 주의가 필요하다.

 


✅ 클래스/인터페이스 관계 요약

org.springframework.data.domain.Pageable (인터페이스)
        ↑
org.springframework.data.domain.AbstractPageRequest (추상 클래스)
        ↑
org.springframework.data.domain.PageRequest (구현 클래스)
  • Pageable
    • public interface Pageable
    • 페이지 요청 정보를 담는 인터페이스 (메소드: getPageNumber(), getPageSize(), getSort(), getOffset() 등).
  • PageRequest
    • public class PageRequest extends AbstractPageRequest
    • AbstractPageRequest를 상속한 기본 구현체로, Pageable 타입의 객체를 생성할 때 사용.
      정적 팩토리 메소드 of(...)를 통해 인스턴스를 생성함 
  • AbstractPageRequest
    • public abstract class AbstractPageRequest implements Pageable, Serializable 
    • 기본적인 구현 로직을 제공하는 추상 클래스 (Pageable 인터페이스를 구현).

 


 

예시 )

✅ 1. Entity – Post

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    private String author;
    private LocalDateTime createdAt;

    // getters, setters, constructors
}

✅ 2. Repository – PostRepository

public interface PostRepository extends JpaRepository<Post, Long> {

    // 기본 제공되는 findAll(Pageable pageable) 사용 가능
    Page<Post> findByAuthor(String author, Pageable pageable);
}

✅ 3. Service – PostService

@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    public Page<Post> getPosts(int page, int size) {
        Pageable pageable = page < 0 || size <= 0
                ? PageRequest.of(0, Integer.MAX_VALUE, Sort.by("createdAt").descending())
                : PageRequest.of(page, size, Sort.by("createdAt").descending());

        return postRepository.findAll(pageable);
    }
}

✅ 4. DTO – PostDto

public class PostDto {

    private Long id;
    private String title;
    private String content;
    private String author;
    private LocalDateTime createdAt;

    public PostDto(Post post) {
        this.id = post.getId();
        this.title = post.getTitle();
        this.content = post.getContent();
        this.author = post.getAuthor();
        this.createdAt = post.getCreatedAt();
    }

    // getters
}

✅ 5. Controller – PostController

@RestController
@RequestMapping("/api/posts")
public class PostController {

    private final PostService postService;

    public PostController(PostService postService) {
        this.postService = postService;
    }

    @GetMapping
    public ResponseEntity<Page<PostDto>> getPosts(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {

        Page<Post> postPage = postService.getPosts(page, size);
        Page<PostDto> result = postPage.map(PostDto::new);

        return ResponseEntity.ok(result);
    }
}

✅ 6. 결과 예시 (JSON 응답)

{
  "content": [
    {
      "id": 1,
      "title": "Spring JPA 페이징 예제",
      "content": "페이지네이션은 중요한 성능 최적화 전략입니다.",
      "author": "jane",
      "createdAt": "2025-07-30T14:12:00"
    }
  ],
  "pageable": {
    "pageNumber": 0,
    "pageSize": 10,
    "offset": 0,
    "paged": true
  },
  "totalPages": 5,
  "totalElements": 45,
  "last": false,
  "first": true,
  "numberOfElements": 10,
  "empty": false
}

 

Pageable 페이지 요청 정보 (번호, 크기, 정렬 등)
PageRequest Pageable의 구현체로 정렬 포함 가능
Page<T> 실제 데이터 + 메타정보 (전체 개수, 페이지 수 등)
.map(Dto::new) 엔티티 → DTO 변환

 

반응형