나는 PageImpl을 활용하여 List를 Page로 바꾸어 응답했다.
api테스트를 하는 도중 마지막 페이지에 접근하면 totalPage
와 totalElements
가 늘어나는 오류가 발생했다.
문제 상황
똑같은 요청에 대해 page정보만 바꿔서 요청을 날려봤다.
마지막 페이지인 9페이지 이전인 8페이지를 요청했을 때

마지막 페이지인 9페이지를 요청했을 때

보이는 거와 같이 totalElements 와 totalPages 가 변경된 것을 볼 수 있다.
원인
PageImpl이 매개변수로 받는 Pageable 객체는 기본적으로 0번 인덱스부터 시작한다.
하지만 모든 ui에서 페이지는 1번 페이지부터 시작하므로 나는 임의로 Pageable의 시작 인덱스를 1번부터 시작하도록 변경하였다.(악몽의 시작이었다.)
원인 분석
PageImpl<RecommendPlacesFindResponse> responses = new PageImpl<>(
response.getRecommendPlaces(), pageable, totalCount);
이는 List를 Page 객체로 변환하는 코드이다. pageable, totalCount 등의 메타 정보를 이용하여 Page 정보를 채운다.
위와 같이 totalCount를 매개변수로 정확히 넘겨줬음에도 내부적으로 totalElements의 개수가 바뀌는 것이 이해가 되지 않았다.
그래서 내부를 살펴봤다.
PageImpl 생성자
public PageImpl(List<T> content, Pageable pageable, long total) {
super(content, pageable);
this.total = (Long)pageable.toOptional().filter((it) -> {
return !content.isEmpty(); // List<T> content가 empty가 아닌 경우만 찾는다.
}).filter((it) -> {
return it.getOffset() + (long)it.getPageSize() > total; // total보다 크면 왼쪽 값이 return된다.
}).map((it) -> {
return it.getOffset() + (long)content.size(); // offset + size로 값을 변경시킨다.
}).orElse(total); // 만약 위 filter에서 false로 결정되었다면 long total 값을 따른다.
}
PageImpl 생성자를 까봤을 때 다음과 같은 로직으로 되어 있었다.
(Long)pageable.toOptional().filter((it) -> { return !content.isEmpty();
1. 비어 있는 경우에는 orElse 문으로 간다.
.filter((it) -> { return it.getOffset() + (long)it.getPageSize() > total;
2. Offset(pageable.size * pageable.page)과 PageSize의 합이 total보다 크지 않으면 orElse 문으로 간다.
Offset(size * page)는 현재 페이지의 시작 인덱스를 의미하며
Offset + PageSize은 현재 페이지의 끝을 의미한다.
즉 실제 페이지의 끝의 값이 사용자가 제공한 total보다 크다면 이 값으로 대체한다.
).map((it) -> { return it.getOffset() + (long)content.size();
3. 위 두 filter 문을 통과했다면 값을 변경해 this.total에 넣는다.
.orElse(total);
4. 만약 위 두 filter에서 통과하지 못했으면 long total값을 this.total에 넣는다.
위 로직을 해석하면 사용자가 제공한 total을 믿지 않고 실제 데이터를 통해 비교하여 더 정확한 값을 넣는 로직이다.
결국 내가 매개변수로 totalElement값을 넘겨준다했을 때 그대로 사용하지 않고,
로직을 통해 검증하여 넣는다.
이 코드의 의도를 파악하지는 못했지만 나 같은 경우에는
총 41 개의 데이터가 있고, size가 5라면
총 1부터 9번의 인덱스가 생기고, 9번 페이지를 요청하는 경우
위의 2.번 filter문에서 9 * 5 + 5 > 41이므로 필터문이 통과하고
45 + 1 로 total이 46으로 변경된다.
(0번부터 인덱스를 잡았다면 40 + 1로 total size가 그대로이다.)
따라서 내부 로직으로 인해 의도치않은 문제가 생긴 것이다.
결론
자바에 구현되어있는 Pageable과 PageImpl을 사용하려면 page 인덱스를 0부터 시작하게끔 해야 한다.
따라서 나도 0번 인덱스부터 시작하도록 변경하였다.
이 외의 방법으로는 PageImpl 을 Page imterface를 상속하여 아예 다시 만드는 방법이 있다.
다만 이는 pageNumber , last , totalPages 를 저장하는 메서드를 모두 변경해야 하므로, 예상치 못한 오류가 발생할 가능성이 높아보였다.
따라서 0번 인덱스부터 시작하도록 변경하였다.
'Spring' 카테고리의 다른 글
Spring에서의 프로세스와 스레드 (0) | 2024.10.21 |
---|---|
Spring에서 @Async를 통한 비동기 처리하기 (0) | 2024.09.26 |
Spring에서의 동기, 비동기 (1) | 2024.09.26 |
[개발 일상] Spring 공통 응답 만들기 (0) | 2024.08.13 |
Spring 자바, 스프링에서 객체 응답시 is로 시작하는 변수가 변경되는 문제 (0) | 2024.05.25 |