카테고리 없음

[Staffriends 프로젝트] (2) 게시판 CRUD 구현하기 - 3. 글수정 및 글삭제

sungw00 2023. 5. 24. 06:07
728x90

글수정 및 글삭제

게시글 상세보기 화면에서 버튼을 클릭하면 해당 글을 수정 또는 삭제할 수 있는 화면으로 진입할 수 있도록 구현했다.

1. updateForm.jsp 작성

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ include file="../layout/header.jsp"%>
<c:if test="${signIn == null}"><c:redirect url="http://staffriends.duckdns.org/user/needLogin"/></c:if>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>board</title>
    <style>
      .ck.ck-editor {max-width: 90%; height: auto; margin: 0 auto;}
      .textarea {width: 100%; border: none; resize: none; height: 3.1rem;}
    </style>
</head>
<body class="center-div">
<div style="text-align:center">
<div class="container">
  <div class="container justify-content-center">
    <h2 style="text-align: center; margin-top: 30px; margin-bottom: 20px; font-family: KakaoBold;">게시글 수정</h2>
  <form id="frm" method="post">
    <table style="margin-left:auto;margin-right:auto; margin: 0 auto">
    <tbody>
      <tr>
          <td>
              <textarea class="textarea" id="title" name="title" placeholder="제목을 입력해주세요." style="font-size: 30px">${boardVo.title}</textarea>
              <hr style="margin-top: 0;"/>
          </td>
      </tr>
      <tr>
          <td colspan="4" class="view_text" style="max-width: 30%; height: auto">
            <textarea title="내용" id="contents" name="contents" style="text-align: center; ">${boardVo.contents}</textarea>
            <script>
                writeEditor();
            </script>
          </td>
      </tr>
    </tbody>
  </table>
  <input type="hidden" id="boardIdx" name="boardIdx" value="${boardVo.boardIdx}">
  </form>
    <div class="btn-group" role="group" aria-label="Basic mixed styles example">
        <button type="button" class="btn btn-warning" id="list" style="margin-top: 20px; margin-bottom: 20px;">되돌아가기</button>
        <button type="button" class="btn btn-success" id="successEdit" style="margin-top: 20px; margin-bottom: 20px;">수정완료</button>
    </div>
    <script>
        modifyBoard();
    </script>
  </div>
</div>
</div>
</body>
<%@ include file="../layout/footer.jsp"%>

게시글 수정 페이지에서는 제목 및 내용을 가져와 수정할 수 있는 상태로 출력해주고, 이전페이지로 되돌아가는 버튼과 수정완료 버튼을 추가하였다.

 

게시글 삭제 버튼을 클릭하면 따로 페이지 이동 없이 게시글 삭제가 완료되었다는 알림창을 띄우고 목록 화면으로 돌아가도록 구현했다.

 

2. BoardController 작성

package board.controller;

import board.vo.BoardVo;
import board.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;

@Controller
public class BoardController {

    @Autowired
    private BoardService boardService;

    @RequestMapping({"/", ""})
    public String index() throws Exception {
        return "/index";
    }

    @RequestMapping("/boardList") // 게시글 리스트 출력 및 페이징
    public ModelAndView boardList(HttpServletRequest request) throws Exception {
        System.out.println("page:"+request.getParameter("page"));
        ModelAndView mv = new ModelAndView();
        Map<String, Integer> map = boardService.paging(request.getParameter("page")); // 쿼리 스트링의 페이지 번호를 전달

        List<BoardVo> list = boardService.selectBoardList(map); // 게시글 정보(BoardVo)
        int startPage = map.get("startPage"); // 시작 페이지 번호
        int endPage = map.get("endPage"); // 끝 페이지 번호
        int totalPages = map.get("totalPages"); // 전체 페이지 갯수
        int cPage = map.get("cPage"); // 현재 페이지 번호

        mv.addObject("list", list);
        mv.addObject("startPage", startPage);
        mv.addObject("endPage", endPage);
        mv.addObject("totalPages", totalPages);
        mv.addObject("cPage", cPage);
        mv.setViewName("/board/boardList");
        return mv;
    }

    @RequestMapping("/board/boardWrite") // 글 작성 페이지
    public String boardWrite() {
        return "/board/boardWrite";
    }

    @RequestMapping("/board/insertBoard") // 글 작성 로직
    public String insertBoard(BoardVo boardVo, @RequestParam String username, @RequestParam String nickname) throws Exception {
        boardVo.setUsername(username);
        boardVo.setNickname(nickname);
        boardService.insertBoard(boardVo);
        return "redirect:/board";
    }

    @RequestMapping("/board/boardDetail") // 글 상세 보기
    public String boardDetail(@RequestParam int boardIdx, Model model) throws Exception {
        BoardVo boardVo = boardService.selectBoardDetail(boardIdx);
        model.addAttribute("board", boardVo);

        return "/board/boardDetail";
    }

    @RequestMapping("/board/updateForm") // 글 수정 폼 요청
    public String updateForm(@RequestParam int boardIdx, Model model) throws Exception {
        BoardVo boardVo = boardService.selectBoardDetail(boardIdx);
        model.addAttribute("boardVo", boardVo);
        return "/board/updateForm";
    }

    @RequestMapping("/board/modifyBoard") // 글 수정 요청
    public String modifyBoard(Model model, BoardVo boardVo) throws Exception {
        int boardIdx = boardVo.getBoardIdx();
        boardService.updateBoard(boardVo);
        model.addAttribute("boardVo", boardVo);
        return "redirect:/board/boardDetail?boardIdx="+boardIdx;
    }

    @RequestMapping("/board/deleteBoard") // 글 삭제
    public String deleteBoard(BoardVo boardVo) throws Exception {
        boardService.deleteBoard(boardVo);
        return "redirect:/board";
    }
}

updateForm, modifyBoard, deleteBoard 메서드가 추가되었다.

  • updateForm 메서드는 boardIdx에 해당하는 게시글의 정보를 가져와 글 수정 페이지로 이동시킨다.
  • modifyForm 메서드는 전달받은 boardVo의 boardIdx를 구한 뒤 해당하는 게시글 내용을 업데이트 시킨 뒤 업데이트 된 게시글 상세보기 화면으로 이동한다.
  • deleteBoard 메서드는 단순히 해당 게시글의 deleted_yn의 값을 "Y"로 업데이트 시켜 게시글 목록에서 더이상 출력되지 않도록 한다.

deleted_yn이 'Y'로 업데이트 된 게시글은
게시글 목록에 더이상 보여지지 않는다.

 

3. BoardService, BoardServiceImpl, BoardMapper 작성

BoardService.java

package board.service;

import board.vo.BoardVo;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public interface BoardService {
    List<BoardVo> selectBoardList(Map<String, Integer> map) throws Exception;

    void insertBoard(BoardVo boardVo) throws Exception;

    BoardVo selectBoardDetail(int boardIdx) throws Exception;

    void updateBoard(BoardVo boardVo) throws Exception;

    void deleteBoard(BoardVo boardVo) throws Exception;

    int getTotalRows();

    Map<String, Integer> paging(String tempPage) throws Exception;
}

BoardServiceImpl.java

package board.service;

import board.vo.BoardVo;
import board.mapper.BoardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class BoardServiceImpl implements BoardService {

    @Autowired
    private BoardMapper boardMapper;

    @Override
    public List<BoardVo> selectBoardList(Map<String, Integer> map) throws Exception { // 게시판 목록 출력
        return boardMapper.selectBoardList(map);
    }

    @Override
    public void insertBoard(BoardVo boardVo) throws Exception { // 글쓰기
        boardMapper.insertBoard(boardVo);
    }

    @Override
    public BoardVo selectBoardDetail(int boardIdx) throws Exception { // 글 상세보기(조회수 증가)
        boardMapper.updateHitCount(boardIdx);
        return boardMapper.selectBoardDetail(boardIdx);
    }

    @Override
    public void updateBoard(BoardVo boardVo) throws Exception { // 글 수정
        boardMapper.updateBoard(boardVo);
    }

    @Override
    public void deleteBoard(BoardVo boardVo) throws Exception{ // 글 삭제
        boardMapper.deleteBoard(boardVo);
    }

    @Override
    public int getTotalRows() { // 전체 행 조회
        return boardMapper.getTotalRows();
    }

    @Override
    public Map<String, Integer> paging(String tempPage) throws Exception {
        Map<String, Integer> map = new HashMap<>();
        int cPage; // 현재 위치한 페이지의 번호
        int currentBlock; // 페이지 블록의 갯수
        int pageLength = 10; // 보여줄 게시글의 갯수
        int startPage; // 페이지 블록의 시작 번호
        int endPage; // 페이지 블록의 마지막 번호
        int totalPages; // 전체 페이지의 갯수
        int start; // SQL 쿼리의 LIMIT에 들어갈 변수



//        // cPage(현재 위치한 페이지의 번호 정하기)
//        if (tempPage == null || tempPage.length() == 0 || tempPage.equals("0")) { // 파라미터로 전달받은 페이지 정보가 없거나 0이면 1페이지로 이동
//            cPage = 1;
//        }

        try {
            cPage = Integer.parseInt(tempPage); // 파라미터로 전달받은 페이지 번호를 현재 페이지에 변환하여 담아줌
        } catch (NumberFormatException e) { // 에러 발생 시(숫자가 아닌 문자가 들어오는 경우) 1로 설정
            cPage = 1;
        }

        int totalRows = getTotalRows(); // 전체 게시글의 갯수

        totalPages = totalRows % pageLength == 0 ? totalRows / pageLength : (totalRows / pageLength) + 1; // 필요한 총 페이지의 개수는 데이터의 개수를 보여줄 게시글의 개수로 나누어 딱맞으면 0, 초과하면 1개의 페이지를 더 생성
        if (totalPages == 0) { // 현재 게시글 데이터의 수가 페이지 최소 단위보다 적은 경우 최소 1개의 페이지를 생성
            totalPages = 1;
        }

        if (cPage > totalPages || cPage <= 0) { // 페이지가 최소 또는 최대 페이지 범위를 벗어난 경우 1로 설정
            cPage = 1;
        }

        // currentBlock: 현재 위치한 페이지 블럭
        // cPage: 현재 페이지
        // pageLength: 전체 페이지 길이
        // startPage: 페이지 블럭의 가장 첫번째 번호
        // endPage: 페이지 블럭의 가장 마지막 번호

        // 페이지 처음과 끝을 지정하는 부분
        currentBlock = cPage % pageLength == 0 ? cPage / pageLength : (cPage / pageLength) + 1; // 현재 위치한 페이지 블록 구하기(ex. cPage=1, pageLength=10 -> 1)
        startPage = (currentBlock - 1) * pageLength + 1; // 페이지 블럭의 시작 번호 구하기(ex. currentBlock=1, pageLength=10 -> 1)
        endPage = startPage + pageLength - 1; // 페이지 블럭의 마지막 번호 구하기(ex. startPage=1, pageLength=10 -> 10)

        // 마지막 페이지 묶음에서 총 페이지수를 넘어가면 끝 페이지를 마지막 페이지 숫자로 지정
        if (endPage > totalPages) {
            endPage = totalPages;
        }

        start = (cPage - 1) * pageLength; // 각 페이지의 첫 번째 게시글의 번호를 구하기 : (현재 페이지 - 1) * 보여줄 게시글의 갯수(ex. 1페이지=0~9, 2페이지=10~19, 3페이지=20~29)

        // 첫 번째 게시글의 번호와 보여줄 게시글의 갯수를 가지고 게시글 목록을 조회(총 pageLength개의 데이터를 가져오며, 현재 페이지 - 1 * pageLength에 해당하는 번호의 게시글들이 조회됨)
        map.put("start", start);
        map.put("pageLength", pageLength);
        map.put("startPage", startPage);
        map.put("endPage", endPage);
        map.put("totalPages", totalPages);
        map.put("cPage", cPage);

        return map;
    }
}

BoardMapper.java

package board.mapper;

import board.vo.BoardVo;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
import java.util.Map;

@Mapper
public interface BoardMapper {
    List<BoardVo> selectBoardList(Map<String, Integer> map) throws Exception;

    void insertBoard(BoardVo boardVo) throws Exception;

    void updateHitCount(int boardIdx) throws Exception;

    BoardVo selectBoardDetail(int boardIdx) throws Exception;

    void updateBoard(BoardVo boardVo) throws Exception;

    void deleteBoard(BoardVo boardVo) throws Exception;

    int getTotalRows();
}

 

4. js/board.js 작성

function editAndDelete() {
    let frm = document.getElementById("frm");

    let list = document.getElementById("list");
    let editButton = document.createElement("button"); // 수정하기 버튼 생성
    editButton.type = 'button';
    editButton.id = 'edit';
    editButton.innerHTML = '수정';
    editButton.className = 'btn btn-warning';
    editButton.style = 'margin-top: 20px; margin-bottom: 20px;';
    list.insertAdjacentElement("afterend", editButton);

    let deleteButton = document.createElement("button"); // 삭제하기 버튼 생성
    deleteButton.type = 'button';
    deleteButton.id = 'delete';
    deleteButton.innerHTML = '삭제';
    deleteButton.className = 'btn btn-danger';
    deleteButton.style = 'margin-top: 20px; margin-bottom: 20px;';
    editButton.insertAdjacentElement("afterend", deleteButton);

    document.getElementById("edit").onclick = function(event) {
        frm.action = "/board/updateForm";
        frm.submit();
    }

    document.getElementById("delete").onclick = function(event) {
        frm.action = "/board/deleteBoard";
        frm.submit();
        alert('게시글 삭제가 완료되었습니다.');
    }
}

function showBoardDetail() {
    ClassicEditor.create( document.querySelector( '#editor' ) ) // 읽기 전용 모드
        .then(editor => {
            editor.enableReadOnlyMode('true');
        })

    document.getElementById("list").onclick = function(event) {
        history.back();
    }
}

function insertBoard() {
    document.getElementById("save").onclick = function(event) {
        let frm = document.getElementById("frm");
        frm.action = "/board/insertBoard";
        frm.submit();
        alert('글 작성이 완료되었습니다.');
    }
}

function writeEditor() {
    CKEDITOR.replace('contents', {
        filebrowserUploadUrl: '/fileUpload',
        height: '500px'
    });
}

function modifyBoard() {
    let frm = document.getElementById("frm");
    document.getElementById("successEdit").onclick = function(event) {
        let boardIdx = document.getElementById('boardIdx');
        frm.action = "/board/modifyBoard";
        frm.submit();
        alert('수정이 완료되었습니다.');
    }

    document.getElementById("list").onclick = function(event) {
        history.back();
        // event.preventDefault(); // 클릭 시 결과 실행 후 대기
    }
}

 

5. sql-board.xml 작성

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="board.mapper.BoardMapper">
    <select id="selectBoardList" resultType="board.vo.BoardVo" parameterType="Map">
        <![CDATA[
            SELECT
                board_idx,
                title,
                hit_cnt,
                DATE_FORMAT(created_datetime, '%Y.%m.%d %H:%i:%s') AS created_datetime,
                reply_count,
                username,
                nickname
            FROM
                board
            WHERE
                deleted_yn = 'N'
            ORDER BY board_idx DESC
            LIMIT #{start}, #{pageLength}
        ]]>
    </select>

    <select id="getTotalRows" resultType="int">
        <![CDATA[
            SELECT
                count(*)
            FROM
                board
            WHERE
                deleted_yn = 'N'
        ]]>
    </select>
    
    <insert id="insertBoard" parameterType="board.vo.BoardVo">
        <![CDATA[
            INSERT INTO board
                (
                    title,
                    contents,
                    created_datetime,
                    username,
                    nickname
                )
            VALUES
                (
                   #{title},
                   #{contents},
                   now(),
                   #{username},
                   #{nickname}
                )
            ]]>
    </insert>

    <update id="updateHitCount" parameterType="int">
        <![CDATA[
            UPDATE
                board
            SET
                hit_cnt = hit_cnt + 1
            WHERE
                board_idx = #{boardIdx}
        ]]>
    </update>

    <select id="selectBoardDetail" parameterType="int" resultType="board.vo.BoardVo">
        <![CDATA[
            SELECT
                board_idx, title, contents, hit_cnt,
                DATE_FORMAT(created_datetime, '%Y.%m.%d %H:%i:%s') AS created_datetime,
                username, nickname
            FROM
                board
            WHERE
                board_idx = #{boardIdx} AND deleted_yn = 'N'
        ]]>
    </select>

    <update id="updateBoard" parameterType="board.vo.BoardVo">
        <![CDATA[
            UPDATE
                board
            SET
                title = #{title},
                contents = #{contents},
                updated_datetime = now(),
                updater_id = #{username}
            WHERE
                board_idx = #{boardIdx}
        ]]>
    </update>

    <update id="deleteBoard" parameterType="int"> -- 글 삭제
        <![CDATA[
            UPDATE
                board
            SET
                deleted_yn = 'Y',
                updated_datetime = now(),
                updater_id = #{username}
            WHERE
                board_idx = #{boardIdx}
        ]]>
    </update>
</mapper>

 

프로젝트 전체 코드 Github 주소

https://github.com/sungwoo-jo/Staffriends-Project

 

GitHub - sungwoo-jo/Staffriends-Project: AI 스마트 시각장애인 지팡이

AI 스마트 시각장애인 지팡이. Contribute to sungwoo-jo/Staffriends-Project development by creating an account on GitHub.

github.com

 

728x90