[Hoops PJ] 트러블 슈팅 & TIL (5)
페이지 네이션
기존에 반환 값이 리스트였던 API중 화면설계상 페이지네이션으로 만들어야하는 요구사항이 생겼다.
이 때 각자 개발하다보니 사이즈 및 페이지 출력에 대한 사전 협의가 없어서 어떤건 1 부터 시작하고 어떤건 0 부터 시작하는 경우가 생겼었다.
해당 부분에 대한 발전과정을 기록하고 정리하고자 글을 썼다.
페이지 네이션은 아래와 같은 순서로 변경 되엇다.
- 기본 리스트 형식으로 출력
- size 로 원하는 길이의 리스트로 출력
- page 를 추가해서 size와 함께 사용하도록 변경
- page와 size 기본 시작 0 으로 변경 <- 다른 팀원들과 규격 통일
기본 리스트 형식 -> size 값을 받아서 컨트롤
변경 전
컨트롤러
@PreAuthorize("hasRole('USER')")
@GetMapping("/my-current-game-list")
public ResponseEntity<List<GameSearchResponse>> myCurrentGameList() {
return ResponseEntity.ok(gameUserService.myCurrentGameList());
}
서비스
public List<GameSearchResponse> myCurrentGameList() {
List<ParticipantGameEntity> userGameList = checkMyGameList();
List<GameEntity> games = userGameList.stream()
.map(ParticipantGameEntity::getGameEntity)
.filter(
game -> game.getStartDateTime().isAfter(LocalDateTime.now()))
.toList();
Long userId = jwtTokenExtract.currentUser().getUserId();
return getGameSearchResponses(games, userId);
}
private static List<GameSearchResponse> getGameSearchResponses(
List<GameEntity> gameListNow, Long userId) {
List<GameSearchResponse> gameList = new ArrayList<>();
gameListNow.forEach(
(e) -> gameList.add(GameSearchResponse.of(e, userId)));
return gameList;
}
변경 후
프론트 측에서 한 페이지당 데이터가 필요한 만큼 변경할 수 있게 파라미터 값을 전달해주도록 변경했다.
이 때 사이즈에 대한 정보는 @PathVariable
형식으로 전달하는 걸로 구현했다.
컨트롤러
@PreAuthorize("hasRole('USER')")
@GetMapping("/my-current-game-list/{size}")
public ResponseEntity<Page<GameSearchResponse>> myCurrentGameList(
@PathVariable int size) {
return ResponseEntity.ok(gameUserService.myCurrentGameList(size));
}
서비스
public Page<GameSearchResponse> myCurrentGameList(int size) {
List<ParticipantGameEntity> userGameList = checkMyGameList();
List<GameEntity> games = userGameList.stream()
.map(ParticipantGameEntity::getGameEntity)
.filter(
game -> game.getStartDateTime().isAfter(LocalDateTime.now()))
.toList();
Long userId = jwtTokenExtract.currentUser().getUserId();
return getPageGameSearchResponses(games, userId, size);
}
private static Page<GameSearchResponse> getPageGameSearchResponses(
List<GameEntity> gameListNow, Long userId, int size) {
List<GameSearchResponse> gameList = new ArrayList<>();
gameListNow.forEach(
(e) -> gameList.add(GameSearchResponse.of(e, userId)));
int start = 0;
int end = Math.min(size, gameList.size());
List<GameSearchResponse> pageContent = gameList.subList(0, end);
PageRequest pageable = PageRequest.of(start, end);
return new PageImpl<>(pageContent, pageable, gameList.size());
}
getPageGameSearchResponses
를 만든 후 나의 지난경기 혹은 내가 참여하고 있는경기, 동적 쿼리를 통한 경기조회들의 리턴 값에 해당 메소드를 갈이 껴서 동일한 형태로 리턴하게 만들었었다.
또한 만약에 데이터가 3개인 경우 size 부분에 5를 넣으면 index 오류가 발생하기 때문에 현재 데이터의 size를 측정하고 입력 받은 숫자와 비교해서 가장 최소 값을 반환하게 만들어서 인덱스 오류를 막을 수 있도록 처리해줬다.
하지만 문제가 있었는데 처음에 그냥 size에 맞게 알아서 만들면 된다고 생각했는데 프론트에서도 페이징 처리는 일괄적으로 맞춰서 하기 때문에 보통 page 와 size를 함께 사용해야 된다고 해서 아래와 같이 다시 변경해줬다.
size 와 page 함께 사용하도록 변경
값을 두 개 받다보니 @RequestParam
형식이 적합할 것 같아 아래와 같이 구현했다.
컨트롤러
@PreAuthorize("hasRole('USER')")
@GetMapping("/my-current-game-list")
public ResponseEntity<Page<GameSearchResponse>> myCurrentGameList(
@Positive @RequestParam int page,
@Positive @RequestParam int size) {
return ResponseEntity.ok(
gameUserService.myCurrentGameList(page, size));
}
서비스
public Page<GameSearchResponse> myCurrentGameList(int page, int size) {
List<ParticipantGameEntity> userGameList = checkMyGameList();
List<GameEntity> games = userGameList.stream()
.map(ParticipantGameEntity::getGameEntity)
.filter(
game -> game.getStartDateTime().isAfter(LocalDateTime.now()))
.toList();
Long userId = jwtTokenExtract.currentUser().getUserId();
return getPageGameSearchResponses(games, userId, page, size);
}
private static Page<GameSearchResponse> getPageGameSearchResponses(
List<GameEntity> gameListNow, Long userId, int page, int size) {
List<GameSearchResponse> gameList = new ArrayList<>();
gameListNow.forEach(
(e) -> gameList.add(GameSearchResponse.of(e, userId)));
int totalSize = gameList.size();
int totalPages = (int) Math.ceil((double) totalSize / size);
int lastPage = totalPages == 0 ? 1 : totalPages;
page = Math.min(page, lastPage);
int start = (page - 1) * size;
int end = Math.min(page * size, totalSize);
List<GameSearchResponse> pageContent = gameList.subList(start, end);
PageRequest pageable = PageRequest.of(page - 1, size);
return new PageImpl<>(pageContent, pageable, totalSize);
}
page와 size 기본 시작 1로 변경 <- 다른 팀원들과 규격 통일
이 때 당시 프론트에서 추가 요청사항이 있었는데 페이지 시작을 1 로 맞췄으면 좋겠다는 요청을 받았다.
기존에는 0을 입력 받을 경우 문제가 있었는데 해당 부분을 아래와 같이 수정해서 규격을 맞췄다.
page = Math.min(page, lastPage);
page = Math.max(1, Math.min(page, lastPage)); <- 변경
myCurrentGameList
, myLastGameList
, findFilteredGames
의 결과에 대한 반환 값을 각각 따로 만들기 보다는 유지보수를 편하게 만들기 위해서 메서드를 따로 분리해서 하나로 통일해 놨었다.
분리해둔 getPageGameSearchResponses
메서드 덕분에 해당 부분만 수정해서 규격을 쉽게 맞출 수 있어서 여러모로 편했다.
댓글남기기