[실전! 스프링 데이터 JPA] 페이징과 정렬, 슬라이스

강신현·2022년 8월 9일
0

종류

  • 정렬 기능
org.springframework.data.domain.Sort
  • 페이징 기능 (내부에 Sort 포함)
org.springframework.data.domain.Pageable
  • 추가 count 쿼리 결과를 포함하는 페이징
org.springframework.data.domain.Page
  • 추가 count 쿼리 없이 다음 페이지만 확인 가능 (내부적으로 limit + 1조회)
org.springframework.data.domain.Slice

페이징

추가 count 쿼리 결과를 포함하는 페이징

페이지는 0번부터 시작한다.

  • MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
    
    // 페이징
    Page<Member> findByAge(int age, Pageable pageable);
}
  • MemberRepositoryTest
@SpringBootTest
@Transactional
@Rollback(false)  // 롤백하지 않고 커밋하여 db에 반영 (test코드-현업 에서는 사용하면 안되지만 공부하는 단계에서만 사용)
class MemberRepositoryTest {

    @Test
    public void paging() throws Exception {
        //given
        memberRepository.save(new Member("member1", 10));
        memberRepository.save(new Member("member2", 10));
        memberRepository.save(new Member("member3", 10));
        memberRepository.save(new Member("member4", 10));
        memberRepository.save(new Member("member5", 10));

        int age = 10;
        PageRequest pageRequest = PageRequest.of(0,3, Sort.by(Sort.Direction.DESC, "username")); 

        //when
        Page<Member> page = memberRepository.findByAge(age, pageRequest);

        //then
        List<Member> content = page.getContent(); //조회된 데이터

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

- 인터페이스

public interface Page<T> extends Slice<T> {
	int getTotalPages(); //전체 페이지 수
	long getTotalElements(); //전체 데이터 수
	<U> Page<U> map(Function<? super T, ? extends U> converter); //변환기
}

슬라이스

추가 count 쿼리 없이 다음 페이지만 확인 가능
내부적으로 limit + 1 을 조회하므로 아래 예시에서 3페이지 + 1 즉, 4페이지를 가져온다.
(다음 페이지가 있을 경우 더보기 같은 버튼으로 다음 페이지부터 다시 3 + 1 페이지를 가져오는 용도)

  • MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {

    // 슬라이스
    Slice<Member> findByAge(int age, Pageable pageable);
}
  • MemberRepositoryTest
@SpringBootTest
@Transactional
@Rollback(false)  // 롤백하지 않고 커밋하여 db에 반영 (test코드-현업 에서는 사용하면 안되지만 공부하는 단계에서만 사용)
class MemberRepositoryTest {

    @Test
    public void slice() throws Exception {
        //given
        memberRepository.save(new Member("member1", 10));
        memberRepository.save(new Member("member2", 10));
        memberRepository.save(new Member("member3", 10));
        memberRepository.save(new Member("member4", 10));
        memberRepository.save(new Member("member5", 10));

        int age = 10;
        PageRequest pageRequest = PageRequest.of(0,3, Sort.by(Sort.Direction.DESC, "username")); // 페이지는 0번부터 시작

        //when
        Slice<Member> page = memberRepository.findByAge(age, pageRequest);

        //then
        List<Member> content = page.getContent(); //조회된 데이터

        assertThat(content.size()).isEqualTo(3); //조회된 데이터 수
        // assertThat(page.getTotalElements()).isEqualTo(5); //전체 데이터 수 -> 기능 없음
        assertThat(page.getNumber()).isEqualTo(0); //페이지 번호
        // assertThat(page.getTotalPages()).isEqualTo(2); //전체 페이지 번호 -> 기능 없음
        assertThat(page.isFirst()).isTrue(); //첫번째 항목인가?
        assertThat(page.hasNext()).isTrue(); //다음 페이지가 있는가?
    }
}

- 인터페이스

public interface Slice<T> extends Streamable<T> {
	int getNumber(); // 현재 페이지	
	int getSize(); // 페이지 크기
	int getNumberOfElements(); // 현재 페이지에 나올 데이터 수
	List<T> getContent(); // 조회된 데이터
	boolean hasContent(); // 조회된 데이터 존재 여부
	Sort getSort(); // 정렬 정보
	boolean isFirst(); // 현재 페이지가 첫 페이지 인지 여부
	boolean isLast(); // 현재 페이지가 마지막 페이지 인지 여부
	boolean hasNext(); // 다음 페이지 여부
	boolean hasPrevious(); // 이전 페이지 여부
	Pageable getPageable(); // 페이지 요청 정보
	Pageable nextPageable(); // 다음 페이지 객체
	Pageable previousPageable(); //이전 페이지 객체
	<U> Slice<U> map(Function<? super T, ? extends U> converter); //변환기
}

반환 방법1 (api 요청 파라미터)

api 요청 파라미터로 다양한 옵션들을 추가해서 반환 받을 수 있다.

👉 page 객체에 따라 api 스펙이 달라지므로 이런 방식은 사용하면 안됨, DTO로 변환하여 반환해야 함

예) /members?page=0&size=3&sort=id,desc&sort=username,desc

page : 현재 페이지, 0부터 시작한다.
size : 한 페이지에 노출할 데이터 건수 (기본 20개)
sort : 정렬 조건을 정의한다.

예) 정렬 속성,정렬 속성...(ASC | DESC), 정렬 방향을 변경하고 싶으면 sort 파라미터 추가 (asc 생략 가능)

  • MemberController
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    return page;
}

반환 방법2 (DTO 로 반환)

page 객체를 controller에서 그대로 반환하면 안됨. api 스펙이 바뀌기 때문
따라서 페이지를 유지하면서 엔티티를 DTO로 변환하여 반환해야 함

  1. MemberController
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    Page<MemberDto> map = page.map(member -> new MemberDto(member.getId(), member.getUsername(), member.getTeam().getName()) );
    return map;
}

MemberDto 에 member을 memberDto로 변환하는 생성자를 따로 만들어 코드를 간단하게 할 수 있음

  • MemberDto
@Data
public class MemberDto {

    ...

    public MemberDto(Member member) {
        this.id = member.getId();
        this.username = member.getUsername();
        this.teamName = member.getTeam().getName();
    }
}
  • MemberController
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    Page<MemberDto> map = page.map(member -> new MemberDto(member));
    return map;
}

page 커스텀

page 시작을 0이 아닌 1부터 시작하게 하는 등 다양하게 커스텀 할 수 있다.

  1. PageRequest(Pageable 구현체)를 생성해서 리포지토리에 넘김
  2. 응답값도 Page 대신에 직접 만들어서 제공
  • MemberRepositoryTest
@SpringBootTest
@Transactional
@Rollback(false)  // 롤백하지 않고 커밋하여 db에 반영 (test코드-현업 에서는 사용하면 안되지만 공부하는 단계에서만 사용)
class MemberRepositoryTest {

    @Test
    public void paging() throws Exception {
        //given
        memberRepository.save(new Member("member1", 10));
        memberRepository.save(new Member("member2", 10));
        memberRepository.save(new Member("member3", 10));
        memberRepository.save(new Member("member4", 10));
        memberRepository.save(new Member("member5", 10));

        int age = 10;
        
        // 1. PageRequest(Pageable 구현체)를 생성해서 리포지토리에 넘김
        PageRequest pageRequest = PageRequest.of(0,3, Sort.by(Sort.Direction.DESC, "username")); 

        //when
        // 2. 응답값도 Page 대신에 직접 만들어서 제공
        Page<Member> page = memberRepository.findByAge(age, pageRequest);
        
        // DTO로 변환하여 반환
        Page<MemberDto> dtoPage = page.map(m -> new MemberDto(m.getId(), m.getUsername(), null));

        //then
        List<Member> content = page.getContent(); //조회된 데이터

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

@PageableDefault (개별 설정)

  • MemberController (개별 설정)
@GetMapping("/members")
public Page<Member> list(@PageableDefault(size = 12, sort = "username", direction = Sort.Direction.DESC) Pageable pageable) {
    Page<Member> page = memberRepository.findAll(pageable);
    return page;
}

@Qualifier (페이징 정보가 둘 이상)

@Qualifier 에 접두사명 추가 "{접두사명}_xxx”

예 : /members?member_page=0&order_page=1

public String list(
      @Qualifier("member") Pageable memberPageable,
      @Qualifier("order") Pageable orderPageable, ...

강의 출처

[인프런 - 김영한] 실전! 스프링 데이터 JPA

profile
땅콩의 모험 (server)

0개의 댓글