JPA Repository를 사용할 때
public interface PostRepository extends JpaRepository<Post,Long> {
Page<Post> findByTitle(String title, Pageable p);
}
JPA repository는 Pageable을 매개변수로 받을 시 , 반환형을 Page로 추상화 해준다.
QueryDsl을 사용할 때
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<Post> findByTitleCustom(String title, Pageable pageable) {
List<Post> posts = queryFactory
.selectFrom(post)
.where(post.title.eq(title))
.offset(pageable.getOffset()) // offset설정
.limit(pageable.getPageSize()) // limit 설정
.fetch();
long total = queryFactory // 총 element 쿼리 한번 필요
.selectFrom(post)
.where(post.title.eq(title))
.fetchCount();
return new PageImpl<>(posts, pageable, total);
}
}
똑같은 결과를 반환하는 메소드를 QueryDsl을 구현하려면, PageImpl을 직접 구현하여 Page<T>를 반환해야 한다.
즉 , Pageable의 정보를 통해 offset과 limit을 설정하여 쿼리를 하고,
total count를 뽑아내는 쿼리를 한번 추가적으로 실행하여 PageImpl의 매개변수로 주어야한다.
여기까지가 익히 알고있는 QueryDsl에서의 페이지네이션 구현방법이다.
위 두방법은 Count query가 무조건적으로 1회 발생하게된다.
하지만 PageImpl 대신, PageableExecutionUtils를 이용하면, 경우에따라서 Count query가 발생하지 않는 최적화 이점을 가지고있다
PageableExecutionUtils란
직접 가져온 PageableExecutionUtils의 내부 구현체이다.
특이한 것은 getPage라는 하나의 정적 메서드를 두어 PageImpl을 한번더 래핑한 것인데,
세번째 매개변수로 Long이 아닌, LongSupplier를 받는다.
LongSupplier에 CountQuery를 넣어주게되면, 내부적으로 Count쿼리가 필요한지 아닌지 파악해서 최적화 하게된다.
PageImpl은 미리 Count 쿼리를 실행해 total count를 직접 매개변수로 넣어줘야만 한다면,
PageableExecutionUtils는 Count쿼리를 매개변수로 두어 동적으로 이걸 실행할건지 안할건지 결정한다.
그렇다면 무엇을 기준으로 Count 쿼리를 하고, 안하고를 결정하는지 코드를 통해서 설명하도록 하겠다.
코드 뜯어보기
첫번째 경우
1. pageable.isUnpaged()가 true라는 의미는, 페이지네이션 자체가 안되어 있다는소리이다.즉 count query가 필요없이 content.size()가 곧 total element이다.
2. 혹은 page의 offset이 0 ( 즉 첫번째 페이지) 일때, 반환된 content의 총 수가 pageSize보다 작으면, content.size()가 곧 total element이다.
3. 나머지 경우에는 매개변수로받았던 supplier.getAsLong()을 실행해 쿼리를 실행시킨다.
두번째 경우
1. 첫번째경우에서 첫번째 페이지임을 분기했으므로, 이 if문을 통과하는 경우는 마지막 페이지일 경우이다.
(왜냐하면 pagSIze보다 content.size()가 작은경우는 첫번째 페이지, 마지막 페이지 밖에 없기때문이다. )
-> 마지막 페이지일땐, 지금까지의 offset + content.size()가 total element가 된다. 즉 쿼리가 필요없다.
2. 이 이외의 경우에는 Count query를 실행해준다.
즉, 첫번째 페이지, 마지막 페이지임이 확실한 경우에만 (다만 content.size()가 pageSize보다 작아야 판단 가능)
추가적인 쿼리를 날리지 않음으로써 미세하게 성능 최적화를 할 수 있다.
QueryDsl에서 PageableExecutionUtils를 사용할 때 (권장)
@Repository
@RequiredArgsConstructor
public class PostRepositoryImpl implements PostRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<Post> findByTitleCustom(String title, Pageable pageable) {
// Fetch 데이터 쿼리
List<Post> posts = queryFactory
.selectFrom(post)
.where(post.title.eq(title))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// Count 쿼리를 따로 추출
LongSupplier totalCountSupplier = () -> queryFactory
.selectFrom(post)
.where(post.title.eq(title))
.fetchCount();
//세번쨰 매개변수로 Count 쿼리를 넣는다
return PageableExecutionUtils.getPage(posts, pageable, totalCountSupplier::getAsLong);
}
}
'Backend > Spring Boot' 카테고리의 다른 글
[Spring boot] Spring AOP - CGLIB Proxy vs Dynamic Proxy (0) | 2024.12.31 |
---|---|
[Spring boot] JPA에서 Slice와 Page의 차이 (1) | 2024.10.07 |
[Spring Boot] Pageable을 이용해 페이지네이션구현(+JPA) (0) | 2024.09.23 |
[Spring Boot] Spring AI + Google vertex ai gemini 이미지 처리하기 (0) | 2024.06.27 |