페이지 처리는 DBMS의 종류에 따라서 사용되는 기법이 다른 경우가 많아서 별도의 학습이 필요합니다.
오라클은 "inline view"를 알아야만 하고, MySQL은 "limit"를 알아야만 했지만 JPA는 내부적으로 이런 처리를 "Dialect" 라는 존재를 이용해서 처리한다고 합니다. 예를 들어 JDBC 정보가 예제와 같은 MariaDB의 경우에는 자동으로 MariaDB를 위한 Dialect가 설정되는데 프로젝트의 로딩 시점에 출력되는 로그를 통해서 확인이 가능하며 application.properties를 통해 설정도 가능한다고 합니다.
JPA가 이처럼 실제 데이터베이스에서 사용하는 SQL의 처리를 자동으로 하기 떄문에 개발자들은 SQL이 아닌 API의 객체와 메서드를 사용하는 형태로 페이징 처리를 할 수 있게 됩니다.
Spring Data JPA에서 페이징 처리와 정렬은 특이하게도 findAll()이라는 메서드를 사용하는데, findAll()은 JpaRepository 인터페이스의 상위인 PagingAndSortRepository의 메서드로 파라미터로 전달되는 Pageable이라는 타입의 객체의 의해서 실행되는 쿼리를 결정하게 됩니다. 단 한 가지 주의할 사항은 리턴 타입을 Page타입으로 지정하는 경우에는 반드시 파라미터를 Pageable 타입을 이용해야 한다는 점입니다.
페이지 처리를 위한 가장 중요한 존재는 org.springframework.data.domain.Pagealbe 인터페이스 입니다. Pageable 인터페이스는 페이지 처리에 필요한 정보를 전달하는 용도의 타입으로, 인터페이스이기 때문에 실제 객체를 생성할 때는 구현체인 org.springframework.data.domain.PageRequest라는 클래스를 사용합니다.
PageRequest 클래스의 생성자는 특이하게도 proected로 선언되어 new를 이용할 수 없습니다. 객체를 생성하기 위해서는 static한 of()를 이용해서 처리합니다(Spring boot 2 버전부터 라고 함.). PageRequest 생성자를 보면 page, size, Sort라는 정보를 이용해서 객체를 생성합니다.
static 메서드인 of()의 경우 몇 가지 형태가 존재하는데 이는 페이지 처리에 필요한 정렬 조건을 같이 지정하기 위해서 입니다.
Spring Data JPA를 이용할 때 페이지 처리는 반드시 '0'부터 시작한다는 점을 기억해야만 함!!
예제 코드
@Test
public void testPageDefault() {
// 1페이지 10개
Pageable pageable = PageRequest.of(0, 10);
Page<Memo> result = memoRepository.findAll(pageable);
System.out.println("result = " + result);
System.out.println("----------------------------");
System.out.println("result.getTotalPages() = " + result.getTotalPages()); // 총 페이지
System.out.println("result.getTotalElements() = " + result.getTotalElements()); // 전체 갯수
System.out.println("result.getNumber() = " + result.getNumber()); // 현재 페이지 번호 0부터 시작
System.out.println("result.getSize() = " + result.getSize()); // 페이지당 데이터 개수
System.out.println("result.hasNext() = " + result.hasNext()); // 다음 페이지 존재 여부
System.out.println("result.isFirst() = " + result.isFirst()); // 시작 페이지(0) 여부
}
실행 결과
Hibernate:
select
memo0_.mno as mno1_0_,
memo0_.memo_text as memo_tex2_0_
from
tbl_memo memo0_ limit ?
Hibernate:
select
count(memo0_.mno) as col_0_0_
from
tbl_memo memo0_
result = Page 1 of 10 containing org.zerock.ex2.entity.Memo instances
----------------------------
result.getTotalPages() = 10
result.getTotalElements() = 99
result.getNumber() = 0
result.getSize() = 10
result.hasNext() = true
result.isFirst() = true
import 시에 org.springframework.data 관련 클래스들을 사용하도록 주의할 필요가 있습니다. 위의 테스트 코드에서는 PageRequest.of()를 이용해서 1페이지의 데이터 10개를 가져오기 위해 파라미터로 '0,10'을 전달하고 있습니다.
주의깊게 볼 부분 중 하나는 리턴 타입이 org.springframework.data.domain.Page 라는 점 입니다. Page 타입이 흥미로운 이유는 단순히 해당 목록만으로 가져오는데 그치지 않고, 실제 페이지 처리에 필요한 전체 데이터 개수를 가져오는 커리 역시 같이 처리하기 때문입니다(만일 데이터가 충분하지 않다면 데이터의 개수를 가져오는 쿼리를 실행하지 않습니다.)
또한 실행 결과를 보면 첫 번째 쿼리에서는 MariaDB에서 페이징 처리에 사용하는 limit 구문이 사용되는 것을 볼 수 있고, 두 번째 커리에서는 count()를 이용해서 전체 개수를 처리하는 것을 볼 수 있습니다. 위와 같이 findAll()에 Pageable 타입의 파라미터를 전달하면 페이징 처리에 관련된 커리들을 실행하고, 이 결과들을 이용해서 리턴 타입으로 지정된 Page 객체로 저장합니다.
for (Memo memo:result.getContent()) {
System.out.println("memo = " + memo);
}
실행 결과
memo = Memo(mno=1, memoText=Sample...1)
memo = Memo(mno=2, memoText=Sample...2)
memo = Memo(mno=3, memoText=Sample...3)
memo = Memo(mno=4, memoText=Sample...4)
memo = Memo(mno=5, memoText=Sample...5)
memo = Memo(mno=6, memoText=Sample...6)
memo = Memo(mno=7, memoText=Sample...7)
memo = Memo(mno=8, memoText=Sample...8)
memo = Memo(mno=9, memoText=Sample...9)
memo = Memo(mno=10, memoText=Sample...10)
위의 코드로 정렬이 기본적으로 엔티티의 순차적인 순서임을 볼 수 있습니다.
페이징 처리를 담당하는 PageRequest에는 정렬과 관련된 org.springframework.data.domain.Sort 타입을 파라미터로 전달할 수 있습니다. Sort는 한 개 또는 여러 개의 필드 값을 이용해서 순차적 정렬(asc)이나 역 정렬(desc)을 지정할 수 있습니다.
@Test
public void testSort() {
Sort sort = Sort.by("mno").ascending(); // .descending() 을 사용하여 역 정렬 가능
Pageable pageable = PageRequest.of(0, 10, sort);
Page<Memo> result = memoRepository.findAll(pageable);
result.get().forEach(memo -> {
System.out.println("memo = " + memo);
});
}
실행 결과
Hibernate:
select
memo0_.mno as mno1_0_,
memo0_.memo_text as memo_tex2_0_
from
tbl_memo memo0_
order by
memo0_.mno asc limit ? // ascending 실행
Hibernate:
select
count(memo0_.mno) as col_0_0_
from
tbl_memo memo0_
memo = Memo(mno=1, memoText=Sample...1)
memo = Memo(mno=2, memoText=Sample...2)
memo = Memo(mno=3, memoText=Sample...3)
memo = Memo(mno=4, memoText=Sample...4)
memo = Memo(mno=5, memoText=Sample...5)
memo = Memo(mno=6, memoText=Sample...6)
memo = Memo(mno=7, memoText=Sample...7)
memo = Memo(mno=8, memoText=Sample...8)
memo = Memo(mno=9, memoText=Sample...9)
memo = Memo(mno=10, memoText=Sample...10)