나의 개발일지

[SpringBoot] Page와 PageImpl 본문

Language/SpringBoot

[SpringBoot] Page와 PageImpl

사각분무기 2024. 2. 21. 20:27

HotShare 1차 프로젝트 기간을 진행하며 많은 것을 배웠습니다.

 

특히 제가 모르는 부분들을 다른 분들의 코드를 참고하는 시간은 각별했습니다.

 

그 중에는 PageImpl 과 PageRequest 라는 낯선 개념들도 등장했는데요.

 

실제로 어떤 역할을 하는지는 모르겠지만 우선 따라서 써본 결과 잘 작동하기 때문에 프로젝트 기간 동안에는 그러려니하고 넘어갔습니다.

@GetMapping("/me")
@MemberOnly
public ResponseEntity<ResponseDto<MyCashLogResponse>> showMyCashLogs(
        final Pageable pageable,
        @Auth final Accessor accessor
) {
    Long memberId = accessor.getMemberId();

    Page<CashLog> cashLogs = cashLogService.findMyPageList(memberId, pageable);

    List<CashLogConfirmResponse> cashLogConfirmResponses =
            cashLogs.stream()
                    .map(CashLogConfirmResponse::of)
                    .toList();

    PageImpl<CashLogConfirmResponse> cashLogConfirmPage =
            new PageImpl<>(
                    cashLogConfirmResponses,
                    pageable,
                    cashLogs.getTotalElements());

    MyCashLogResponse myCashLogResponse = cashLogService.getMyCashLogById(
            memberId,
            cashLogConfirmPage
    );

    return ResponseEntity.ok(new ResponseDto<>(
            HttpStatus.OK.value(),
            "캐시 사용 내역 조회 성공", null,
            null, myCashLogResponse
    ));
}

 

위는 해당 코드입니다.

 

Page 와 관련하여 세가지 단계를 거칩니다.

 

1. 프론트에서 전달 받은 page와 size값을 Pageable 객체로 받아 Repository에 넘기면 Page<> 객체를 반환받습니다.

 

2. Page<> 객체의 내용물들을 map 함수를 통해 dto 로 변환 후 List를 반환합니다.

 

3. 반환 받은 List를 다시 PageImpl 의 생성자를 통해 PageImpl<> 객체로 변환합니다.

 

이렇게 완성해두고 다른 코드들을 찬찬히 살피던 중 이상한 점을 발견했습니다.

 

팀원분이 작성하신 부분에선 이 과정 중 dto 로 변환 후 List로 변환한 후 PageImpl을 생성하는 작업 없이 바로 Page를 엔드포인트의 반환값 중 일부로 사용하더군요.

 

이런 식의 활용도 결국 문제가 없다니 신기했습니다.

 

제가 PageImpl 을 처음 접한 것은 rest api 환경의 서버를 처음 만들게 된 이번 프로젝트부터였기 때문에 다른 분이 작성한 것만 보고 지레짐작하길 rest api 환경에선 PageImpl을 사용해야하는 줄 알았으니까요

 

문제가 없다는 것은 팀원분의 코드가 정상 작동하는 것을 보아 틀림이 없고 그렇다면 뭔가 다른 부분이 있는 거겠지?

 

 

이 콘솔창은 제 팀원분 Page의 dto 반환 값이고

 

 

이것은 제가 작성한 PageImpl의 dto 반환값입니다.

 

content 를 시작으로 totalPages에 이르기까지 하나도 다른게 없습니다.

 

그렇다면 PageImpl 는 무엇을 위한 것일까요?


2.3. PageImpl

Finally, there’s the PageImpl class, which provides a convenient implementation of the Page interface that can be used to represent a page of results from a query, including the pagination metadata. It’s commonly used in conjunction with Spring Data’s repository interfaces and pagination mechanisms to retrieve and manipulate data in a pageable manner.
Now that we’ve got a basic understanding of the involved components, let’s set up a simple example.

PageImpl 클래스는 Page 인터페이스의 구현체로, 쿼리 결과의 한 페이지와 페이징 메타데이터를 표현하는 데 유용하게 사용됩니다. 주로 Spring Data의 리포지토리 인터페이스 및 페이징 메커니즘과 함께 사용되어 데이터를 페이지별로 검색하고 조작하는 데 적합합니다. 이로써 간단한 예제 설정에 대한 기본 이해를 마쳤습니다.
https://www.baeldung.com/spring-data-jpa-convert-list-page

 

위는 Baeldung 에서 가져온 글입니다.

 

결론은 PageImpl은 곧 Page 라고 보면 될 것 같습니다. 구현체라는 개념에 대한 이해가 부족했기 때문에 생긴 혼란이라고 볼 수 있겠네요.

 

저희가 그동안 사용해왔던 Page 또한 일종의 PageImpl이라고 생각할 수 있었던 겁니다.

 

PageImpl 을 Page로 형변환할 경우엔 기존 Page와 다를 바 없이 사용할 수 있다면 이제 제가 작성했던 코드를 좀 더 줄일 필요가 있습니다.

 

@GetMapping("/me")
public ResponseEntity<ResponseDto<MyCashLogResponse>> showMyCashLogs(
        final Pageable pageable,
        @Auth final Accessor accessor
) {
    Member member = memberService.getMemberById(accessor.getMemberId());

    Page<CashLogConfirmResponse> cashLogConfirmResponses = cashLogService.findMyPageList(member, pageable).map(CashLogConfirmResponse::of);

    MyCashLogResponse myCashLogResponse = cashLogService.getMyCashLogById(
            member,
            cashLogConfirmResponses
    );

    return ResponseEntity.ok(new ResponseDto<>(
            HttpStatus.OK.value(),
            "캐시 사용 내역 조회 성공", null,
            null, myCashLogResponse
    ));
}

 

불필요했던 과정들을 없애고 코드의 역할을 더 명확히 하기 위해 몇몇 코드는 합쳐보았습니다.

 

한결 깔끔해진 모습이 마음에 드네요.

 

또한 이와 비슷한 구현체와 인터페이스의 관계론 Pageable과 PageRequest가 있습니다.