No Offset Paging 구현
offset 기반의 페이징
페이지 번호와 페이지 사이즈 기반으로 구현한 기존의 페이징 방식은
다음과 같이 페이지 번호가 하단에 출력된다.
- offset : 페이지 번호
- limit : 페이지 사이즈
No Offset (Cursor based Pagination) 기반의 페이징
SNS 에서는 이 방식 보다는 다음과 같이 무한 스크롤을 가능하게 하는 No Offset 방식이 더 어울리며
No Offset 방식은 페이지 번호를 지정하는 방식보다 더 빠르다.
위 이미지를 보면 페이지 번호를 클릭해서 페이지가 넘어가는 게 아니라
스크롤이 페이지 끝에 닿았을 때 새로운 페이지가 로딩되는 것을 확인할 수 있다.
Q. 페이지 번호가 없는 방식을 No Offset이라 하는데 왜 더 빠를까?
A. 최신 컨텐츠가 우선으로 조회되어야 하는데 페이지 번호를 지정하는 쿼리는
앞에서 읽었던 행을 다시 읽어야 하기 때문에 뒤로 갈수록 느려진다.
No Offset 방식은 조회 시작 부분을 인덱스로 빠르게 찾아 매번 첫 페이지만 읽는다.
구현 코드
- PostService
@Transactional(readOnly = true)
public List<Post> getPosts(Long userId, Long lastPostId, Pageable pageable) {
if (lastPostId > 0) {
return postRepository.findByUserIdAndIdLessThan(userId, lastPostId, pageable);
}
return postRepository.findByUserId(userId, pageable);
}
처음 조회하는 경우 lastPostId
가 없기 때문에
findByUserId()
메서드를 통해 최신 게시글을 조회한다.
이후 호출되는 API 부터는 lastPostId
를 기준으로 조회한다.
- PostRepository
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByUserId(Long userId, Pageable Pageable);
List<Post> findByUserIdAndIdLessThan(@Param("userId") Long userId, @Param("id") Long lastPostId, Pageable Pageable);
@Query(value = "SELECT p"
+ " FROM Post p"
+ " JOIN Follow f ON p.user.id = f.follower.id"
+ " WHERE p.user.id = :userId"
+ " AND f.follower.id = :userId")
List<Post> findByJoinFollow(@Param("userId") Long userId, Pageable pageable);
@Query(value = "SELECT p"
+ " FROM Post p"
+ " JOIN Follow f ON p.user.id = f.follower.id"
+ " WHERE p.user.id = :userId"
+ " AND f.follower.id = :userId"
+ " AND p.id < :lastPostId")
List<Post> findByJoinFollowAndLastIdLessThan(@Param("userId") Long userId, @Param("lastPostId") Long lastPostId, Pageable pageable);
}