[Spring JPA] 11. 게시판 글 수정하기

YB·2023년 8월 5일
0

JPA

목록 보기
12/12

1. 게시판 글 수정하기 html

list.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
<div layout:fragment="content" class="content">
  <form th:action th:object="${form}" method="get">
    <nav class="container">
      <br>
      <div class="input-group">
        <input type="text" name="searchVal" th:value="${searchVal}" class="form-control" placeholder="제목을 입력해주세요.">
        <button type="submit" class="btn btn-secondary">검색</button>
      </div>
      <br>
      <table class="table table-hover">
        <colgroup>
          <col width="2%" />
          <col width="5%" />
          <col width="20%" />
          <col width="5%" />
          <col width="5%" />
          <col width="5%" />
        </colgroup>
        <thead>
        <tr>
          <th>
            <label class="checkbox-inline">
              <input type="checkbox" id="allCheckBox" onclick="allChecked()">
            </label>
          </th>
          <th>번호</th>
          <th>제목</th>
          <th>작성자</th>
          <th>날짜</th>
          <th>조회수</th>
        </tr>
        </thead>

        <tbody>
        <tr th:each="list, index : ${list}">
          <td>
            <label class="checkbox-inline">
              <input type="checkbox" name="chk" class="chk"onclick="chkClicked()" th:value="${list.id}">
            </label>
          <td th:text="${totalCount - (size * number) - index.index}"></td>
          <td><a th:text="${list.title}" th:href="@{/update/{boardId}(boardId=${list.id})}"></a></td>
          <td th:text="${list.username}"></td>
          <td th:text="${#temporals.format(list.regDate, 'yyyy-MM-dd')}"></td>
          <td th:text="${list.viewCount}"></td>
        </tr>
        </tbody>
      </table>
      <br>

      <div class="d-flex justify-content-end">
        <a href='javascript:boardDelete();' class="btn btn-danger">글삭제</a>
        <a href="/write" class="btn btn-primary">글쓰기</a>
      </div>
      <br>
      <nav class="container d-flex align-items-center justify-content-center" aria-label="Page navigation example"
           th:with="start=${(list.number/maxPage)*maxPage + 1},
                      end=(${(list.totalPages == 0) ? 1 : (start + (maxPage - 1) < list.totalPages ? start + (maxPage - 1) : list.totalPages)})">
        <ul class="pagination">

          <li th:if="${start > 1}" class="page-item">
            <a th:href="@{/?(page=0, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
              <span aria-hidden="true">&laquo;&laquo;</span>
            </a>
          </li>

          <li th:if="${start > 1}" class="page-item">
            <a th:href="@{/?(page=${start - maxPage-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Previous">
              <span aria-hidden="true">&laquo;</span>
            </a>
          </li>

          <li th:each="page: ${#numbers.sequence(start, end)}" class="page-item" th:classappend="${list.number+1 == page} ? active">
            <a th:href="@{/?(page=${page-1}, searchVal=${searchVal})}" th:text="${page}" class="page-link" href="#">1</a>
          </li>

          <li th:if="${end < list.totalPages}" class="page-item">
            <a th:href="@{/?(page=${start + maxPage -1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
              <span aria-hidden="true">&raquo;</span>
            </a>
          </li>

          <li th:if="${end < list.totalPages}" class="page-item">
            <a th:href="@{/?(page=${list.totalPages-1}, searchVal=${searchVal})}" class="page-link" href="#" aria-label="Next">
              <span aria-hidden="true">&raquo;&raquo;</span>
            </a>
          </li>
        </ul>
      </nav>
    </nav>
  </form>
</div>
</html>

<script>

  //체크박스 전체 선택 클릭 이벤트
  function allChecked(target){

    //전체 체크박스 버튼
    const checkbox = document.getElementById('allCheckBox');

    //전체 체크박스 버튼 체크 여부
    const is_checked = checkbox.checked;

    //전체 체크박스 제외한 모든 체크박스
    if(is_checked){
      //체크박스 전체 체크
      chkAllChecked()
    }

    else{
      //체크박스 전체 해제
      chkAllUnChecked()
    }
  }

  //자식 체크박스 클릭 이벤트
  function chkClicked(){

    //체크박스 전체개수
    const allCount = document.querySelectorAll(".chk").length;

    //체크된 체크박스 전체개수
    const query = 'input[name="chk"]:checked'
    const selectedElements = document.querySelectorAll(query)
    const selectedElementsCnt = selectedElements.length;

    //체크박스 전체개수와 체크된 체크박스 전체개수가 같으면 전체 체크박스 체크
    if(allCount == selectedElementsCnt){
      document.getElementById('allCheckBox').checked = true;
    }

    //같지않으면 전체 체크박스 해제
    else{
      document.getElementById('allCheckBox').checked = false;
    }
  }

  //체크박스 전체 체크
  function chkAllChecked(){
    document.querySelectorAll(".chk").forEach(function(v, i) {
      v.checked = true;
    });
  }

  //체크박스 전체 체크 해제
  function chkAllUnChecked(){
    document.querySelectorAll(".chk").forEach(function(v, i) {
      v.checked = false;
    });
  }

  //글삭제
  function boardDelete(){
    //체크박스 체크된 항목
    const query = 'input[name="chk"]:checked'
    const selectedElements = document.querySelectorAll(query)

    //체크박스 체크된 항목의 개수
    const selectedElementsCnt = selectedElements.length;

    if(selectedElementsCnt == 0){
      alert("삭제할 항목을 선택해주세요.");
      return false;
    }

    else{
      if (confirm("정말로 삭제하시겠습니까?")) {
        //배열생성
        const arr = new Array(selectedElementsCnt);

        document.querySelectorAll('input[name="chk"]:checked').forEach(function(v, i) {
          arr[i] = v.value;
        });

        const form = document.createElement('form');
        form.setAttribute('method', 'post');        //Post 메소드 적용
        form.setAttribute('action', '/delete');

        var input1 = document.createElement('input');
        input1.setAttribute("type", "hidden");
        input1.setAttribute("name", "boardIds");
        input1.setAttribute("value", arr);
        form.appendChild(input1);
        console.log(form);
        document.body.appendChild(form);
        form.submit();
      }
    }
  }

</script>

제목 클릭 시, update/{id} url로 넘어가도록 설정

update.html


<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/default_layout}">

<head layout:fragment="css">
  <style>
         .fieldError {
             border-color: #bd2130;
         }

         .form-group p{
            color: red;
         }
    </style>
</head>

<div layout:fragment="content" class="content">
  <form th:action="@{/update/ + *{id}}" th:object="${boardDto}" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="hidden" name="id" th:value="*{id}" />
    <article>
      <div class="container" role="main">
        <div class="form-group">
          <label for="title">제목</label>
          <input type="text" class="form-control" id="title" name="title" th:value="*{title}" placeholder="제목을 입력해 주세요" th:class="${#fields.hasErrors('title')}? 'form-control fieldError' : 'form-control'">
          <p th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Incorrect date</p>
        </div>
        <br>
        <div class="mb-3">
          <label for="reg_id">작성자</label>
          <input type="text" class="form-control" id="reg_id" name="regId"  value="관리자" readonly>
        </div>
        <br>
        <div class="mb-3">
          <label for="content">내용</label>
          <textarea class="form-control" rows="5" id="content" name="content" th:text="*{content}" placeholder="내용을 입력해 주세요"></textarea>
        </div>
        <br>
        <br>
        <div>
          <button type="submit" class="btn btn-sm btn-primary" id="btnSave">수정</button>
          <button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
        </div>
      </div>
    </article>
  </form>
</div>
</html>

<script>
</script>

list.html과 update.html에 빨간줄이 표시될 수 있지만 타임리프 문법을 인식하지 못하여 생기는 오류이기 때문에 무시

2. 게시글 수정 Controller 및 Service

BoardService.java

package jpa.board.service;

import jpa.board.dto.BoardDto;
import jpa.board.entity.Board;
import jpa.board.entity.Member;
import jpa.board.repository.BoardRepository;
import jpa.board.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;
    private final MemberRepository memberRepository;

    public Board selectBoardDetail(Long id) {
        return boardRepository.findById(id).get();
    }

    @Transactional
    public Long saveBoard(BoardDto boardDto) {
        List<Member> memberList = memberRepository.findAll();
        Member member = memberList.get(0);
        Board board = null;

        //insert
        if (boardDto.getId() == null) {
            board = boardDto.toEntity(member);
            boardRepository.save(board);
        } else { //update
            board = boardRepository.findById(boardDto.getId()).get();
            board.update(boardDto.getTitle(), boardDto.getContent());
        }

        return board.getId();
    }

    @Transactional
    public Board deleteBoard(Long id) {
        Board board = boardRepository.findById(id).get();

        //플래그값이 Y이면 논리삭제
        board.delete("Y");
        return board;
    }

}

selectBoardDetail method 생성

BoardController.java

package jpa.board.controller;

import jpa.board.dto.BoardDto;
import jpa.board.entity.Board;
import jpa.board.repository.CustomBoardRepository;
import jpa.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@Controller
@RequiredArgsConstructor
public class BoardController {

    private final CustomBoardRepository customBoardRepository;
    private final BoardService boardService;

    @GetMapping("/")
    public String list(String searchVal, Pageable pageable, Model model) {
        Page<BoardDto> results = customBoardRepository.selectBoardList(searchVal, pageable);
        model.addAttribute("list", results);
        model.addAttribute("maxPage", 5);
        model.addAttribute("searchVal", searchVal);

        pageModelPut(results, model);
        return "board/list";
    }

    private void pageModelPut(Page<BoardDto> results, Model model) {
        model.addAttribute("totalCount", results.getTotalElements());
        model.addAttribute("size", results.getPageable().getPageSize());
        model.addAttribute("number", results.getPageable().getPageNumber());
    }

    @GetMapping("/write")
    public String write(Model model) {
        model.addAttribute("boardDto", new BoardDto());
        return "board/write";
    }

    @PostMapping("/write")
    public String save(@Valid BoardDto boardDto, BindingResult result) {

        //유효성검사 걸릴 시
        if (result.hasErrors()) {
            return "board/write";
        }

        boardService.saveBoard(boardDto);
        return "redirect:/";
    }

    @GetMapping("/update/{boardId}")
    public String detail(@PathVariable Long boardId, Model model) {
        Board board = boardService.selectBoardDetail(boardId);
        BoardDto boardDto = new BoardDto();
        boardDto.setId(boardId);
        boardDto.setTitle(board.getTitle());
        boardDto.setContent(board.getContent());
        model.addAttribute("boardDto", boardDto);

        return "board/update";
    }

    @PutMapping("/update/{boardId}")
    public String update(@Valid BoardDto boardDto, BindingResult result) {
        //유효성검사 걸릴 시
        if (result.hasErrors()) {
            return "board/update";
        }

        boardService.saveBoard(boardDto);
        return "redirect:/";
    }

    @PostMapping("/delete")
    public String delete(@RequestParam List<String> boardIds) {

        for(int i = 0; i < boardIds.size(); i++) {
            Long id = Long.valueOf(boardIds.get(i));
            boardService.deleteBoard(id);
        }

        return "redirect:/";
    }
}

/update api를 타면 Validation 처리 후 이상이 없으면 저장하는 방식

3. Put 방식 설정

application.yml

spring:  
  mvc:
    hidden-method:
      filter:
        enabled: true

put이나 delete로 Controller로 전송할 경우 에러가 발생하는 경우가 있기 때문에 간단한 설정 필요

4. 결과 화면

profile
개인이 공부한걸 작성하는 블로그입니다..

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

유익한 글이었습니다.

답글 달기