현재는 컨트롤러가 아래와 같다
페이징을 할려면 서비스에서 Page한 결과를 리턴하고 컨트롤러에서 아래 처럼 반환하면 된다.
우선 페이징을 위한 컨트롤러(api)를 Version 2로 만들겠다.
@GetMapping("/bookSetting/{donationId}/v2")
public String bookSettingV2(@RequestParam(defaultValue = "0") int page,@PathVariable Long donationId, Model model) {
PageRequest pageRequest = PageRequest.of(page, 5); // page 파라미터로 받은 값을 사용
List<BookResponsePageDto> bookResponseDtos = bookApplyDonationService.getDonationBooksV2(BookStatusEnum.POSSIBLE);
model.addAttribute("books", bookResponseDtos);
model.addAttribute("donationId", donationId);
return "/admin/bookSetting";
}
컨트롤러 뿐만 아니라 새로운 DTo 역시 필요하다
왜냐면 페이지 최대 개수 역시 html에 던져 줘야 하기 때문이다.
package com.example.team258.dto;
import java.util.List;
@Data
public class BookResponsePageDto {
private List<BookResponseDto> bookResponseDtos;
private Long totalPages;
public BookResponsePageDto(List<BookResponseDto> bookResponseDtos, Long totalPages){
this.bookResponseDtos = bookResponseDtos;
this.totalPages = totalPages;
}
}
컨트롤러를 구성했으니 이제 서비스도 만들어야한다.
서비스도 기존 서비스와 이름을 다르게 V2를 붙여서 만들겠다.
public BookResponsePageDto getDonationBooksV2(BookStatusEnum bookStatus) {
Page<Book> pageBooks = bookRepository.findPageByBookStatus(bookStatus);
List<BookResponseDto> bookResponseDtos= pageBooks.stream()
.map(BookResponseDto::new)
.toList();
return new BookResponsePageDto(bookResponseDtos, pageBooks.getTotalPages());
}
페이징을 위해선 DB에서 데이터를 가져오기 위한 쿼리가 필요하다
따라서 페이징을 위한 메소드명 기반 쿼리 생성 함수를 repository에서 작성해야한다.
Page<Book>findPageByBookStatus(BookStatusEnum bookStatusEnum);
그럼 이제 최종적으로 컨트롤러를 아래와 같이 수정하면 될 것 같다
페이징을 위한 백엔드에서의 처리는 완료 된 것 같다.
그럼 이제 프론트 쪽 작업을 해야한다.
정상적으로 로직이 동작하는지 체크하기 위해 서버를 실행해 보니 에러가 발생하였다.
아래의 내용에 따라 문제를 해결 하였다.
트러블 슈팅 : Spring JPA에서 페이징 쿼리 오류 해결
기존의 book setting은 donation html의 book Setting 버튼을 클릭하면 동작한다.
여기서 버튼 동작시 Get 요청 보내는 경로를 v2로 바꿔 줘야한다.
아래 처럼 경로를 바꾼 이후 정상적으로 컨트롤러에서 매핑되는 것을 확인 가능하다.
아래처럼 지정한 데이터 수(5) 만큼만 프론트에서 나오는 것을 알 수 있다.
그럼 이제 페이지 이동을 위한 네비바를 만들어야한다.
우선 네비바를 적용할 setting.html을 settingV2.html로 복사해서 만든 이후 적용하겠다.
아래처럼 settingV2.html에 네비바 코드를 작성하였고 정상적으로 동작 되는걸 확인하였다.
5개는 너무 적은 것 같아서 15개로 했다.
이 과정도 전과 유사하다
우선 컨트롤러를 먼저 v2를 만든다.
컨트롤러에 v2 만들었고 event만을 처리하기 위한 DTO, service도 따로 만들었다.
해당 부분은 전에 페이징 했던 부분이랑 거의 유사하다
디버깅 부분을 보면 오직 이벤트 정보만 정상적으로 보내지는 것을 확인 가능하다
결과 역시 잘 나오는 것을 확인 가능하다
이제 페이지 이동을 위한 네비게이션 바를 만들면 된다.
<div>
<ul class="pagination">
<!-- 현재 페이지 번호가 10 이상일 경우, 이전 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item" th:if="${(currentPage / 10 ) * 10 > 0}">
<a class="page-link" th:href="@{/users/bookDonationEvent/v2(page=${(currentPage / 10 - 1) * 10})}" aria-label="Go to the first page of the next group">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 현재 페이지 그룹의 페이지 번호들을 표시. 현재 페이지 번호 기준으로 10개 페이지를 표시 -->
<li th:each="pageNumber : ${#numbers.sequence((currentPage / 10) * 10, (currentPage / 10) * 10 + 9)}"
th:if="${pageNumber < totalPages}"
class="page-item"
th:class="${pageNumber == currentPage ? 'active' : ''}">
<a class="page-link" th:href="@{/users/bookDonationEvent/v2(page=${pageNumber})}" th:text="${pageNumber + 1}"></a>
</li>
<!-- 현재 페이지 그룹의 마지막 페이지가 전체 페이지 수보다 작을 경우, 다음 페이지 그룹의 첫 번째 페이지로 이동 -->
<li class="page-item" th:if="${(currentPage / 10 + 1) * 10 < totalPages}">
<a class="page-link" th:href="@{/users/bookDonationEvent/v2(page= ${(currentPage / 10 + 1) * 10})}" aria-label="Go to the first page of the next group">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div>
그리고 컨트롤러가 새로 만든 V2 html로 return 하도록 수정한다.
@GetMapping("/v2")
public String bookDonationEventOnlyV2(@RequestParam(defaultValue = "0") int page, Model model) {
PageRequest pageRequest = PageRequest.of(page, 8); // page 파라미터로 받은 값을 사용
BookDonationEventOnlyPageResponseDto bookDonationEventOnlyPageResponseDto = bookDonationEventService.getDonationEventOnlyV2(pageRequest);
model.addAttribute("currentPage",page);
model.addAttribute("totalPages", bookDonationEventOnlyPageResponseDto.getTotalpages());
model.addAttribute("events", bookDonationEventOnlyPageResponseDto.getBookDonationEventOnlyResponseDtos());
return "/users/bookDonationEventV2";
}
결과
문제 1. events와 도서는 1대다 관계로 되어있다 따라서 getbooks를 하면 한번에 이벤트와 연관된 책들을 불러와서 페이징이 힘들다
->
custom query로 해결해야한다.
@GetMapping("{donationId}")
public String bookApplyDonationEventPage(Model model, @PathVariable Long donationId) {
BookDonationEvent bookDonationEvent = bookDonationEventRepository.findById(donationId).orElseThrow(
() -> new IllegalArgumentException("해당 이벤트가 존재하지 않습니다.")
);
List<Book> books = bookDonationEvent.getBooks().stream().filter(book -> book.getBookStatus().equals(BookStatusEnum.DONATION)).toList();
List<BookResponseDto> bookResponseDtos = books.stream()
.map(BookResponseDto::new)
.toList();
BookDonationEventResponseDto bookDonationEventResponseDto = new BookDonationEventResponseDto(bookDonationEvent);
model.addAttribute("bookDonationEvent", bookDonationEventResponseDto);
model.addAttribute("books", bookResponseDtos);
return "/users/bookApplyDonation";
}
아래처럼 커스텀 커리를 작성하였지만 정상적으로 동작하지 않는다.
도서와 event가 1대 다 관계라서 정상적으로 작동하지 않는 걸로 추정된다.
@Query(value = "select bde from BookDonationEvent bde" +
" join fetch bde.books book" +
" where bde.id = :donationId and book.bookStatus = :status")
Page<BookDonationEvent> findPageByDonationId(@Param("donationId") Long donationId, @Param("status") BookStatusEnum status, Pageable pageable);
그럼 getbooks 대신 하나씩 조회하면 되지 않을까?
배치사이즈 사용해서
쿼리가 이상하다
난 분명 getbooks.get(0)을 이용해서 하나의 책만을 가져왔는데 전체 책을 조회하는 쿼리가 실행되었다.
트러블 슈팅 : 디버거에서의 지연 로딩(Lazy Loading) 트랩
-> 여전히 이벤트에 해당하는 전체 book 쿼리를 가져온다
getbooks 하는 순간에 이미 전부 가져오는 것이 문제이다.
그럼 book을 타고 역으로 조회하는 방법이 최선일까?
book을 이용해서 book -> events로 커스텀 쿼리로 조회
페이징 네비바는 없지만 페이징 되서 정상적으로 나오는 것을 확인 가능하다
Controller
@GetMapping("{donationId}/v2")
public String bookApplyDonationEventPageV2(@RequestParam(defaultValue = "0") int page,Model model, @PathVariable Long donationId) {
PageRequest pageRequest = PageRequest.of(page, 15); // page 파라미터로 받은 값을 사용
BookDonationEvent bookDonationEvent = bookDonationEventRepository.findById(donationId).orElseThrow(
() -> new IllegalArgumentException("해당 이벤트가 존재하지 않습니다.")
);
Page<Book> books = bookRepository.findBooksByDonationId(donationId,BookStatusEnum.DONATION,pageRequest);
List<BookResponseDto> bookResponseDtos = books.stream()
.map(BookResponseDto::new)
.toList();
BookDonationEventResponseDto bookDonationEventResponseDto = new BookDonationEventResponseDto(bookDonationEvent);
model.addAttribute("bookDonationEvent", bookDonationEventResponseDto);
model.addAttribute("books", bookResponseDtos);
model.addAttribute("currentPage",page);
model.addAttribute("totalPages", books.getTotalPages());
return "/users/bookApplyDonation";
}
페이징 성공
페이징을 통해 이벤트 가져오고
해당 이벤트 Id에 대한 책의 페이징을 따로 가져온다