주말에 스터디를 참여하며 많이 들었던 말이
웹 개발자는 처음부터 끝까지 게시판을 생각해야 한다는 말이었다.
게시판을 만들 때 CRUD 기능이 필요하고
여기서 Read는 페이징 처리 기능이 필수적이다.
오늘은 공기밥에 페이징 처리를 적용하는 과정을 적어보겠다.
먼저 컨트롤러에서 Pageable을 받아준다.
이전 내 게시글을 보면 알겠지만 원래는 pageNo만 받아서 pageRequest를 직접 만들어 repository에 줬다. 하지만 이러한 과정은 객체지향적이지 않다고 생각했다.
컨트롤러에서 page에 대한 모든 세팅을 하고 시작하는 것이다.
@GetMapping("/restaurant/list")
public ModelAndView getResturantList(HttpServletRequest request, @PageableDefault(page = 1, size = 5)Pageable pageable) {
}
Page<Place> placePage = null;
try {
int page = pageable.getPageNumber();
// page 번호 예외처리 (음수인 경우)
if(page <= 0) {
return null;
}
if(request.getParameter("selectedDistrict") == null || "".equals(request.getParameter("selectedDistrict").trim())) {
return placePage;
}
int districtId = Integer.parseInt(request.getParameter("selectedDistrict"));
PageRequest pageRequest = PageRequest.of(page-1, pageable.getPageSize(), Sort.by("place_id").descending());
placePage = placeRepository.findByDistrictId(pageRequest, districtId);
return placePage;
}
catch (NumberFormatException e) {
// 도시 id 지역 id가 숫자가 아닌 값이 들어올 경우 예외처리
log.info("NumberFormatException 에러 발생 : " + e.toString());
return null;
}
page 번호가 음수인 경우 예외처리를 해준다.
page 번호에 특수문자가 글자가 들어갈 경우 page 번호에는 0이 들어가게 된다.
공백을 넣게 되면 default 값인 1이 들어가는데 왜 그럴까?
그건 Spring Data JPA의 방식에 따라 0으로 처리되기 때문이다.
그래서 컨트롤러에서 받은 pageable을 가지고 pagereqeust에 정렬 속성을 추가하여 jpa에 던저준다.
받은 페이지를 컨트롤러에 리턴한다.
@GetMapping("/restaurant/list")
public ModelAndView getResturantList(HttpServletRequest request, @PageableDefault(page = 1, size = 5)Pageable pageable) {
ModelAndView model = new ModelAndView();
List<City> cities = restaurantService.getCities();
Page<Place> placePage = restaurantService.getPageByDistrictId(request, pageable);
if(placePage != null) {
// 페이지 바 만들기
int blockSize = 5;
int startPage = (((int) Math.ceil(((double) pageable.getPageNumber() / blockSize))) - 1) * blockSize + 1;
int endPage = Math.min(startPage + blockSize - 1, placePage.getTotalPages());
model.addObject("startPage", startPage);
model.addObject("endPage", endPage);
}
페이지 바는 다른 블로그에서 참고했는데
startPage에 대한 설정이 어려울 것 같아 설명을 해보겠다.
pageable.getPageNumber는 현재 페이지 번호를 리턴하는데
page의 시작값은 0이다. 0 1 2 3~ 이런식인것을 항상 인지해야 한다.
(Math.ceil(0 / 5) - 1) 5 + 1 =
( (1) - 1) 5 + 1 = 1
(Math.ceil(1 / 5) - 1) 5 + 1 =
( (1) - 1) 5 + 1 = 1
(Math.ceil(5 / 5) - 1) 5 + 1 =
( (1) - 1) 5 + 1 = 1
--
(Math.ceil(6 / 5) - 1) 5 + 1 =
( (2) - 1) 5 + 1 = 6
(Math.ceil(11 / 5) - 1) 5 + 1 =
( (3) - 1) 5 + 1 = 11
뭐 이런식이다.
Math.min(startPage + blockSize - 1, placePage.getTotalPages());
참고로 이건 전체 페이지 번호를 가져와서
만약 블록 크기 보다 작게 되면 (5개의 블록보다 작은 경우 3페이지가 끝이거나 6페이지가 끝이거나) 그 부분을 선택하기 위한 것이다.
그렇지 않으면 5, 10, 15가 끝 페이지가 될 것이다.
<div class="pagebar-wrap" th:if="${placePage != null} ">
<!-- 맨처음 버튼 -->
<a th:href="@{/restaurant/list(selectedCity=${selectedCity}, selectedDistrict=${selectedDistrict}, page=1)}"><<</a>
<!-- 이전 버튼 -->
<a th:href="@{${placePage.first} ? '' : @{/restaurant/list(selectedCity=${selectedCity}, selectedDistrict=${selectedDistrict}, page=${placePage.number})}}">이전</a>
<!-- 페이지 바 -->
<span th:each="page: ${#numbers.sequence(startPage, endPage)}">
<span th:if="${page == placePage.number + 1} " th:text="${page}" th:style="${'margin: 0 2vw; color: red'}"></span>
<span th:unless="${page == placePage.number + 1}">
<a th:href="@{/restaurant/list(selectedCity=${selectedCity}, selectedDistrict=${selectedDistrict}, page=${page})}" th:text="${page}"></a>
</span>
</span>
<!-- 다음 버튼 -->
<a th:href="@{${placePage.last} ? '' : @{/restaurant/list(selectedCity=${selectedCity}, selectedDistrict=${selectedDistrict}, page=${placePage.number+2})}}">다음</a>
<!-- 맨마지막 페이지 버튼 -->
<a th:href="@{/restaurant/list(selectedCity=${selectedCity}, selectedDistrict=${selectedDistrict}, page=${placePage.totalPages})}">>></a>
</div>
타임리프 문법이 어려워서 정말 공부하면서 작성했다...
완성