Spring Boot/STUDY

[Spring Boot] JPA 프로젝트 - 수정 기능 구현

코맹 2024. 6. 24. 13:55

 

 

게시물 등록까지 했으니 수정 기능을 구현해보자

 

 

/entity/Board, Reply.java 수정일자 필드 추가
  @LastModifiedDate
  @Column(name = "modifyDate")
  private LocalDateTime modifyDate;
  • Board.java와 Reply.java와 동일한 컬럼을 추가

 

  • DBEAVER를 통해 modfiy_date 컬럼이 추가된 것을 확인할 수 있음

 

/templates/board/detail.html 수정, 삭제 버튼 추가
<!-- 수정/삭제 버튼 영역 -->
<div th:if="${board.writer != null and #authentication.getPrincipal().getUsername() == board.writer.username}"  
     sec:authorize="isAuthenticated()"
     class="my-3 d-flex justify-content-end">
	<a th:href="@{|/board/modify/${board.bno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
	<a th:data-uri="@{|/board/delete/${board.bno}|}" 
       href="javascript:void(0)" class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>
  • sec:authorize="isAuthenticated()" 없으면 500 에러
/templates/board/detail.html 젤 아래 <script> 추가
<script layout:fragment="sub-script" type="text/javascript">
        const del_elements = document.getElementsByClassName('delete');

        Array.from(del_elements).forEach((element) => {
            element.addEventListener('click', function() {
                if(confirm('정말로 삭제하시겠습니까?')) {
                    location.href = this.dataset.uri;
                };
            });
        });
</script>

  • 본인 게시글일 때 수정/삭제 버튼 나옴

  • 로그인을 하지 않거나 작성자 아이디와 다른 경우 버튼이 나타나지 않음

 

/controller/BoardController.java modify() GET 메서드 작성
  @PreAuthorize("isAuthenticated()")  // 로그인 시만 작성 가능
  @GetMapping("/modify/{bno}")
  public String modify(BoardForm boardForm, @PathVariable("bno") Long bno, Principal principal){
    Board board = this.boardService.getBoard(bno);    // 기본 게시글 가져옴

    if(!board.getWriter().getUsername().equals(principal.getName())) {
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }

    boardForm.setTitle(board.getTitle());
    boardForm.setContent(board.getContent());

    return "board/create";
  }
  • 로그인한 회원의 .name과 게시글 작성자 name과 비교해서 일치하는 경우 수정가능하도록 함
  • 수정 클릭시 기존의 create 게시글 제목, 내용 가져옴

  • 수정을 눌렀을 때 기존 글의 제목, 내용이 나타나는 것을 확인할 수 있음

 

/templates/board/create.html form 태그에 있는 th:action 삭제
<form th:object="${boardForm}" method="post">
  • create.html 생성, 수정할 때 모두 사용
  • get이 /board/create로 들어가면 post도 /board/create로 실행
  • get이 /board/modify/{bno}로 들어가면 post도 /board/modify/{bno}로 실행
  • 따라서 form 태그에 th:action=@{/board/create}을 적어줄 필요가 없다
    • 안지워주면 modify를 할 때도 create로 들어가는 문제 생김!!

 

/service/BoardService.java 수정 관련된 메서드 추가
 // 24.06.24. modBoard 추가 작성
  public void modBoard(Board board, String title, String content){
    board.setTitle(title);
    board.setContent(content);
    board.setModifyDate(LocalDateTime.now()); // 수정된 일시 추가

    this.boardRepository.save(board);   // PK가 있으면 UPDATE
  }

 

 

/controller/BoardController.java POST 메서드 작성
@PreAuthorize("isAuthenticated()")  // 로그인 시만 작성 가능
  @PostMapping("/modify/{bno}")
  public String modify(@Valid BoardForm boardForm,
                        BindingResult bindingResult, 
                        Principal principal,
                        @PathVariable("bno") Long bno) {

    if(bindingResult.hasErrors()) {
      return "board/create";    // 현재 html 그대로머무르기
    }
    
    Board board = this.boardService.getBoard(bno);

    if(!board.getWriter().getUsername().equals(principal.getName())){
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }

    this.boardService.modBoard(board, boardForm.getTitle(), boardForm.getContent());

      return String.format("redirect:/board/detail/%s", bno);
  }
  • html에는 BoardForm 객체 값이 들어있음. 컨트롤러에서 받아서 Board 객체 다시 만들어서 서비스로 전달
  • GET에서는 BoardForm에 저장하고, POST에서는 BOARD에 저장함

 

/service/BoardService.java 삭제관련 메서드 추가
public void remBoard(Board board) {
    this.boardRepository.delete(board);
  }

 

 

/controller/BoardController.java delete() GET 메서드 작성
@PreAuthorize("isAuthenticated()")  // 로그인 시만 작성 가능
  @GetMapping("/delete/{bno}")
  public String delete(@PathVariable("bno") Long bno, Principal principal) {
    Board board = this.boardService.getBoard(bno);

    if(!board.getWriter().getUsername().equals(principal.getName())){
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
    }

    this.boardService.remBoard(board);    // 삭제

    return "redirect:/";
  }

 

 


댓글도 똑같이 해주면 된다

/templates/board/detail.html 댓글 수정, 삭제 버튼 추가
<!-- 수정/삭제 버튼 영역 -->
<div th:if="${reply.writer != null and #authentication.getPrincipal().getUsername() == reply.writer.username}"
     sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
   <a th:href="@{|/reply/modify/${reply.rno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
   <a th:data-uri="@{|/reply/delete/${reply.rno}|}" href="javascript:void(0)"
      class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>

 

 

/service/ReplyService.java 수정, 삭제 관련 메서드 추가
  • getReply() 메서드
  // 댓글 수정하기 위해 댓글 가져오기
  public Reply getReply(Long rno) {
    Optional<Reply>  reply = this.replyRepository.findById(rno);
    if(reply.isPresent()) {
      return reply.get();

    } else {
      throw new NotFoundException("Reply NOT FOUND!");
    }
  }
  • 댓글 수정 버튼 클릭시 rno 값을 가진 내용을 가져오기 위함

 

  • modReply() 메서드
// 댓글 수정처리
public void modReply(Reply reply, String content) {
    reply.setContent(content);
    reply.setModifyDate(LocalDateTime.now());

    this.replyRepository.save(reply);
 }
  • 위 getReply()메서드에서 가져온 댓글을 수정한 후 다시 reply에 저장하기 위함

 

/controller/ReplyController.java modify() GET, POST 메서드 작성
  • modify() GET 메서드
@PreAuthorize("isAuthenticated()")  // 로그인시만 작성가능
  @GetMapping("/modify/{rno}")
  public String modify(ReplyForm replyForm, @PathVariable("rno") Long rno, Principal principal) {
    Reply reply = this.replyService.getReply(rno);

    if(!reply.getWriter().getUsername().equals(principal.getName())){
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
    }
    replyForm.setContent(reply.getContent());
    return "reply/modify";  // templates/reply/modify.html
  }
  • 수정버튼 클릭시 modify.html 화면으로 이동

 

  • modify() POST 메서드
  @PreAuthorize("isAuthenticated()")  // 로그인시만 작성가능
  @PostMapping("/modify/{rno}")
  public String modify(@Valid ReplyForm replyForm, 
                       @PathVariable("rno") Long rno,
                        BindingResult bindingResult,
                        Principal principal) {
      if(bindingResult.hasErrors()) {
        return "reply/modify";
      }
      Reply reply = this.replyService.getReply(rno);

      if(!reply.getWriter().getUsername().equals(principal.getName())){
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
      }
      
      this.replyService.modReply(reply, replyForm.getContent());

      return String.format("redirect:/board/detail/%s", reply.getBoard().getBno());
  }

 

 

/templates/reply/modify.html 생성
<html layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container">
    <h5 class="my-3 border-bottom pb-2">댓글 수정</h5>
    <form th:object="${replyForm}" method="post">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <div th:replace="~{errors :: formErrorFragment}"></div>
        <div class="mb-3">
            <label for="content" class="form-label">내용</label>
            <textarea th:field="*{content}" class="form-control" rows="10"></textarea>
        </div>
        <div class="d-flex justify-content-end">
            <input type="submit" value="저장하기" class="btn btn-primary my-2 mx-1">
            <a href="/board/list" class="btn btn-sm btn-secondary my-2">취소</a>
        </div>
        
    </form>
</div>
</html>

 

/service/ReplyService.java remply() 삭제 메서드 추가
// 댓글 삭제
  public void remReply(Reply reply) {
    this.replyRepository.delete(reply);
  }

 

 

/controller/ReplyController.java delete() 메서드 추가
  // 삭제
  @PreAuthorize("isAuthenticated()")  // 로그인시만 작성가능
  @GetMapping("/delete/{rno}")
  public String delete(@PathVariable("rno") Long rno, Principal principal) {
    Reply reply = this.replyService.getReply(rno);

    if(!reply.getWriter().getUsername().equals(principal.getName())){
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
    }

    this.replyService.remReply(reply);    // 지우기

    return String.format("redirect:/board/detail/%s", reply.getBoard().getBno());

  }

 

 

/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 th:if="${board.modifyDate !=null}" class="badge text-bg-primary p-2">
                    <div class="mb-2">
                        <span>최종수정일</span>
                    </div>
                    <div th:text="${#temporals.format(board.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
                <!-- 작성자와 작성일을 표시하는 뱃지 -->
                <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 th:if="${board.writer != null and #authentication.getPrincipal().getUsername() == board.writer.username}"
                sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
                <a th:href="@{|/board/modify/${board.bno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
                <a th:data-uri="@{|/board/delete/${board.bno}|}" href="javascript:void(0)"
                    class="delete btn btn-sm btn-outline-danger">삭제</a>
            </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 th:if="${reply.modifyDate !=null}" class="badge text-bg-primary p-2">
                    <div class="mb-2">
                        <span>최종수정일</span>
                    </div>
                    <div th:text="${#temporals.format(reply.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
                <!-- 작성자와 작성일을 표시하는 뱃지 -->
                <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(reply.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                </div>
            </div>
            <!-- 수정삭제 버튼 영역 -->
            <div th:if="${reply.writer != null and #authentication.getPrincipal().getUsername() == reply.writer.username}"
                sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
                <a th:href="@{|/reply/modify/${reply.rno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
                <a th:data-uri="@{|/reply/delete/${reply.rno}|}" href="javascript:void(0)"
                    class="delete btn btn-sm btn-outline-danger">삭제</a>
            </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>

<script layout:fragment="sub-script" type="text/javascript">
    const del_elements = document.getElementsByClassName('delete');

    Array.from(del_elements).forEach(function (element) {
        element.addEventListener('click', function () {
            if (confirm('삭제하시겠습니까?')) {
                location.href = this.dataset.uri;
            }
        });
    });

</script>

</html>

 

  • 등록일과 수정일이 잘 나오는 것을 확인할 수 있다.
  • 나중엔 수정한 날만 나오게끔 변경할 예정

 


 

게시글 목록 버튼 눌렀을 때 이전 페이지로 이동

/controller/BoardController.java detail() 메서드 수정
  @GetMapping("/detail/{bno}")
  public String detail(Model model, 
                      @PathVariable("bno") Long bno, ReplyForm replyForm, HttpServletRequest request) {
    
    String prevUrl = request.getHeader("referer");//이전 페이지 변수 담기

    log.info(String.format("현재 이전 페이지: %s", prevUrl));

    Board board = this.boardService.getBoard(bno);
    model.addAttribute("board", board);
    model.addAttribute("prevUrl", prevUrl); //이전 페이지 URL 뷰에 전달

    return "board/detail";
  }

 

 

/templates/detail.html 목록 버튼 수정
<a th:href="${prevUrl}" class="btn btn-sm btn-secondary">목록</a>