본문 바로가기
학습/DB

JPA를 이용해 페이징 만들기

코동이 2021. 12. 15.

[ 개요 ]

 

Paging은 각 회사마다, 각 프로젝트마다 환경에 맞게 구성되어 있다. 따로 페이징을 위한 클래스를 구분하기도 하고, 엔티티가 페이징 클래스를 상속받도록 만들기도 한다. 새로운 프로젝트를 짤 때 특이사항이 아니라면 다른곳에서 사용하던 것을 그대로 사용한다. 사실상 핵심 비지니스도 아니고 복사 붙여넣기를 많이하다보니 굉장히 귀찮고 번거로운 작업이다. 이 작업을 JPA Data가 제공하는 기능을 통해 간편하게 만들어 줄 수 있다.

 

[ 목표 ]

 

Spring JPA에서 제공하는 페이징 쿼리 기능을 시작으로 Data에서 제공하는 @PageableDefault 기능을 알아본다.

 

 


1. 순수 JPA 페이징 기능 이용하기

 

public List<Member> findByPage(int age, int offset, int limit) {
	return em.createQuery("select m from Member m where m.age = :age order by m.username desc")
		.setParameter("age", age)
		.setFirstResult(offset)
		.setMaxResults(limit)
		.getResultList();
}


em.createQuery()는 순수 JPA를 이용한 쿼리이다. em은 EntityManager이고 createQuery()를 통해 쿼리문을 만든다.

 

setFirstResult(offset)은 조회된 Member의 리스트 중에서 시작 번호를, setMaxResults(limit)은 그 중에 몇 개를 조회할 것인지를 나타낸다.

 

결과가 여러개가 나오기 때문에 getResultList()로 리스트를 만들어 리턴한다.

 

 

결과의 count 값도 다음과 같이 조회할 수 있다.

 

public long totalCount(int age) {
	return em.createQuery("select count(m) from Member m where m.age = :age", Long.class)
	.setParameter("age", age)
	.getSingleResult();
}

 

getResultList()가 아닌 getSingleResult()로 조회하며 리턴형도 long이다. 한가지 고민해볼 것은 굳이 따로 totalCount() 함수를 만들지 않고 List<Member>로 리턴받은 결과를 size()로 계산하면 되지 않을까.



2. 페이징과 정렬(Sort, Pageable)

 

JPA에서 제공하는 페이징 기능은 다음과 같으며 페이징이 필요한지, 어떤 방식의 count를 사용할지에 따라 달라진다.

 

패키지 특징 비고
org.springframework.data.domain.Sort  정렬 기능을 한다  
org.springframework.data.domain.Pageable 페이징 기능을 한다 내부에 Sort 포함
org.springframework.data.domain.Page count 쿼리를 포함해서 페이징 기능을 한다 '일반 페이징'에 사용
org.springframework.data.domain.Slice count 쿼리 없이 다음페이지만 확인 가능하다.
(내부적으로 limit + 1 조회한다)
'더보기' 기능에 사용
List(자바 컬렉션) coutn 쿼리 없이 결과만 반환한다.  

 

 

* 실제 쿼리를 사용하는 방법

 

Page<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용
Slice<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByUsername(String name, Pageable pageable); //count 쿼리 사용 안함
List<Member> findByUsername(String name, Sort sort);

 

유의할 것은 2번째 매개변수에 들어간 Pageable이다. Spring JPA Data가 제공하는 Pageable interface는 페이지 번호와 페이지 사이즈를 입력받아 계산하는 메서드를 가지고 있다. 이는 PageRequest 클래스를 통해 구현되는데, 2가지 정보만으로도 내부적으로 페이징과 관련된 많은 정보를 계산해 리턴해준다. 따라서 개발자는 페이지 번호와 페이지 사이즈만 정해주면 된다.

 

 

PageRequest는 어떻게 사용할 수 있는지 알아보자.

 

PageRequest는 of 메서드를 이용해 page, size 혹은 sort를 사용하여 내부적으로 계산한다.

 

public static PageRequest of (int page, int size);
protected PageRequest of (int page, int size, Sort sort);

 

0부터 5개를 출력하는 페이징을 해보자.

 

Pageable paging = PageRequest.of(0, 5);

Page<OrderDo> page = orderRepository.findAll(paging);
Slice <OrderDo> page = orderRepository.findAll(paging);

 

너무 편한 것은, 기존에 List<OrderDo>에서 페이징을 위해 리턴형을 Page<OrderDo>로 바꾸더라도,  매개변수에 Pageable 구현체만 추가하면 된다. JPA에서 따로 등록을 해줄 필요도, 고려할 것도 없다. 왜냐하면, 기존에 이용하던 JpaRepository는 이미 페이징 관련된 PagingAndSortingRepository를 구현하고 있기 때문이다.

 

따라서 내부적으로 이미 페이징과 관련된 모든 구현들을 하고있기 때문에 리턴형이 Page로 바뀌어도 Pageable만 추가해서 사용이 가능하다.

 

PagingAndSortingRepository interface에 어떤 메서드가 있고 작동하는지 좀 더 살펴본다.

 

 

 

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { 
        Iterable<T> findAll(Sort sort); 
        Page<T> findAll(Pageable pageable);
    }

 

이름에서 보다시피 Sort만 가능한 메서드가 있고, Sort를 포함하여 더 많은 기능을 가진 Pageable이 있다. 각각 리턴형은 IterablePage이다.

 

Sort만 사용하는 경우는 드물겠지만, 다음과 같이 사용할 수 있다.

 

Sort sort = Sort.by("orderAt");
sort=sort.descending();
Iterable<OrderDo> orderIterator = orderRepository.findAll(sort);
                                        
Iterable<OrderDo> orderIterator = orderRepository.findAll(Sort.by("orderAt").descending());

 

* Slice vs Page

 

페이징 쿼리를 날리고 응답받은 반환형은 SlicePage 2가지다. PageSlice를 상속받으며 전체 페이지 수와 전체 데이터 수를 알 수 있는 메서드가 추가된다. 생각보다 많은 정보를 주기 때문에 어떤 정보를 리턴하는지 확인하여 쉽게 페이징에 이용하도록 잘 숙지한다.

 

 

 

 

* Page 인터페이스

 

public interface Page<T> extends Slice<T> {
	int getTotalPages(); //전체 페이지 수
	long getTotalElements(); //전체 데이터 수
	<U> Page<U> map(Function<? super T, ? extends U> converter); //변환기
}



* Slice 인터페이스

 

public interface Slice<T> extends Streamable<T> {
	int getNumber(); //현재 페이지
	int getSize(); //페이지 크기
	int getNumberOfElements(); //현재 페이지에 나올 데이터 수
	List<T> getContent(); //조회된 데이터
	boolean hasContent(); //조회된 데이터 존재 여부
	Sort getSort(); //정렬 정보
	boolean isFirst(); //현재 페이지가 첫 페이지 인지 여부
	boolean isLast(); //현재 페이지가 마지막 페이지 인지 여부
	boolean hasNext(); //다음 페이지 여부
	boolean hasPrevious(); //이전 페이지 여부
	Pageable getPageable(); //페이지 요청 정보
	Pageable nextPageable(); //다음 페이지 객체
	Pageable previousPageable();//이전 페이지 객체
	<U> Slice<U> map(Function<? super T, ? extends U> converter); //변환기
}



3. 프로젝트에서 이용하기

 

 

실제 프로젝트에서 페이징 기능을 사용하고 싶다면 @PageableDefault을 사용하면 된다.

 

@GetMapping
public String interviewLists(@PageableDefault(size=10, sort="id", direction= Sort.Direction.ASC) Pageable pageable) {
    Page<InterviewResultDto> page = interviewService.getInterviews(pageable);
    model.addAttribute("list", page.getContent());
    model.addAttribute("page", PageResultDto.of(page));
    ...

 

 

@PageableDefault안에 size, sort, direction을 지정한다.  해당 pageable을 조회 메서드에 매개변수로 추가하면 끝이다.

 

 

 

 

4. 카운트 쿼리 분리

 

추가적으로 카운트 쿼리를 분리가 실무에서는 굉장히 중요하다. 전체 count를 쿼리하는 것은 엄청 무겁기 때문이다.

 

@Query(value = "select m from Member m",
countQuery = "select count(m.username) from Member m")
Page<Member> findMemberAllCountBy(Pageable pageable);

 

 

[ 정리 ]

 

Spring JPA에서는 페이징 기능을 제공한다.

 

JPA Repository를 사용하면서 매개변수에 Pageable만 추가하면 끝이다.

그 이유는 이미 JpaRepository가 PagingAndSortingRepository를 구현하고 있기 때문이었다.

 

Pageable 구현체는 PageRequest 를 사용하면 되고, 페이지 번호와 페이지 사이즈를 넣어주면 된다.

 

Page, Slice 혹은 List를 반환형으로 가지며, Page는 전체 페이지 수와 전체 데이터수를 가진다.

Slice는 count는 알지 못하고 단지 더보기 기능을 위해 limit + 1을 조회할 뿐이다.

List는 어떠한 count도 알지 못한다.

 

 

반응형

'학습 > DB' 카테고리의 다른 글

JDBC란?  (0) 2022.07.05
HikariCP란?  (0) 2022.07.04
JPA를 이용해 자동으로 시간, 작성자 추가하기  (1) 2021.12.14
다형성 쿼리  (0) 2021.10.07
MERGE INTO(oracle)  (0) 2021.10.07