[SpringBoot] Page, Slice

KMS·2022년 4월 25일
0

SpringData

목록 보기
3/9

SpringBoot에서는 쿼리에 대한 페이징 기능을 제공합니다. 해당 기능들을 알아보도록 하겠습니다.
(참고: PageRequest implements AbstractPageRequest && AbstractPageRequest implements Pageable)

Page

 	Page<Member> findByAge(int age, Pageable pageable);
	@Test
    public void page() throws Exception {
        Team teamA = new Team("TeamA");
        teamDataRepository.save(teamA);

        //given
        memberDataRepository.save(new Member("member0", 10, teamA));
        memberDataRepository.save(new Member("member1", 10, teamA));
        memberDataRepository.save(new Member("member2", 10, teamA));
        memberDataRepository.save(new Member("member3", 10, teamA));
        memberDataRepository.save(new Member("member4", 10, teamA));
        memberDataRepository.save(new Member("member5", 10, teamA));
        memberDataRepository.save(new Member("member6", 10, teamA));
        memberDataRepository.save(new Member("member7", 10, teamA));
        memberDataRepository.save(new Member("member8", 10, teamA));
        memberDataRepository.save(new Member("member9", 10, teamA));

        PageRequest pageRequest = PageRequest.of(2, 3, Sort.by(Sort.Direction.DESC, "username"));
        Page<Member> page = memberDataRepository.findByAge(10, pageRequest);


        Page<MemberDTO> pageDTO = page.map(member -> new MemberDTO(member.getUsername(), member.getAge(), member.getTeam()));
        for (MemberDTO memberDTO : pageDTO) {
            System.out.println("memberDTO = " + memberDTO);
        }

        System.out.println("=============================================");
        //then
        List<Member> content = page.getContent(); // == Page #3
        System.out.println("content.size() = " + content.size());
        for (Member member : content) {
            System.out.println("member = " + member);
        }// member.username = {member3, member2, member1}

        long totalElements = page.getTotalElements(); // == 전체 데이터 수
        System.out.println("totalElements = " + totalElements);

        int number = page.getNumber(); //페이지 번호
        System.out.println("number = " + number);

        int totalPages = page.getTotalPages();
        System.out.println("totalPages = " + totalPages);

        assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
        assertThat(page.getTotalElements()).isEqualTo(10); //전체 데이터 수
        assertThat(page.getNumber()).isEqualTo(2); //페이지 번호
        assertThat(page.getTotalPages()).isEqualTo(4); //전체 페이지 번호
        assertThat(page.isFirst()).isFalse(); //첫번째 항목인가?
        assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?


    }

Member0 ~ Member9 까지 총 10개의 데이터가 저장되어 있을때 페이징을 진행했습니다.
PageRequest는 쿼리에 대한 페이징 조건이 들어갑니다.
PageRequest.of(page, size, sort)로 page는 데이터를 몇번째 페이지에서 가져올지, size는 총 몇개의 데이터를 가져올지, 그리고 sort는 데이터를 어떤 조건으로 정렬할것인지를 알려줍니다.(이때, page는 0번째 page부터 시작합니다.)
해당 예제에서는 Member는 username을 내림차순으로 정렬 했을때, 2번째 페이지에서 총 3개의 값을 가져오겠다는 것을 뜻합니다.(SQL: ORDER BY username DESC OFFSET 6 LIMIT 3)
이때, 총 두개의 쿼리가 실행 됩니다.

		* 1. select member0_.member_id as member_i1_0_, member0_.age as age2_0_, member0_.team_id as team_id4_0_, member0_.username as username3_0_ 
        * from member member0_ where member0_.age=10 order by member0_.username desc limit 3 offset 6;
        * 2. select count(member0_.member_id) as col_0_0_ from member member0_ where member0_.age=10;

1번 쿼리는 실제 원하는 결과를 가져와 주는 쿼리문이고, 2번 쿼리는 count를 실행한 쿼리입니다. 페이징을 하지 않았을때 조건을 만족 시키는 데이터의 수를 알려주기 위해 2번 쿼리가 자동으로 같이 실행 됩니다.
그러나, 이 부분은 굉장히 조심해야 합니다.
JOIN을 여러번 하는 경우에는 2번 쿼리가 실행 될때도 똑같이 JOIN을 하고난 후의 데이터의 갯수를 가져오기 때문에 성능 저하가 일어납니다.
해결 방법은 다음과 같습니다:

@Query(value = “select m from Member m left join m.team t”,
 countQuery = “select count(m.username) from Member m”)
Page<Member> findMemberAllCountBy(Pageable pageable);

호출 하는 메서드에 @Query 애노테이션을 추가하고, countQuery를 목적에 맞게 작성함으로써, 성능 최적화를 할 수 있습니다.

엔티티를 그대로 클라이언트에 반환하는 것은 위험함으로, DTO를 변환하는 기능도 제공합니다.

Page<MemberDTO> pageDTO = page.map(member -> new MemberDTO(member.getUsername(), member.getAge(), member.getTeam()));

Slice

	Slice<Member> findSliceByAge(int age, Pageable pageable);
	@Test
    public void slicing(){

        //given
        memberDataRepository.save(new Member("member0", 10));
        memberDataRepository.save(new Member("member1", 10));
        memberDataRepository.save(new Member("member2", 10));
        memberDataRepository.save(new Member("member3", 10));
        memberDataRepository.save(new Member("member4", 10));
        memberDataRepository.save(new Member("member5", 10));
        memberDataRepository.save(new Member("member6", 10));
        memberDataRepository.save(new Member("member7", 10));
        memberDataRepository.save(new Member("member8", 10));
        memberDataRepository.save(new Member("member9", 10));


        //when
        PageRequest pageRequest = PageRequest.of(2, 3, Sort.by(Sort.Direction.DESC, "username"));
        Slice<Member> slice = memberDataRepository.findSliceByAge(10, pageRequest);

        System.out.println("=============================================");
        //then
        List<Member> content = slice.getContent(); // == Page #3
        System.out.println("content.size() = " + content.size());
        for (Member member : content) {
            System.out.println("member = " + member);
        }// member.username = {member3, member2, member1}

        int number = slice.getNumber(); //페이지 번호
        System.out.println("number = " + number);


        assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
        assertThat(slice.getNumber()).isEqualTo(2); //페이지 번호
        assertThat(slice.isFirst()).isFalse(); //첫번째 항목인가?
        assertThat(slice.hasNext()).isTrue(); //다음 페이지가 있는가?

Slice를 사용 할 경우 자동으로 count 쿼리는 실행이 되지 않으며, 다음의 쿼리 하나만 처리됩니다:

1. select member0_.member_id as member_i1_0_, member0_.age as age2_0_, member0_.team_id as team_id4_0_, member0_.username as username3_0_
from member member0_ where member0_.age=10 order by member0_.username desc limit 4 offset 6;

Page와는 다르게 PageRequest.of()에서 명시한 size+1 만큼 LIMIT 조건으로 실행 됩니다.
이를 통해서, 다음 페이지의 여부를 확인 할 수 있습니다.

profile
Student at Sejong University Department of Software

0개의 댓글