Pageable은 Spring Data JPA에서 제공하는 인터페이스이며, 페이징 및 정렬 기능을 구현할 수 있도록 도와줍니다.
int getPageNumber() : 현재 페이지 번호를 반환합니다.
int getPageSize() : 한 페이지에 표시할 항목의 수를 반환합니다.
Sort getSort() : 정렬 기준을 반환합니다.
@GetMapping("/")
public String home(@AuthenticationPrincipal UserAccount userAccount, Model model,
Pageable pageable, //페이징 정보가 있습니다.
@RequestParam(required = false) String option, //검색 옵션 담당 null일수도 있습니다.
@RequestParam(required = false) String keyword) { //검색어 담당 null일수도 있습니다.
//spring security에서 생성된 커스텀 userDetails 객체입니다.
//엔터티를 직접적으로 참조하지 않고, 사용자 정보를 포함하고 있습니다.
if (userAccount != null) {
model.addAttribute(userAccount);
}// 인증된 사용자가 있을 때만 정보를 보여줍니다. // top nav 등 로그인 한 사용자에게만 노출하기 위함
Page<BoardDto> boardDtoPage;
if(keyword != null && !keyword.isEmpty()){
boardDtoPage = boardService.searchPosts(keyword, pageable, option);
}else {
boardDtoPage = boardService.getPostPage(pageable);
}
model.addAttribute("boardDtoPage",boardDtoPage);
model.addAttribute("keyword", keyword);
model.addAttribute("option", option);
return "index";
}
//Pageable : Spring Data 에서 제공하는 페이징 및 정렬을 위한 인터페이스
public Page<BoardDto> getPostPage(Pageable pageable) { //글 목록 - 페이징 기능 구현
//한 페이지에 출력할 글의 개수를 10개로 지정합니다. 정렬은 id를 기준으로 내림차순으로 설정합니다.
Pageable pageableWithSize10 = PageRequest.of(pageable.getPageNumber(), 10, Sort.by("id").descending());
//PageRequest.of 메서드를 이용해서 페이지 요청 객체를 만듭니다.
return boardRepository.findAll(pageableWithSize10).map(this::converToDto);
}
public Page searchPosts(String keyword, Pageable pageable, String option) { //검색어가 주어졌을때 메인화면에 게시글 출력
//한 페이지에 출력할 글의 개수를 10개로 지정합니다. 정렬은 id를 기준으로 내림차순으로 설정합니다.
Pageable pageableWithSize10 = PageRequest.of(pageable.getPageNumber(), 10, Sort.by("id").descending());
Page<Board> boardDtoPage; //페이징 처리된 결과를 나타내는 객체 //Board 객체를 포함하는 페이지네이션된 결과를 나타내는 Page 타입의 객체
switch (option) {
case "title":
boardDtoPage = boardRepository.findByTitleContainingIgnoreCase(keyword, pageableWithSize10);
break;
case "content":
boardDtoPage = boardRepository.findByContentContainingIgnoreCase(keyword, pageableWithSize10);
break;
case "writer":
boardDtoPage = boardRepository.findByWriterContainingIgnoreCase(keyword, pageableWithSize10);
break;
default:
boardDtoPage = boardRepository.findByTitleOrContentContainingIgnoreCase(keyword, pageableWithSize10);
break;
}
return boardDtoPage.map(this::converToDto);
}
private BoardDto converToDto(Board board){ //Board 엔터티 객체를 BoardDto 객체로 변환
return BoardDto.builder()
.id(board.getId())
.title(board.getTitle())
.viewCount(board.getViewCount())
.writer(board.getWriter().getNickname())
.content(board.getContent())
.createdDate(board.getCreatedDate())
.build();
}
100개의 게시물을 생성했습니다.
제목+내용으로 검색을 default로 설정 했습니다.
${option == ' '} 사용자가 이전에 선택한 검색 옵션이 폼에 유지되도록 설정합니다.
<!-- 검색 시작-->
<div class="form-inline">
<select name="option" class="btn btn-white btn-outline-info" aria-label=".form-select-sm example" style="width: 115px;height: 40px; text-align: left">
<option value="titleAndContent" th:selected="${option == 'titleAndContent'}">제목+내용</option>
<option value="title" th:selected="${option == 'title'}" >제목</option>
<option value="content" th:selected="${option == 'content'}">내용</option>
<option value="writer" th:selected="${option == 'writer'}">글쓴이</option>
</select>
<label>
<input class="btn btn-white btn-outline-info" type="text" name="keyword">
</label>
<button class="btn btn-info bi bi-arrow-return-left" type="submit">
<i class="fa fa-search"></i>
</button>
</div>
<!-- 검색 끝-->
Spring Data JPA의 page 객체가 제공하는 속성들을 사용해서 페이징 기능을 구현합니다.
first : 현재 페이지가 첫 번째 페이지인지 여부를 나타냅니다.
last : 현재 페이지가 마지막 페이지인지 여부를 나타냅니다.
size : 페이지당 데이터 수를 나타냅니다. //boardDtoPage.size 페이지를 service에서 Pageable 객체를 통해 설정합니다. (size 10으로 설정 했습니다.)
totalPages : 전체 페이지의 수를 나타냅니다.
th:if="${page >= 0 and page < boardDtoPage.totalPages}" : 페이지 번호가 0 이상이고 전체 페이지 수 보다 작을 때만 페이지 링크를 표시합니다.
numbers.sequence : boardDtoPage.number를 기준으로 앞뒤로 2개의 페이지 번호를 포함하는 시퀀스를 생성합니다.
<nav aria-label="Page navigation">
<!-- 가장 첫 페이지로 -->
<ul class="pagination justify-content-center">
<li th:class="${boardDtoPage.first} ? 'disabled'"> <!-- 첫번째 페이지의 경우 이전 페이지로 이동할 수 없음(disabled)-->
<a class="page-link" th:href="@{/(page=0, keyword=${keyword}, option=${option})}"><span aria-hidden="true">«</span></a>
</li>
<!-- 이전 페이지로 -->
<li th:class="${boardDtoPage.first} ? 'disabled'"> <!-- 첫번째 페이지의 경우 이전 페이지로 이동할 수 없음(disabled)-->
<a class="page-link" th:href="@{/(page=${boardDtoPage.number - 1}, size=${boardDtoPage.size}, keyword=${keyword}, option=${option})}">
<span aria-hidden="true">‹</span>
</a>
</li>
<!-- 페이지 네비게이션의 페이지 번호 -->
<li th:each="page : ${#numbers.sequence(boardDtoPage.number -2 , boardDtoPage.number + 2)}"
th:class="${boardDtoPage.number == page} ? 'active' : ''"
th:if="${page >= 0 and page < boardDtoPage.totalPages}">
<a class="page-link" th:href="@{/(page=${page}, size=${boardDtoPage.size}, keyword=${keyword}, option=${option})}" th:text="${page + 1}"></a>
</li>
<!-- 다음 페이지로 -->
<li th:class="${boardDtoPage.number == boardDtoPage.totalPages - 1} ? 'disabled'">
<a class="page-link" th:href="@{/(page=${boardDtoPage.number + 1}, size=${boardDtoPage.size}, keyword=${keyword}, option=${option})}">
<span aria-hidden="true">›</span> <!-- Next page symbol -->
</a>
</li>
<!-- 가장 마지막 페이지로 -->
<li th:class="${boardDtoPage.last} ? 'disabled'"> <!-- 마지막 페이지의 경우 다음 페이지로 이동할 수 없음(disabled)-->
<a class="page-link" th:href="@{/(page=${boardDtoPage.totalPages - 1}, size=${boardDtoPage.size}, keyword=${keyword}, option=${option})}">»</a>
</li>
</ul>
</nav>
페이지 번호를 -2 +2로 설정했습니다. 3을 클릭하면 12345가 출력됩니다.
keyword와 option 파라미터를 포함하고 있기 때문에
default가 아닌 다른 option을 설정하여도 검색 옵션이 반영된 상태로 페이지가 전환됩니다.
option을 제목으로 설정하고 키워드 0을 입력했습니다.
검색 폼에서 입력한 키워드를 유지하고 싶으면 input에 th:value="${keyword}" 를 추가해주면 됩니다.