현재 질문 목록 화면을 유심히 보면 페이지마다 게시물 번호가 항상 1부터 시작되는 문제가 있다. 페이지를 이리저리 이동해 봐도 게시물 번호는 1부터 시작한다. 이 문제를 해결해 보자.
두번째 페이지로 이동하더라도 여전히 게시물 번호가 1부터 시작된다.
게시물 번호 공식 만들기
만약 질문 게시물이 12개라면 0페이지에는 12번째~3번째 게시물이, 1페이지에는 2번째~1번째 게시물이 역순으로 표시되어야 한다. 질문 게시물의 번호를 역순으로 정렬하려면 다음과 같은 공식을 적용해야 한다.
번호 = 전체 게시물 개수 - (현재 페이지 * 페이지당 게시물 개수) - 나열 인덱스
항목설명
번호
최종 표시될 게시물 번호
전체 게시물 개수
데이터베이스에 저장된 게시물 전체 개수
현재 페이지
페이징에서 현재 선택한 페이지 (만약 페이지가 1부터 시작한다면 1을 빼주어야 한다. 하지만 스프링부트의 페이징은 0부터 시작하므로 1을 뺄 필요가 없다.)
페이지당 게시물 개수
한 페이지당 보여줄 게시물의 개수
나열 인덱스
for 문 안의 게시물 순서 (나열 인덱스는 현재 페이지에서 표시할 수 있는 게시물의 인덱스이므로 10개를 표시하는 페이지에서는 0~9, 2개를 표시하는 페이지에서는 0~1로 반복된다.)
공식이 조금 복잡하니 질문 게시물이 12개인 상황을 예로 들어 설명해 보자. 현재 페이지가0이면 번호는 전체 게시물 개수 12에서 나열 인덱스 0~9를 뺀 12~3이 된다. 현재 페이지가 1이면 페이지당 게시물 개수는 10이므로 12에서 10을 뺀 값 2에 나열 인덱스 0~1을 다시 빼므로 번호는 2~1이다.
게시물 번호 공식을 질문 목록 템플릿에 적용하기
이제 게시물 번호 공식을 다음처럼 질문 목록 템플릿에 적용해 보자.다음 코드의 1번째 td 엘리먼트에 이 공식을 그대로 적용했다.
질문 목록을 조회하는 getList 메서드를 위와 같이 변경했다. getList 메서드는 정수 타입의 페이지번호를 입력받아 해당 페이지의 질문 목록을 리턴하는 메서드로 변경했다. Pageable 객체를 생성할때 사용한PageRequest.of(page, 10)에서 page는 조회할 페이지의 번호이고 10은 한 페이지에 보여줄 게시물의 갯수를 의미한다. 이렇게 하면 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경된다.
Question 서비스의 getList 메서드의 입출력 구조가 변경되었으므로 Question 컨트롤러도 다음과 같이 수정해야 한다.
http://localhost:8080/question/list?page=0처럼 GET 방식으로 요청된 URL에서 page값을 가져오기 위해@RequestParam(value="page", defaultValue="0") int page매개변수가 list 메서드에 추가되었다. URL에 페이지 파라미터 page가 전달되지 않은 경우 디폴트 값으로 0이 되도록 설정했다.
스프링부트의 페이징은 첫페이지 번호가 1이 아닌 0이다.
그리고 템플릿에Page<Question>객체인 paging을 전달했다. Page 객체에는 다음과 같은 속성이 있다. 다음의 속성들은 템플릿에서 페이징을 처리할 때 사용할 것이다.
항목설명
paging.isEmpty
페이지 존재 여부 (게시물이 있으면 false, 없으면 true)
paging.totalElements
전체 게시물 개수
paging.totalPages
전체 페이지 개수
paging.size
페이지당 보여줄 게시물 개수
paging.number
현재 페이지 번호
paging.hasPrevious
이전 페이지 존재 여부
paging.hasNext
다음 페이지 존재 여부
그리고 기존에 전달했던 이름인 "questionList" 대신 "paging" 이름으로 템플릿에 전달했기 때문에 템플릿도 다음과 같이 변경해야 한다.
이전 페이지가 없는 경우에는 "이전" 링크가 비활성화(disabled)되도록 하였다. (다음페이지의 경우도 마찬가지 방법으로 적용했다.) 그리고 페이지 리스트를 루프 돌면서 해당 페이지로 이동할 수 있는 링크를 생성하였다. 이때 루프 도중의 페이지가 현재 페이지와 같을 경우에는 active클래스를 적용하여 강조표시(선택표시)도 해 주었다.
타임리프의th:classappend="조건식 ? 클래스값"속성은 조건식이 참인 경우 클래스값을 class 속성에 추가한다.
#numbers.sequence(시작, 끝)은 시작 번호부터 끝 번호까지의 루프를 만들어 내는 타임리프의 유틸리티이다. 그리고 페이지 리스트를 보기 좋게 표시하기 위해 부트스트랩의 pagination 컴포넌트를 이용하였다. 템플릿에 사용한pagination,page-item,page-link등이 부트스트랩 pagination 컴포넌트의 클래스이다.
게시물을 역순으로 조회하기 위해서는 위와 같이PageRequest.of메서드의 세번째 파라미터로 Sort 객체를 전달해야 한다.Sort.Order객체로 구성된 리스트에Sort.Order객체를 추가하고Sort.by(소트리스트)로 소트 객체를 생성할 수 있다. 작성일시(createDate)를 역순(Desc)으로 조회하려면Sort.Order.desc("createDate")같이 작성한다.
만약 작성일시 외에 추가로 정렬조건이 필요할 경우에는 sorts 리스트에 추가하면 된다.
이렇게 수정하고 첫번째 페이지를 조회하면 이제 가장 최근에 등록된 순으로 게시물이 출력되는 것을 확인한 수 있다.
축하한다! 페이징 기능이 완성되었다. 페이징은 사실 구현하기 까다로운 기술이다. 페이징 클래스의 도움이 없었다면 아마 이렇게 쉽게 해내기는 힘들었을 것이다.
지금까지 만든 페이징 기능에 '처음'과 '마지막' 링크를 추가하고 '…' 생략 기호까지 추가하면 더 완성도 높은 페이징 기능이 될 것이다.