JPA

[JPA] Page vs Slice

Junuuu 2023. 10. 8. 00:01
728x90

개요

페이징을 처리하면서 Page, Slice, Window의 차이에 대해 명확하게 이해하고 넘어가고자 합니다.

 

TestEntityRepository 준비

interface TestEntityRepository: JpaRepository<TestEntity, UUID> {
    fun findPageBy(pageable: Pageable): Page<TestEntity>
    fun findSliceBy(pageable: Pageable): Slice<TestEntity>
}

 

Page

Page는 일반적인 게시판 형태의 페이징에서 많이 활용됩니다.

구글에서 제공하는 페이징의 예시

 

예시 코드

@DataJpaTest
class TestEntityTest @Autowired constructor(
    private val testEntityRepository: TestEntityRepository,
){


    @PostConstruct
    fun `데이터 세팅`(){
        repeat(1000){
            testEntityRepository.save(TestEntity(name = it.toString()))
        }
    }

    @Test
    fun `Page 객체로 조회한다`(){
        val pageRequest = PageRequest.of(3,10)

        val actual: Page<TestEntity> = testEntityRepository.findPageBy(pageRequest)

        println(actual)
    }
}

1000개의 데이터를 세팅하고 testEntityRepository에서 구현한 findPageBy()을 활용하면 PageRequest객체를 넘기고 Page객체로 반환받을 수 있습니다.

 

 

실제 쿼리

Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        test_entity t1_0 offset ? rows fetch first ? rows only
Hibernate: 
    select
        count(t1_0.id) 
    from
        test_entity t1_0

count 쿼리를 날려서 전체 페이지의 개수를 가져오고 총 몇 페이지가 존재하고 현재는 몇 페이지인지 계속 체크하면서 동작합니다.

 

Page의 단점

1000개의 데이터가 있다고 가정했을때 offset이 900이고 limit이 10이라면 900부터 10개의 데이터만 가져올 것이라 예상할 수 있습니다.

하지만 910개의 데이터를 모두 DB에서 불러오고 limit만큼만 우리에게 반환해주기 때문에 900개의 데이터는 읽기만 하고 버리게 됩니다.

페이지가 뒤로 이동할수록 읽어야 할 행의 수는 기하급수적으로 늘어나기 때문에 성능을 고려해주어야 합니다. 

 

추가적으로 Count 쿼리를 날리게 되는데 100만건의 데이터가 있을 때 MyISAM의 경우에는 row 수가 테이블의 저장되므로 O(1)의 시간복잡도를 가지지만 InnoDB는 full scan을 수행하게 됩니다.

 

Slice

Slice는 일반적으로 무한스크롤을 구현할때 활용됩니다.

구글에서 제공하는 Slice 예시 결과 더보기

 

 

코드 예시

    @Test
    fun `Slice 객체로 조회한다`(){
        val pageRequest = PageRequest.of(3,10)

        val actual: Slice<TestEntity> = testEntityRepository.findSliceBy(pageRequest)

        println(actual)
    }

 

실제 쿼리

Hibernate: 
    select
        t1_0.id,
        t1_0.name 
    from
        test_entity t1_0 offset ? rows fetch first ? rows only
Slice 3 containing com.example.study.entity.TestEntity instances

count 쿼리를 날리지 않고 다음 페이지만 확인 가능합니다.

 

Slice의 단점

count 쿼리를 날리지 않을 뿐 offset기반의 단점은 그대로 가지고 있습니다.

 

Slice가 다음페이지를 판단하는 방법

query를 binding하는 logging을 추가하고 보면 size가 10인경우 limit을 11까지 조회합니다.

즉, 11개가 조회된다면 다음 페이지가 있다고 판단합니다.

만약 10개이하라면 다음 페이지가 없다고 판단합니다.

 

 

Slice를 활용하여 offset을 사용하지 않는 페이징 구현하기

https://www.inflearn.com/questions/690623/quarydsl-%EB%A1%9C-no-offset-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98%EC%9D%B4-%EA%B5%AC%ED%98%84%EA%B0%80%EB%8A%A5%ED%95%A0%EA%B9%8C%EC%9A%94

 

Quarydsl 로 no-offset 페이지네이션이 구현가능할까요? - 인프런 | 질문 & 답변

[질문 내용]여기에 질문 내용을 남겨주세요.김영한님 로드맵중 실전로드맵 패키지 듣고있는데,restapi설계중에 무한스크롤을 구현해야하는 시점에강의 내용에 Quarydsl로 페이지네이션 강의가 나

www.inflearn.com

 

 

 

 

참고자료

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.special-parameters

 

Spring Data JPA - Reference Documentation

Example 121. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io