댓글 기능을 수정하기 전에 먼저 코드 전체적인 부분을 수정해줍니다.
priavte final 즉 생성자 주입 방식으로 교체를 하면 생성자가 필요합니다.
기존에 사용하던 @Autowired
대신에 아래 코드처럼 사용해줍니다.
private final UserService userService;
public AccountController(UserService userService) {
this.userService = userService;
}
불변성 유지
생성자 주입 방법은 필드를 final로 선언하여 불변성을 유지할 수 있습니다. 이는 객체가 생성된 이후에는 필드 값을 변경할 수 없으므로, 코드의 안정성을 높이고 예측 가능성을 높일 수 있습니다.
의존성 명확하게 표시
생성자 주입 방법은 필드에 대한 의존성을 명시적으로 표시할 수 있습니다. 즉, 객체를 생성할 때 필요한 모든 의존성을 생성자 매개변수로 전달하여, 객체 간의 의존성이 명확하게 드러나도록 할 수 있습니다.
유닛 테스트 용이성
생성자 주입 방법은 객체를 생성할 때 모든 의존성을 외부에서 전달받으므로, 유닛 테스트에서 객체를 쉽게 모의(mock)하거나 대체할 수 있습니다. 이는 테스트 코드 작성을 쉽게 만들어줍니다.
DI 컨테이너 독립성
생성자 주입 방법은 DI(Dependency Injection) 컨테이너에 의존하지 않습니다. 이는 객체 생성 방법이 명시적이고 간단하므로, DI 컨테이너 없이도 객체를 생성할 수 있습니다. 이는 코드의 유연성을 높이고, DI 컨테이너를 교체해야 할 경우에도 유용합니다.
@PreAuthorize("hasRole('ADMIN') or #board.user.username == authentication.name")
@DeleteMapping("/boards/{id}")
@Transactional
void deleteBoard(@PathVariable Long id) {
Board board = boardRepository.findById(id).orElseThrow();
List<FileData> files = board.getFiles();
if(files != null && !files.isEmpty()){
for(FileData file : files){
//파일 경로
Path filePath = Paths.get("src/main/resources/static",file.getFilepath());
//파일 삭제
try{
Files.delete(filePath);
}
catch (IOException e){
e.printStackTrace();
}
}
}
//게시글 삭제
boardRepository.deleteById(id);
}
메소드 수준 권한 체크 수정
@PreAuthorize("hasRole('ADMIN') or @boardService.isBoardAuthor(#id, authentication.name)")
//메소드 수준에서 권한 체크를 하기 위한 코드
public boolean isBoardAuthor(Long id, String username) {
Board board = boardRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Comment not found with id: " + id));
return board.getUser().getUsername().equals(username);
}
<ul>
<li th:each="file : ${files}">
<span th:text="${file.filename}"></span>
<a class="btn btn-primary" role="button" th:href="@{/api/file/download/{id}(id=${file.id})}">다운로드</a>
</li>
</ul>
a태그를 button 태그 처럼 보이게 class를 추가해줍니다.
Page<Comment> findAllByBoardId(Long boardId, Pageable pageable);
Comment
엔티티에서 boardId
필드를 기준으로 댓글을 조회하고, 페이징
처리된 결과를 반환합니다.
public Page<Comment> getCommentsByBoardIdWithPaging(Long postId, Pageable pageable) {
return commentRepository.findAllByBoardId(postId, pageable);
}
@GetMapping("/post")
public String post(Model model, @RequestParam(required = false) Long id, Principal principal,
@PageableDefault(size = 5) Pageable pageable){
Board board = boardRepository.findById(id).orElse(null);
List<FileData> files = fileService.findByBoardId(id);
User user = userService.findByUsername(principal.getName());
model.addAttribute("board", board);
model.addAttribute("files", files);
model.addAttribute("userId", user.getId());
Page<Comment> comments = commentService.getCommentsByBoardIdWithPaging(id, pageable);
model.addAttribute("comments", comments);
int block = 5;
int currentBlock = (comments.getPageable().getPageNumber() / block) * block;
int startPage = currentBlock + 1;
int endPage = Math.min(comments.getTotalPages(), currentBlock + block);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
model.addAttribute("comments", comments);
return "board/post";
}
이전에 사용한 페이징 처리 방식을 그대로 사용해줍니다.
@RequestParam
을 이용하여 url 뒤에 붙일 id를 가져오고 페이징 처리를 해줍니다.
현재 댓글 페이징 처리에서 오름차순, 내림차순 기능을 추가해줍니다.
Page<Comment> findAllByBoardIdOrderByCreatedAtDesc(Long boardId, Pageable pageable);
Page<Comment> findAllByBoardIdOrderByCreatedAtAsc(Long boardId, Pageable pageable);
public Page<Comment> getCommentsByBoardIdWithPagingDesc(Long boardId, Pageable pageable) {
return commentRepository.findAllByBoardIdOrderByCreatedAtDesc(boardId, pageable);
}
public Page<Comment> getCommentsByBoardIdWithPagingAsc(Long boardId, Pageable pageable) {
return commentRepository.findAllByBoardIdOrderByCreatedAtAsc(boardId, pageable);
}
@GetMapping("/post")
public String post(Model model, @RequestParam(required = false) Long id, Principal principal,
@PageableDefault(size = 5) Pageable pageable, @RequestParam(required = false) String commentOrderBy,
HttpSession session){
if (commentOrderBy == null) {
commentOrderBy = (String) session.getAttribute("commentOrderBy");
if (commentOrderBy == null) {
commentOrderBy = "desc";
}
}
session.setAttribute("commentOrderBy", commentOrderBy);
Board board = boardRepository.findById(id).orElse(null);
List<FileData> files = fileService.findByBoardId(id);
User user = userService.findByUsername(principal.getName());
model.addAttribute("board", board);
model.addAttribute("files", files);
model.addAttribute("userId", user.getId());
Page<Comment> comments;
if (commentOrderBy.equals("asc")) {
comments = commentService.getCommentsByBoardIdWithPagingAsc(id, pageable);
} else {
comments = commentService.getCommentsByBoardIdWithPagingDesc(id, pageable);
}
model.addAttribute("comments", comments);
int block = 5;
int currentBlock = (comments.getPageable().getPageNumber() / block) * block;
int startPage = currentBlock + 1;
int endPage = Math.min(comments.getTotalPages(), currentBlock + block);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
model.addAttribute("comments", comments);
model.addAttribute("commentOrderBy", commentOrderBy);
return "board/post";
}
commentOrderBy = (String) session.getAttribute("commentOrderBy");
이 코드는 session
에서 "commentOrderBy"
라는 이름의 attribute값
을 가져오는 코드입니다.
commentOrderBy라는 변수에는 세션에서 "commentOrderBy"로 저장된 값을 가져와서 할당합니다. 이후 commentOrderBy 변수를 사용하여 해당 속성 값을 활용할 수 있습니다.
이 전에 사용한 페이징 처리와 유사한 코드입니다.
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{/board/post(id=${board.id},page=0)}">First</a>
</li>
<li class="page-item" th:classappend="${1 == comments.pageable.pageNumber + 1} ? 'disabled'">
<a class="page-link" th:href="@{/board/post(id=${board.id},page=${comments.pageable.pageNumber - 1})}">Previous</a>
</li>
<li class="page-item" th:classappend="${i == comments.pageable.pageNumber + 1 && i != 0} ? 'disabled'"
th:each="i : ${#numbers.sequence(startPage, endPage)}">
<a class="page-link" href="#" th:href="@{/board/post(id=${board.id},page=${i - 1})}"
th:text="${i}" th:if="${i != 0}"></a>
</li>
<li class="page-item" th:classappend="${comments.totalPages == comments.pageable.pageNumber + 1 || comments.totalPages == 0} ? 'disabled'">
<a class="page-link" th:href="@{/board/post(id=${board.id},page=${comments.pageable.pageNumber + 1})}">Next</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{/board/post(id=${board.id},page=${comments.totalPages - 1})}">Last</a>
</li>
</ul>
</nav>
<div>
<label for="commentOrderBy" class="sr-only">정렬 방식</label>
<select id="commentOrderBy" class="custom-select" style="width: 120px"
th:onchange="|location.href='?id=' + ${board.id} + '&commentOrderBy=' + this.value;|">
<option value="desc" th:selected="${commentOrderBy == 'desc'}">최신순</option>
<option value="asc" th:selected="${commentOrderBy == 'asc'}">오래된순</option>
</select>
</div>
게시글 페이징 처리를 위한 버튼을 만들어줍니다.
th:onchange="|location.href='?id=' + ${board.id} + '&commentOrderBy=' + this.value;|">
이 코드는 사용자가 드롭다운 메뉴에서 선택한 값을 this.value로 가져와서 새로운 URL을 만들어 페이지를 새로고침합니다. 이 URL에는 두 개의 쿼리 매개변수가 포함됩니다: id와 commentOrderBy. id는 ${board.id}로 설정되고 commentOrderBy는 사용자가 선택한 값으로 설정됩니다.
게시글이 하나도 없을 경우 1번 페이지 다음에 0번 페이지가 뜨고
게시글이 없는 경우에도 next 버튼이 눌리는 문제를 수정해줍니다.
<li class="page-item" th:classappend="${i == boards.pageable.pageNumber + 1} ? 'disabled'"
th:each="i : ${#numbers.sequence(startPage, endPage)}"><a class="page-link" href="#"
th:href="@{/board/list(page=${i -1},searchText=${param.searchText})}"
th:text="${i}" th:if="${i != 0}"></a></li>
<li class="page-item" th:classappend="${boards.totalPages == boards.pageable.pageNumber + 1 || boards.totalPages == 0} ? 'disabled'">
<a class="page-link" th:href="@{/board/list(page=${boards.pageable.pageNumber + 1},searchText=${param.searchText})}">Next</a>
</li>
댓글 페이징 사진입니다.