Spring Boot/STUDY

[Spring Boot] JPA 프로젝트 - 게시글 등록 시 작성자 추가

코맹 2024. 6. 21. 16:45

 

이전에 게시글 등록을 할 때는 작성자를 고려하지 않고 기능을 구현했다.

회원가입/로그인 기능까지 완성됐으니 게시글 등록시에도 작성자를 넣어주도록 하겠다.

 

/entity/Board.java와 /entity/Reply.java에 작성자 변수(속성) 추가
// 사용자가 여러 개의 게시글을 작성할 수 있다. 다대일 설정(Member가 부모, Board가 자식)
  @ManyToOne
  private Member writer;

 

 

  • DB 확인

  • WRITER_MID가 생성된 것을 확인할 수 있다.

 

/controller/ReplyController.java 코드 수정
package com.eunji.backboard.controller;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;

import com.eunji.backboard.entity.Board;
import com.eunji.backboard.service.BoardService;
import com.eunji.backboard.service.ReplyService;
import com.eunji.backboard.validation.ReplyForm;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

@RequestMapping("/reply")
@Controller
@RequiredArgsConstructor
@Log4j2
public class ReplyController {
  private final ReplyService replyService;
  private final BoardService boardService;

  // Principal 객체 추가하면 로그인한 사용자명(Member 객체)을 알 수 있음(Member 객체를 조회할 수 있다)
  @PostMapping("/create/{bno}")
  public String create(Model model, @PathVariable("bno") Long bno,
                      @Valid ReplyForm replyForm, BindingResult bindingResult,
                       Principal principal) throws Exception {

      Board board = this.boardService.getBoard(bno);    // 게시글 정보 가져오기

      if(bindingResult.hasErrors()) {
        model.addAttribute("board", board);
        return "board/detail";
      }                  

      this.replyService.setReply(board, replyForm.getContent());
      log.info("[ReplyController] 댓글 저장 처리 완료");
      
      return String.format("redirect:/board/detail/%s", bno);

  }
}
  • Principal을 파라미터에 추가

 

/service/MemberService.java에 getMember() 메서드 추가
public Member getMember(String username) {
    Optional<Member> member = this.memberRepository.findByUsername(username);
    if(member.isPresent()) 
      return member.get();
    else 
      throw new Exception("");

  }
  • 💥 throw new Exception()으로 예외처리를 하면 메서드 뒤에 항상 throws Exception을 적어줘야 되는 번거로음 발생

따라서 /common/NotFoundException.java를 생성해서 throws Exception 쓰는 데 반영해줄 예정이다

 

/common/NotFoundException.java 생성
package com.eunji.backboard.common;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "entity not found")
public class NotFoundException extends RuntimeException{
  private static final long serialVersionUID = 1L;

  public NotFoundException(String message) {
    super(message);   // RuntimeException에서 처리!
  }
  
}

 

/service/MemberService.java에 getMember() 메서드 throw 부분 변경
 public Member getMember(String username) {
    Optional<Member> member = this.memberRepository.findByUsername(username);
    if(member.isPresent()) 
      return member.get();
    else 
      throw new NotFoundException("Member not found!");

  }

 

 

/service/ReplyService.java 코드 변경
public class ReplyService {
  private final ReplyRepository replyRepository;

  public void setReply(Board board, String content, Member writer) {
    //  builder를 사용한 방식
    Reply reply = Reply.builder().content(content).createDate(LocalDateTime.now()).board(board).build();
    log.info("댓글 객체 생성");
    reply.setWriter(writer);

    this.replyRepository.save(reply);
    log.info("댓글 객체 저장");

  }
  
}
  • setReply() 파라미터 값으로 Member writer 추가
  • reply.setWriter(writer) 코드 추가 - 작성자 넣어주는 것

 

/controller/ReplyController.java 코드 변경 
package com.eunji.backboard.controller;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;

import com.eunji.backboard.entity.Board;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.service.BoardService;
import com.eunji.backboard.service.MemberService;
import com.eunji.backboard.service.ReplyService;
import com.eunji.backboard.validation.ReplyForm;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

@RequestMapping("/reply")
@Controller
@RequiredArgsConstructor
@Log4j2
public class ReplyController {
  private final ReplyService replyService;
  private final BoardService boardService;
  private final MemberService memberService;    // 작성자 입력을 위해 추가

  // Principal 객체 추가하면 로그인한 사용자명(Member 객체)을 알 수 있음(Member 객체를 조회할 수 있다)
  @PostMapping("/create/{bno}")
  public String create(Model model, @PathVariable("bno") Long bno,
                      @Valid ReplyForm replyForm, BindingResult bindingResult,
                       Principal principal) throws Exception {

      Board board = this.boardService.getBoard(bno);    // 게시글 정보 가져오기
      Member writer = this.memberService.getMember(principal.getName()); // principal.getName() -> 지금 로그인 중인 사람의 ID값

      if(bindingResult.hasErrors()) {
        model.addAttribute("board", board);
        return "board/detail";
      }                  

      this.replyService.setReply(board, replyForm.getContent(), writer);
      log.info("[ReplyController] 댓글 저장 처리 완료");
      
      return String.format("redirect:/board/detail/%s", bno);

  }
}
  • private final MemberService memberService 추가
    • 작성자 입력을 위해 추가
  • Member writer = this.memberService.getMember(principal.getName());
    • principal.getName(); -> 현재 로그인한 사람의 ID를 가져올 수 있다.
  • this.replySerivce.setReply(board, replyForm.getContent(), writer);
    • setReply() 파라미터에 writer 추가

 


Board에도 똑같이 적용

 

/service/BoardService.java setBoard() 코드 수정
 public void setBoard(String title, String content, Member writer){
    // 빌더로 생성한 객체
    Board board = Board.builder().title(title).content(content)
                       .createDate(LocalDateTime.now()).build();

    board.setWriter(writer);

    // 객체 저장
    this.boardRepository.save(board);
  }
  • setBoard() 메서드 파라미터 값으로 Member writer 추가
  • board.setWriter(writer)을 통해 값을 넣어줌

 

/controller/BoardController.java create() 코드 수정
@PostMapping("/create")
  public String create(@Valid BoardForm boardForm,
                       BindingResult bindingResult,
                       Principal principal) {
    if(bindingResult.hasErrors()) {
      return "board/create";    // 현재 html에 그대로 머무르기
    }

    Member writer = this.memberService.getMember(principal.getName());  // 현재 로그인 사용자 아이디

    // this.boardService.setBoard(title, content);
    this.boardService.setBoard(boardForm.getTitle(), boardForm.getContent(), writer);
    return "redirect:/board/list";
  }
  • private final MemberService memberService;
  • create() 메서드 파라미터값에 Principal 추가
  • Member writer = this.memberService.getMember(principal.getName());를 통해
    • 현재 로그인한 사용자 아이디를 들고 올 수 있다.
    • 이후 this.boardService.setBoard() 파라미터 값에 writer 추가

 

  • 로그인 없이 게시글을 등록하려고 하면 pricipal is null 관련 오류 발생
  • 문제를 해결하기 위해 BoardController.java와 ReplyController.java에 @PreAuthorize 추가

 

/controller/BoardController.java create() get, post 메서드와 /controller/BoardController.java create() 코드 수정
- @PreAuthorize(isAuthenticated()) 어노테이션 추가

 

  • BoardController.java
@PreAuthorize("isAuthenticated()")  // 로그인 시만 작성 가능
  @GetMapping("/create")
  public String create(BoardForm boardForm){
    return "board/create";
  }

  @PreAuthorize("isAuthenticated()")  // 로그인 시만 작성 가능
  @PostMapping("/create")
  public String create(@Valid BoardForm boardForm,
                       BindingResult bindingResult, 
                       Principal principal) {
    if(bindingResult.hasErrors()) {
      return "board/create";    // 현재 html에 그대로 머무르기
    }

    Member writer = this.memberService.getMember(principal.getName());

    // this.boardService.setBoard(title, content);
    this.boardService.setBoard(boardForm.getTitle(), boardForm.getContent(), writer);
    return "redirect:/board/list";
  }
  • ReplyController.java
@PreAuthorize("isAuthenticated()")  // 로그인시만 작성가능
  @PostMapping("/create/{bno}")
  public String create(Model model, @PathVariable("bno") Long bno,
                      @Valid ReplyForm replyForm, BindingResult bindingResult,
                       Principal principal) throws Exception {

      Board board = this.boardService.getBoard(bno);    // 게시글 정보 가져오기
      Member writer = this.memberService.getMember(principal.getName()); // principal.getName() -> 지금 로그인 중인 사람의 ID값

      if(bindingResult.hasErrors()) {
        model.addAttribute("board", board);
        return "board/detail";
      }                  

      this.replyService.setReply(board, replyForm.getContent(), writer);
      log.info("[ReplyController] 댓글 저장 처리 완료");
      
      return String.format("redirect:/board/detail/%s", bno);

  }

 

 

/security/SecurityConfig.java에 @PreAuthorize 동작하도록 설정 추가
@EnableMethodSecurity(prePostEnabled = true)  // @PreAuthorize 사용설정

 

 

/templates/board/detail.html 답변 textarea 로그인 전 로그인 후로 구분
<textarea sec:authorize="isAnonymous()" disabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
<textarea sec:authorize="isAuthenticated()" enabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>

 

로그인 전 후에 따른 댓글창

 

/templates/board/list.html에 작성자 추가
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container my-3">
  <!-- 등록 버튼 -->
  <div class="d-flex justify-content-end">
    <a th:href="@{/board/create}" class="btn btn-sm btn-primary my-2">게시글 등록</a>
  </div>
  <!-- 게시글 리스트 -->
  <table class="table table-light table-striped">
    <thead class="table-dark">
      <tr class="text-center">
        <th>번호</th>
        <th style="width: 60%">제목</th>
        <th>작성자</th>
        <th>작성일</th>
      </tr>
    </thead>
    <tbody>
      <!-- <tr th:each="board, loop: ${boardList}"> -->
      <tr th:each="board, loop: ${paging}" class="text-center">
        <td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
        <!-- <td th:text="${loop.count}"></td> -->
        <td class="text-start">
          <a th:href="@{|/board/detail/${board.bno}|}" th:text="${board.title}"></a>
          <span th:if="${#lists.size(board.replyList) > 0}" th:text="${#lists.size(board.replyList)}"
            class="badge text-bg-success">Success</span>
        </td>
        <!-- 작성자 추가 -->
        <td>
          <span th:if="${board.writer != null}" th:text="${board.writer.username}"></span>
        </td>
        <td th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></td>
      </tr>
    </tbody>
  </table>

  <!-- 페이징 시작 -->
  <div th:if="${!paging.isEmpty()}">
    <ul class="pagination justify-content-center">
      <!-- 이전버튼 -->
      <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
        <a th:href="@{|?page=0|}" class="page-link">《</a>
      </li>
      <li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
        <a th:href="@{|?page=${paging.number-1}|}" class="page-link">〈</a>
      </li>
      <!-- 페이지번호버튼 -->
      <li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}"
        th:if="${page >= paging.number-5 and page <= paging.number+5}"
        th:classappend="${page == paging.number} ? active" class="page-item">
        <a th:href="@{|?page=${page}|}" th:text="${page + 1}" class="page-link"></a>
      </li>
      <!-- 다음버튼 -->
      <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
        <a th:href="@{|?page=${paging.number+1}|}" class="page-link">〉</a>
      </li>
      <li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
        <a th:href="@{|?page=${paging.totalPages-1}|}" class="page-link">》</a>
      </li>
    </ul>
  </div>
  <!-- 페이징 끝 -->

</div>

</html>

 

  • 작성자도 함께 뜨는 것을 확인할 수 있다

 

/templates/board/detail.html 게시글 작성자, 댓글 작성자 표시 추가
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
    <div layout:fragment="main-content" class="container my-3">
        <!-- 게시글 영역 -->
        <h2 th:text="${board.title}" class="border-bottom py-2"></h2>
        <div class="card text-bg-light mb-3">
            <div class="card-body">
                <div th:text="${board.content}" class="card-text"></div>
                <div class="d-flex justify-content-end">
                    <!-- 작성자와 작성일을 표시하는 뱃지 -->
                    <div class="badge text-bg-secondary p-2">
                        <div class="mb-2">
                            <span th:if="${board.writer != null}"
                                  th:text="${board.writer.username}"></span>
                        </div>
                        <div th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 댓글 리스트 영역 -->
        <h5 th:text="|${#lists.size(board.replyList)}개의 댓글|" class=" border-bottom my-3 py-2"></h5>
        <div th:each="reply : ${board.replyList}" class="card border-info shadow-sm my-3">
            <div class="card-body">
                <div th:text="${reply.content}" class="card-text"></div>
                <div class="d-flex justify-content-end">
                    <!-- 작성자와 작성일을 표시하는 뱃지 -->
                    <div class="badge text-bg-light p-2">
                        <div class="mb-2">
                            <span th:if="${reply.writer != null}"
                                  th:text="${reply.writer.username}"></span>
                        </div>
                        <div th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 답변기능 영역 -->
        <form th:action="@{|/reply/create/${board.bno}|}" th:object="${replyForm}" method="post" class="my-3">
            <div th:replace="~{errors :: formErrorFragment}"></div>
            <textarea sec:authorize="isAnonymous()" disabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
            <textarea sec:authorize="isAuthenticated()" enabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
            <div class="d-flex justify-content-end mt-2">
                <input type="submit" value="댓글등록" class="btn btn-sm btn-primary mx-1">
                <a href="/board/list" class="btn btn-sm btn-secondary">목록</a>
            </div>
        </form>
    </div>
</html>

 

  • 게시글 작성자와 댓글 작성자가 나타나는 것을 확인할 수 있다