게시글의 정렬 처리 기능을 추가해 보자!
들어가며
이전 포스팅까지 좋아요 기능을 구현해 봤습니다. 이번 포스팅에서는 이전에 구현했던 조회수, 좋아요, 댓글 기능을 활용해 게시글의 최신글순, 좋아요 순, 댓글 많은 순, 조회수순을 통해 정렬하도록 구현해 보겠습니다.
(Spring Data JPA를 사용해 정렬 처리를 해보도록 하겠습니다.)
사전 준비
만약 게시판 프로젝트를 하시려는 분은
이전포스팅 따라 해 만들어주시기 바랍니다!
[SpringBoot] 스프링부트 - 무작정 (REST API)CRUD 게시판 만들기 좋아요 기능 구현 [13-2]
게시글의 좋아요 기능을 구현해 보자! 들어가며 이전포스팅에서는 좋아요 기능을 구현하기 위한 엔티티를 설계해 봤습니다. 이번 포스팅에서는 사용자 게시글마다 좋아요를 할 수 있으며 좋아
back-stead.tistory.com
전체 코드는 깃에 올려두었습니다.
GitHub - CHISANW/message-board
Contribute to CHISANW/message-board development by creating an account on GitHub.
github.com
구현할 내용
작성한 게시글을 최신글순, 좋아요 순, 많은 댓글순, 조회수순으로 정렬하는 버튼을 만들어 정렬되도록 구현해 보자
Repository
BoardRepository.class 내용추가
Page<Board> findAllByOrderByCountDesc(Pageable pageable); //조회수를 DESC 정렬
Page<Board> findAllByOrderByViewsDesc(Pageable pageable); // 조회수순 DESC 정렬
Page<Board> findAllByOrderByDateTimeDesc(Pageable pageable); //시간순으로 DESC 정렬
Page<Board> findAllByOrderByBoardLikeDesc(Pageable pageable); //좋아요순으로 DESC정렬
Spring Data JPA를 사용해 각각 내림차순으로 정렬하는 코드를 추가해 줍니다. 반환타입은 Page로 하며 파라미터로는 Pagealbe를 받아야 합니다. (import org.srpingframework.data.domain.* 을사용해 줍니다.)
Service
BoardSortServiceImpl.class
package messageboard.service.Impl;
import lombok.RequiredArgsConstructor;
import messageboard.Exception.BoardException;
import messageboard.entity.Board;
import messageboard.repository.BoardRepository;
import messageboard.service.BoardService;
import messageboard.service.BoardSortService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class BoardSortServiceImpl implements BoardSortService{
private final BoardRepository boardRepository;
private static final String SORT_TYPE_LIKE = "likeType";
private static final String SORT_TYPE_LAST_BOARD = "lastBoardType";
private static final String SORT_TYPE_MANY_COUNT = "manyCountType";
private static final String SORT_TYPE_MANY_VIEW = "manyViewType";
@Override
public Page<Board> TypeSort(String type,Pageable pageable) {
Page<Board> boards = null;
switch (type){
case SORT_TYPE_LIKE:
boards = likeSortDesc(pageable);
return boards;
case SORT_TYPE_LAST_BOARD:
boards = lasBoardSortDesc(pageable);
return boards;
case SORT_TYPE_MANY_COUNT:
boards= CommentSOrtDesc(pageable);
return boards;
case SORT_TYPE_MANY_VIEW:
boards= viewSortDesc(pageable);
return boards;
default:
throw new BoardException("정렬 오류발생");
}
}
@Override
public String TypeValue(String typeValue) {
String result= "";
switch (typeValue) {
case SORT_TYPE_LIKE:
result = "좋아요순";
return result;
case SORT_TYPE_LAST_BOARD:
result = "최신글순";
return result;
case SORT_TYPE_MANY_COUNT:
result = "많은 댓글순";
return result;
case SORT_TYPE_MANY_VIEW:
result = "조회수순";
return result;
default:
throw new BoardException("정렬 오류발생");
}
}
@Override
public Page<Board> likeSortDesc(Pageable pageable) {
return boardRepository.findAllByOrderByBoardLikeDesc(pageable);
}
@Override
public Page<Board> viewSortDesc(Pageable pageable) {
return boardRepository.findAllByOrderByViewsDesc(pageable);
}
@Override
public Page<Board> CommentSOrtDesc(Pageable pageable) {
return boardRepository.findAllByOrderByCountDesc(pageable);
}
@Override
public Page<Board> lasBoardSortDesc(Pageable pageable) {
return boardRepository.findAllByOrderByDateTimeDesc(pageable);
}
}
- 강한 의존성을 과 모듈화 하가 위해서 인터페이스구현해서 사용정의했습니다.
- 상수사용: TypeSort, TypeValue의 동일한 값을 사용해서 재사용을 위해 클래스 변수를 정의해 사용했습니다.
- TypeSort: 주어진 타입에 따라 게시글을 정렬합니다. likeSortDesc, lasBoardSortDesc, CommentSOrtDesc, viewSortDesc 메서드를 호출하여 해당하는 정렬 기준에 따라 정렬된 페이지를 반환합니다.
- TypeValue: 정렬 타입에 따른 값을 반환합니다. 주어진 타입에 따라 "좋아요 순", "최신글순", "많은 댓글순", "조회수순" 중 하나의 값을 반환합니다.
- likeSortDesc, viewSortDesc, CommentSOrtDesc, lasBoardSortDesc: 각각 좋아요, 조회수, 댓글 수, 최신글에 따라 게시글을 내림차순으로 정렬하여 페이지로 반환하는 메서드입니다.
Controller
BoardSortController.class
@Controller
@RequiredArgsConstructor
@Slf4j
public class BoardSortController {
private final BoardSortService boardService;
@GetMapping("/board/sortType")
public String likeSon(@RequestParam(value = "sortBoard",defaultValue = "",required = false) String sortType,
Model model,
HttpSession session,
@PageableDefault Pageable pageable){
try {
Member loginMember = (Member) session.getAttribute("loginMember");
Page<Board> boards = boardService.TypeSort(sortType, pageable);
List<Board> content = boards.getContent();
String typeValue = boardService.TypeValue(sortType);
int currentPage = boards.getPageable().getPageNumber() + 1; // 현재 페이지 (0부터 시작하는 인덱스를 1로 변환)
int totalPages = boards.getTotalPages(); // 전체 페이지 수
int visiblePages = 3;
int startPage = Math.max(1, currentPage - visiblePages / 2); // 현재 페이지를 중심으로 앞으로 visiblePages/2 만큼의 범위를 표시
int endPage = Math.min(totalPages, startPage + visiblePages - 1); // 시작 페이지부터 visiblePages 개수만큼의 범위를 표시
model.addAttribute("typeValue",typeValue);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
model.addAttribute("sortBoardList", content);
model.addAttribute("page", boards);
model.addAttribute("sortBoard", sortType);
model.addAttribute("loginMember", loginMember);
return "board/sortBoard";
}catch (BoardException e){
e.printStackTrace();
return "redirect:/board";
}catch (Exception e){
log.error("Exception occur",e);
return "redirect:/error-500";
}
}
}
뷰에게 값을 전달할 BoardSortController 작성
최대한 Controller에서는 로직을 수행하지 않기 위해서 서비스에서 로직을 처리하게 작성했으나, 페이징은 처리는 컨트롤로에서 처리하는 게 맞다고 판단해 Controller에서 처리했습니다.
VIEW
board.html 내용추가
<div class="btn-group">
<li class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
정렬기준 선택하기
</li>
<ul class="dropdown-menu dropdown-menu-end">
<li><button class="dropdown-item" type="button" value="lastBoardType" onclick="lastBoard()">최신글순</button></li>
<li><button class="dropdown-item" type="button" value="likeType" onclick="likeson()">좋아요순</button></li>
<li><button class="dropdown-item" type="button" value="manyCountType" onclick="manyComment()">많은댓글순</button></li>
<li><button class="dropdown-item" type="button" value="manyViewType" onclick="manyView()">조회수순</button></li>
</ul>
</div>
검색 폼 아래에 해당 html을 추가해 줍니다.
각 버튼이 동작할 수 있도록 jquery의 ajax를 사용해 함수를 작성해 줍니다.
// 좋아요 순으로 게시글을 정렬하는 함수
function likeson() {
var value = document.querySelector('.dropdown-item[value="likeType"]').value;
window.location.href = "/board/sortType?sortBoard=" + value;
}
// 많은 댓글 수로 게시글을 정렬하는 함수
function manyComment() {
var value = document.querySelector('.dropdown-item[value="manyCountType"]').value;
window.location.href = "/board/sortType?sortBoard=" + value;
}
// 많은 조회 수로 게시글을 정렬하는 함수
function manyView() {
var value = document.querySelector('.dropdown-item[value="manyViewType"]').value;
window.location.href = "/board/sortType?sortBoard=" + value;
}
// 최신 게시글을 정렬하는 함수
function lastBoard() {
var value = document.querySelector('.dropdown-item[value="lastBoardType"]').value;
window.location.href = "/board/sortType?sortBoard=" + value;
}
해당 버튼의 vlaue값을 이용해 정렬된 페이지로 이동하게 합니다.
sortBoard.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header"></head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script th:inline="javascript">
var loginverfiy = false;
$(document).ready(function () {
var login = document.getElementById('loginMember_username').value;
if (login !==null){
loginverfiy = true;
}
});
function writing(){
if(loginverfiy ===true){
window.location.href="/boardWrit";
}
if (loginverfiy===false){
alert("로그인 먼저 해주세요");
}
};
function likeson(){
var value = document.querySelector('.dropdown-item[value="likeType"]').value;
window.location.href = "/board/sortType?sortBoard="+value;
}
function manyComment(){
var value = document.querySelector('.dropdown-item[value="manyCountType"]').value;
window.location.href = "/board/sortType?sortBoard="+value;
}
function manyView(){
var value = document.querySelector('.dropdown-item[value="manyViewType"]').value;
window.location.href = "/board/sortType?sortBoard="+value;
}
function lastBoard(){
var value = document.querySelector('.dropdown-item[value="lastBoardType"]').value;
window.location.href = "/board/sortType?sortBoard="+value;
}
</script>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#" th:href="@{/}">CRUD 게시판</a>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<div th:if="${loginMember != null}" class="d-flex justify-content-between">
<div class="nav-item">
<a class="nav-link active" aria-current="page" th:text="'사용자: '+${loginMember.username}">Home</a>
</div>
<div class="nav-item">
<form th:action="@{/logout}" method="post">
<button type="submit" class="btn btn-link">로그아웃</button>
</form>
</div>
</div>
<div th:if="${loginMember==null}" class="d-flex justify-content-between">
<li class="nav-item" >
<a class="nav-link active" aria-current="page" th:href="@{/login}">로그인</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/createMember}">회원가입</a>
</li>
</div>
</ul>
</div>
</div>
</nav>
<div> 총 상품수 :
<span th:text="${page.totalElements}">19</span>
</div>
<form method="get" th:action="@{/board}">
<div class="col-6 d-flex justify-center">
<label for="title" class="visually-hidden">검색</label>
<input type="text" class="form-control flex-grow-1 me-2" id="title" name="title">
<button type="submit" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 9.672a6.5 6.5 0 1 0-1.07 1.07l3.53 3.53a.75.75 0 0 0 1.06-1.06l-3.52-3.54zM10.5 6A4.5 4.5 0 1 1 6 1.5 4.5 4.5 0 0 1 10.5 6z"/>
</svg>
</button>
</div>
</form>
<div class="btn-group">
<li class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<span th:text="${typeValue}"></span>
</li>
<ul class="dropdown-menu dropdown-menu-end">
<li><button class="dropdown-item" type="button" value="lastBoardType" onclick="lastBoard()">최신글순</button></li>
<li><button class="dropdown-item" type="button" value="likeType" onclick="likeson()">좋아요순</button></li>
<li><button class="dropdown-item" type="button" value="manyCountType" onclick="manyComment()">많은댓글순</button></li>
<li><button class="dropdown-item" type="button" value="manyViewType" onclick="manyView()">조회수순</button></li>
</ul>
</div>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">제목</th>
<th scope="col">작성자</th>
<th scope="col">작성일</th>
<th scope="col">조회수</th>
</tr>
</thead>
<tbody th:each="boards: ${sortBoardList}">
<tr >
<th scope="row">
<input type="hidden" th:value="${boards.id}" id="board_Id">
<a th:href="@{/board/{boardId}(boardId=${boards.id})}" th:text="${boards.id}"/>
</th>
<td>
<span th:text="${boards.title}"></span>
<span th:if="${boards.count != 0}" th:text="'('+${boards.count}+')'">a</span>
</td>
<td th:text="${boards.writer}">김민우</td>
<td th:text="${#temporals.format(boards.dateTime, 'yyyy-MM-dd')}">오늘작성</td>
<td th:text="${boards.views}">1112</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-end">
<button onclick="writing()">글쓰기</button>
</div>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item " th:classappend="${1 == page.pageable.pageNumber +1}?'disabled'">
<a class="page-link" href="#"th:href="@{/board/sortType(sortBoard=${sortBoard}, page=${page.pageable.pageNumber - 1})}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="page-item" th:classappend="${i == page.pageable.pageNumber+1}? 'disabled'" th:each="i : ${#numbers.sequence(startPage,endPage)}">
<a class="page-link" href="#" th:href="@{/board/sortType(sortBoard=${sortBoard},page=${i} -1)}" th:text="${i}" >pageNumber</a>
</li>
<li class="page-item" th:classappend="${page.totalPages == page.pageable.pageNumber +1 } ? 'disabled'">
<a class="page-link" th:href="@{/board/sortType(sortBoard=${sortBoard}, page=${page.pageable.pageNumber + 1})}" href="#">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
<div th:if="${loginMember!=null}">
<input type="hidden" id="loginMember_username" th:value="${loginMember.username}">
</div>
</body>
</html>
정렬된 페이지의 값들이 나올 수 있는 sortBaord.html를 새롭게 만어서 작성해 줍니다.
검색하는 폼은 이전에 페이징 처리에서 만들었던 것을 그대로 사용해 줍니다.
실행
각 정렬 버튼을 누를 대마다 정상적으로 정렬이 되는 것을 볼 수 있다.
다음으로
이번 포스팅까지 게시판의 정렬 기능을 구현해 봤습니다. 스프링 시큐리티를 활용한 회원가입 및 로그인을 구현해 보도록 하겠습니다.