[46일차] 페이지 네이션

유태형·2022년 7월 4일
0

코드스테이츠

목록 보기
46/77

오늘의 할일

  1. 페이지 네이션이란?
  2. Page
  3. Mapper



내용

페이지 네이션이란?

페이지 네이션이란 서버에서 클라이언트로 전송해야할 목록이 수천개, 수만개, 수십만개 등 엄청나게 많을 때 데이터들을 한번에 모두 전송하는 것은 리소스 낭비도 심하고 시간도 오래 걸릴 것입니다.
데이터를 정해진 몇개만 보내고, 추가 요청 마다 몇개의 데이터만 추가로 전송하게 된다면 리소스 낭비도 줄이고 시간도 빨라질 것입니다.
페이지 네이션을 이용한 방법으론 2가지 방법이 가장 많이 사용 됩니다.

일반적인 웹처럼 몇개의 데이터를 묶어 하나의 페이지로 표현할 수도 있습니다.

유튜브, 인스타그램, 페이스북등 모바일 앱 등에서 많이 사용하는 무한 스크롤도 페이지 네이션을 응용한 표현입니다.




Page

페이지 네이션을 구현하기 위해 가장 기본적으로 사용되는 클래스는 Page<엔티티> 입니다. Page<엔티티>는 엔티티 리스트 뿐만 아니라 몇 페이지냐, 또 페이지당 엔티티는 얼마나등의 페이지 정보를 가지고 있습니다.



PageRequest.of()

자바에서는 of 생성자 패턴이 존재합니다. of메서드의 매개변수에 따라 같은 클래스를 다른 객체로 만드는 방법입니다.
PageRequest클래스도 of()메서드를 제공합니다. 여러 매개변수가 존재할 수 있지만 몇페이지인지, 또 페이지당 몇개의 엔티티를 가져올 것인지 정하는 of()로 예시를 들면,
PageRequest.of(페이지,엔티티 수))로 해당 페이지의 정해진 수 만큼의 엔티티를 가져오라는 메서드입니다.

Page<Member> members = memberRepository.findAll(PageRequest.of(page,size));

size갯수만큼의 엔티티가 있는 해당page의 Page객체를 반환합니다.

레포지토리의 findAll()은 이전에 보아 오던 findAll()과는 사뭇 다릅니다.



PagingAndSortingRepository<>

이전의 CrudRepository<엔티티,@Id타입>에서 사용하던 findAll()메서드와는 리턴과, 매개변수가 다르다는것을 눈치 채셨나요?
분명 Iterable<T> findAll();로 정의되어 있었지만 위에서 사용한 findAll()은 확실히 다릅니다.

그 이유는, PagingAndSortingRepository<엔티티,@Id타입>CrudRepository<엔티티,@Id타입>를 상속받아 Iterable<T> findAll();Page<T> findAll(Pageable pageable);로 오버라이딩 하였습니다.

레포지토리 인터페이스를 CrudRepository<엔티티,@Id타입>부터 상속받던것으로 부터 PagingAndSortingRepository<엔티티,@Id타입> 상속받는 것으로 수정하면 특별한 구현없이 인터페이스를 그대로 이용할 수 있습니다.

public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
    Optional<Member> findByEmail(String email);
    Page<Member> findAll(Pageable pageable);
}



Mapper

Mapper는 서비스 계층과 API 계층에서 사용하는 객체를 완전히 분리시키기 위해 두 객체간 자동전환(default 키워드로 커스텀가능)을 지원하여 서비스 계층은 엔티티 객체만 API 계층은 DTO 객체만 사용하도록 합니다.

서비스 계층에서 반환받은 Page<엔티티>를 클라이언트에게 전송하기 위해선 Mapper에서 DTO 객체로 전환하여 API 계층에서 클라이언트로 전송해야 합니다.

ex)
Page<Member> -> List<Meber>(엔티티 리스트 내림차순 정렬) + PageInfo(반환할 페이지 정보만 추출)로 분리 후 -> MemberResponsesDto로 다시 DTO객체로 전환하여 클라이언트로 전송할 수 있도록 합니다.

@Getter
@Builder
@AllArgsConstructor
public class MemberResponsesDto {
    private List<MemberResponseDto> data;
    private PageInfo pageInfo;
}
@AllArgsConstructor
@Getter
public class PageInfo {
    private int page;
    private int size;
    private long totalElements;
    private int totalPages;
}
	List<MemberResponseDto> membersToMemberResponseDtos(List<Member> members);
    default MemberResponsesDto pageToMemberResponsesDto(Page<Member> pages){
        //페이지 정보
        PageInfo pageInfo = new PageInfo(pages.getNumber(),
                pages.getSize(),
                pages.getTotalElements(),
                pages.getTotalPages());
        //멤버 리스트를 뽑아내서 정렬
        List<Member> members = pages.getContent()
                .stream()
                .sorted(Comparator.comparing(Member::getMemberId).reversed())
                .collect(Collectors.toList());
        List<MemberResponseDto> response = membersToMemberResponseDtos(members);
        //재조합 : 페이지 정보 + 정렬된 리스트
        return new MemberResponsesDto(response,pageInfo);
    }

Mapper

기존의 Page<엔티티>에서 엔티티 리스트를 추출하여 내림차순으로 정렬하고, 페이지 정보들 중 필요한 것만 뽑아 객체로 만든 다음 DTO객체로 합쳐서 반환하는 매핑과정을 정의하였습니다.

{
    "data": [
        //실제로 엔티티 -> dto로 변환한 객체들이 리스트로 나옵니다.
    ],
    "pageInfo": {
        "page": 1,
        "size": 10,
        "totalElements": 20,
        "totalPages": 2
    }
}



후기

페이지네이션은 API를 사용하면 쉬울것 같지만 제대로 구현할려하면 JPA는 기본적으로 알고, 또 세세하게 구현할려면 많은 지식이 요구되는 기술입니다. 세세히 커스텀 할려기보단 쉽고 정확한 구현들을 해보면서 장벽을 허물어야 겠습니다.




GitHub

private!

profile
오늘도 내일도 화이팅!

0개의 댓글