Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20240822 페이지네이션(Pagination) 본문

개발Article

[TIL]20240822 페이지네이션(Pagination)

최밤빵 2024. 8. 22. 23:13

🤓페이지네이션(Pagination) 구현하기

페이지네이션(Pagination)은 웹 애플리케이션에서 많은 양의 데이터를 한 번에 모두 표시하지 않고, 페이지별로 나누어 사용자에게 데이터를 제공하는 방법이다. 이는 서버와 클라이언트의 성능을 최적화하는 데 필수적인 기술로,오늘 개발일지에서는 페이지네이션의 개념, 필요성, 구현 방법, 그리고 고려해야 할 사항들을 정리해봤다! 

 

▶ 페이지네이션의 개념과 필요성

페이지네이션은 데이터의 양이 많아질수록 웹 페이지의 성능과 사용성에 직접적인 영향을 미치는 중요한 개념이다. 예를 들어, 수천 개의 데이터 를 한 번에 불러와 화면에 출력하게 되면, 사용자가 페이지를 로드하는 데 시간이 오래 걸리고, 브라우저가 멈추는 등의 문제가 발생할 수 있다. 페이지네이션을 적용하면 데이터는 여러 페이지로 나뉘어 표시되며, 사용자는 페이지를 넘기면서 더 많은 데이터를 확인할 수 있게 된다. 이러한 방식은 특히 게시판, 쇼핑몰 상품 리스트, 뉴스 피드 등과 같은 웹 애플리케이션에서 자주 사용된다.

 

▶페이지네이션이 필요한 이유?

→ 성능 최적화:

한 번에 모든 데이터를 가져오지 않기 때문에 서버와 클라이언트 모두의 성능이 향상된다.

→ 사용자 경험 개선:

페이지 로딩 시간이 줄어들고, 사용자는 데이터를 더 쉽게 탐색할 수 있다.

→ 데이터 전송량 감소:

필요한 데이터만 서버에서 클라이언트로 전송하기 때문에 네트워크 대역폭을 절약할 수 있다.

 

▶ 페이지네이션의 기본 개념

페이지네이션을 구현하기 위해서는 몇 가지 개념을 이해해야 한다.

→ 페이지 번호 (Page Number):

현재 사용자가 보고 있는 페이지의 번호로, 보통 1부터 시작하며, 사용자가 다음 페이지로 이동할 때마다 증가한다.

→ 페이지 크기 (Page Size):

한 페이지에 표시할 데이터의 수. 예를 들어, 한 페이지에 10개의 게시글을 보여주기로 설정하면, 페이지 크기는 10이 된다.

→ 전체 데이터 수 (Total Items):

전체 데이터 레코드의 수 데이터베이스에서 특정 조건에 따라 데이터를 조회할 때 이 값이 사용된다.

→ 전체 페이지 수 (Total Pages):

전체 데이터를 페이지 크기로 나눈 값. 이 값은 전체 데이터 수 / 페이지 크기로 계산된다.

 

▶ 페이지네이션 구현 방법

페이지네이션을 구현하는 방법은 백엔드와 프론트엔드에서 모두 다를 수 있다. 나는 백엔드를 공부하고있기때문에 백엔드에서 페이지네이션을 구현하는 방법을 정리했다. 가장 일반적으로 사용되는 방법은 SQL의 LIMIT과 OFFSET을 활용하는 것으로,  필요한 데이터만 선택적으로 가져올 수 있다.

 

▽ MySQL을 사용하여 데이터베이스에서 users 테이블의 데이터를 페이지네이션으로 가져온다고 가정했을 때,

SELECT * FROM users LIMIT 10 OFFSET 0;  -- 1페이지 데이터를 가져옴 (페이지 크기: 10)
SELECT * FROM users LIMIT 10 OFFSET 10; -- 2페이지 데이터를 가져옴
SELECT * FROM users LIMIT 10 OFFSET 20; -- 3페이지 데이터를 가져옴

LIMIT은 한 번에 가져올 데이터의 수를 지정하고, OFFSET은 건너뛸 데이터의 수를 지정한다. 이를 통해 원하는 페이지의 데이터만 조회할 수 있게 된다.

 

▶ Spring Boot에서의 페이지네이션 구현

Spring Boot와 JPA(Hibernate)를 사용하여 페이지네이션을 구현하는 예시. Spring Data JPA는 기본적으로 페이지네이션을 지원하고, PageablePage 인터페이스를 제공한다.

 

▽ User 엔티티와 UserRepository

package com.example.paginationdemo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
 
package com.example.paginationdemo.repository;

import com.example.paginationdemo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findAll(Pageable pageable);
}
UserService와 UserController를 통해 페이지네이션을 구현
package com.example.paginationdemo.service;

import com.example.paginationdemo.model.User;
import com.example.paginationdemo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public Page<User> getUsers(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
}
package com.example.paginationdemo.controller;

import com.example.paginationdemo.model.User;
import com.example.paginationdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public Page<User> getUsers(@RequestParam(defaultValue = "0") int page,
                               @RequestParam(defaultValue = "10") int size) {
        return userService.getUsers(page, size);
    }
}

→ 예시 코드는 간단한 페이지네이션 API를 구현했다.. 클라이언트가 GET /users?page=1&size=10 요청을 보내면, 1페이지에 해당하는 10개의 사용자 데이터를 반환한다.

 

▶ 페이지네이션을 구현할 때 고려해야 할 사항

→ 성능 최적화:

데이터 양이 많아질수록 OFFSET의 성능이 떨어질 수 있다. 대규모 데이터를 다룰 때는 Keyset Pagination이나 Seek Pagination 등의 기술을 고려해야 한다.

 

▽ Keyset PaginationSeek Pagination

구분 Keyset Pagination Seek Pagination
기본 원리 마지막으로 조회된 데이터의 키 값(ex: id)을 기준으로 다음 페이지를 가져온다. 지정된 위치로부터 데이터를 찾고, 주어진 크기만큼 데이터를 가져온다.
사용 사례 단일 컬럼(주로 id)을 기준으로 다음 데이터를 조회할 때 사용. 복합 키나 여러 조건을 기준으로 페이지네이션을 수행할 때 사용.
성능 대량의 데이터에서도 빠른 성능을 제공하며, 데이터의 크기에 비례해 성능 저하가 거의 없다. Keyset Pagination과 유사하게 빠른 성능을 제공하지만, 더 복잡한 조건을 처리하는 데 적합하다.
쿼리 예시 SELECT * FROM users WHERE id > ? ORDER BY id ASC LIMIT 10; SELECT * FROM users WHERE (id, name) > (?, ?) ORDER BY id, name ASC LIMIT 10;
장점 단순하고 직관적이며, OFFSET을 사용하지 않아 데이터가 많을 때도 효율적이다. 복합 조건에 대해 더 강력한 성능을 제공하고, 다양한 필터 조건과 함께 사용할 수 있다.
단점 단일 조건 기준의 페이징만 가능하고, 복합 조건이 필요할 때는 복잡도가 증가할 수 있다. 사용이 다소 복잡하고, 여러 조건을 기반으로 페이지네이션을 설계해야 하므로 쿼리가 복잡해질 수 있다.
장점이 있는 경우 단순히 id와 같은 기본 키를 기준으로 데이터 목록을 페이지네이션할 때 적합하다. 여러 조건을 기준으로 정렬하거나 필터링된 데이터를 페이지네이션할 때, 예를 들어 id와 name을 기준으로 할 때.

→ 사용자 경험:

페이지네이션 UI를 설계할 때는 사용자가 쉽게 페이지를 이동하고 데이터를 탐색할 수 있도록 해야 한다. 페이지 번호, 다음/이전 버튼, 검색 기능 등을 고려할 수 있다.

→ 비동기 요청 처리:

프론트엔드에서는 AJAX를 사용하여 비동기적으로 페이지를 전환하면 더 나은 사용자 경험을 제공할 수 있다.

→ 캐싱:

동일한 데이터에 대한 반복된 요청을 줄이기 위해 캐싱을 적용할 수 있다. 이를 통해 서버 부하를 줄이고 응답 시간을 단축할 수 있다.

 

▶결론

페이지네이션은 웹 애플리케이션에서 성능을 최적화하고, 사용자 경험을 개선하기 위해 반드시 필요한 기능이다. Spring Boot와 JPA를 활용하면 쉽게 페이지네이션을 구현할 수 있고, 페이지네이션을 적용할 때는 성능 최적화와 사용자 경험을 함께 고려해야 한다. 페이지네이션을 제대로 이해하고 활용한다면, 대규모 데이터를 효율적으로 관리할 수 있을 것이다.