상기한 UI 요소를 '페이지네이션(Pagination)' 이라고 부른다. 수많은 데이터 목록은 한 번에 불러오고 보여주기 어려우니, 그 일부만 가져와서 보여주는 방법이다. 이를 위해 DB에 저장된 데이터를 잘 자르는 것을 '페이징(Paging)' 이라 한다.
'페이지 번호'
'페이지당 출력할 데이터의 수'
'화면 하단에 출력할 페이지의 사이즈'
'검색 키워드'
'검색 유형'
등등...
페이지네이션을 위해 필요한 정보는 아주 많을 수 있다. 그러나 이 글에서는 페이지 번호(page) 와 페이지당 출력할 데이터의 개수(size) 만 고려한 페이징에 대해 이야기하고자 한다.
물론 DB에서 모오든 데이터를 가져와서 (select * from table) 메모리상에서 순서대로 잘 끊어서 원하는 데이터만 골라낼 수도 있겠지만, 내가 설명하려는 페이징은 DB에서 원하는 데이터만 쿼리를 해오는 방법이다.
방법은 정말 여러가지이다. 그러나 본 게시물엔 필자가 이해한 것만 기록한다.
1. 직접 쿼리 만들어서 날리기
2. Spring-Data-Jpa 이용하기
MySQL 에서 LIMIT 구문은 데이터를 원하는 만큼 가져오고 싶을 때 사용한다.
LIMIT 의 첫 번째 파라미터는 시작위치를, 두 번째 파라미터는 가져올 데이터의 개수를 지정한다.
따라서 우리가 원하는 데이터는 LIMIT (page-1)*size, size
이다.
즉, page 와 size 가 주어졌을 때 SELECT * FROM table LIMIT (page-1)*size, size
쿼리를 날리면 원하는 데이터만 뽑아올 수 있다.
page=5, size=10 일때 쿼리예시
(spring 서버에 in-memory h2 db를 띄워서 콘솔에 직접 입력한 것이다.)
Spring Data Jpa 을 이용하면
(알아서 해줘서 편하다는 말)
인터페이스다!!
Spring Data Jpa 에 정의되어 있는 Paeable 인터페이스엔 다음과 같은 아주 짧은 설명 주석이 붙어있다.
Pageable 이 Pagination 요청 정보를 담기위한 추상 인터페이스 라는 의미이다. 실제로 쓰기 위해서는 구현체가 필요하고, 물론 다~ 준비되어있다.
이 중 가장 기본이 되는 PageRequest 를 가장 많이 사용하게 된다.
(사실 이것도 귀찮다고 또 Spring 에서 만들어놓은 어노테이션이 있다. 그래서 이것도 많이 안쓴다..)
page 와 size 정보로 어찌저찌 Pageable 객체를 만들었다고 치고 이걸 쿼리메서드와 사용해보자.
JpaRepository 와 CrudRepository 사이에 있는 PagingAndSortingRepository 를 보면 Pageable 을 파라미터로 받는 메서드가 선언되어있다.
(Page 는 Pagination 에 필요한 정보를 담는 객체다. 요청받은 데이터들 외에 전체페이지수, 현재페이지번호 등 여러 부가정보를 담고있다.)
이걸 JpaRepository 가 상속받고, 또 이걸 우리의 커스텀 Repository로 상속해서 사용하게 된다.
//User 테이블을 페이징하는 메서드
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findAll(Pageable pageable);
}
(메서드 정의는 org.springframework.data.jpa.repository.support.SimpleJpaRepository.java
에 있다....ㅎ)
드디어 Pageable 객체를 직접 만들어보자.
쿼리 파라미터로 page 와 size 가 오면 다음과 같은 방법으로 Pageable 객체를 만들 수 있다.
protected PageRequest(int page, int size, Sort sort) {
super(page, size);
Assert.notNull(sort, "Sort must not be null!");
this.sort = sort;
}
public static PageRequest of(int page, int size) {
return of(page, size, Sort.unsorted());
}
public static PageRequest of(int page, int size, Sort sort) {
return new PageRequest(page, size, sort);
}
public static PageRequest of(int page, int size, Direction direction, String... properties) {
return of(page, size, Sort.by(direction, properties));
}
// 사용 예시
Page<Entity> pleaseGiveMePages(int page, int size) {
Pageable pageable = PageRequest.of(page,size);
Page<Entity> page = entityRepository.findAll(pageable);
return page;
}
MySql의
LIMIT (page-1)*size, size
문법의 경우는 offset , limit의 문법과 같다고 생각하는데요. (즉, 몇 번째 ~ 몇 개를 조회한다.) 이는 offset 구간까지 테이블 full-scan이 진행되는 것으로 알고있어요. 만약 데이터가 10만건이라고 하고 마지막 페이지를 조회하기 위해서는 앞에서부터 모든 table의 full-scan이 진행된다고 알고 있습니다.만약, 이 부분은 where문을 통해
where id > 이전 조회의 마지막 id
를 이용한다면, 해당 부분에 대한 최적화를 진행할 수 있을 것 같네요! (이 경우네는 B-tree에서 나온 값을 가지고 빠르게 접근해서 페이징 조회를 진행할 수 있겠죠)이 부분에 대해 참고자료 남겨봅니다!
https://dba.stackexchange.com/questions/261714/offset-vs-where-performance-for-pagination-with-index
https://jojoldu.tistory.com/528