본문 바로가기

Study/Spring & Spring Boot

[Spring Boot] 게시판 기능 구현하기(3)

답글 등록 및 글 수정/삭제


Mapper

<!-- 답글 삽입 -->
<update id="bbsAnswerUpdate" parameterType="com.hwangduil.springbootbackend.dto.BbsDto">
  UPDATE BBS
  SET STEP = STEP + 1
  WHERE REF = (SELECT * FROM (SELECT REF WHERE SEQ = #{seq}) AS BB)
      AND STEP > (SELECT * FROM (SELECT STEP WHERE SEQ = #{seq}) AS BB2)
</update>

<insert id="bbsAnswer" parameterType="com.hwangduil.springbootbackend.dto.BbsDto">
  INSERT INTO BBS (SEQ, ID, REF, STEP, DEPTH, TITLE, CONTENT, WDATE, DEL, READCOUNT)
  VALUES (NEXTVAL('SEQ_BBS'),
            #{id},
            (SELECT REF FROM BBS AS BB3 WHERE SEQ = #{seq}),
            (SELECT STEP FROM BBS AS BB4 WHERE SEQ = #{seq}) + 1,
            (SELECT DEPTH FROM BBS AS BB5 WHERE SEQ = #{seq}) + 1,
            #{title},
            #{content},
            SYSDATE(),
            0,
            0
  )
</insert>

<!-- 게시글 수정 -->
<update id="bbsUpdate" parameterType="com.hwangduil.springbootbackend.dto.BbsDto">
  UPDATE BBS
  SET TITLE = #{title}, CONTENT = #{content}
  WHERE SEQ = #{seq}
</update>

<!-- 게시글 삭제 : DEL 컬럼의 숫자를 1로 바꿔주고 DB에서는 삭제하지 않음. -->
<update id="bbsDelete" parameterType="com.hwangduil.springbootbackend.dto.BbsDto">
  UPDATE BBS
  SET DEL = 1
  WHERE SEQ = #{seq}
</update>

MySQL에서는 자기 테이블을 참조할 수 없기 때문에 ALIAS를 붙여주어야 한다.

삭제 시 DEL 컬럼의 값을 1로 바꿔주어 DB에서 직접 삭제하지 않고 화면단에서만 보여주지 않게 처리할 것이다. 때문에 update를 사용하였다.

DAO

import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface BbsDao {

    List<BbsDto> getBbsList();

    void insertBbs(BbsDto dto);

    BbsDto getBbsDetail(int seq);

    void readcount(int seq);

    List<BbsDto> getBbsListSearch(BbsParam param);

    List<BbsDto> getBbsListSearchPage(BbsParam param);

    int getBbsCount(BbsParam param);

    void bbsAnswer(BbsDto dto);            // 추가

    int bbsAnswerUpdate(BbsDto dto);    // 추가

    void bbsUpdate(BbsDto dto);            // 추가

    int bbsDelete(BbsDto dto);            // 추가

}

답글을 추가하는 기능과 추가시 REF, STEP, DEPTH를 넣어주는 로직이 필요하다.
수정 및 삭제하는 메서드도 구현되어야 한다.

Service

import com.hwangduil.springbootbackend.dao.BbsDao;
import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class BbsService {

    private final BbsDao dao;

    public List<BbsDto> getBbsList() {
        return dao.getBbsList();
    }

    public void insertBbs(BbsDto dto) {
        dao.insertBbs(dto);
    }

    public BbsDto getBbsDetail(int seq) {
        return dao.getBbsDetail(seq);
    }

    public void readcount(int seq) {
        dao.readcount(seq);
    }

    public List<BbsDto> getBbsListSearch(BbsParam param) {
        return dao.getBbsListSearch(param);
    }

    public List<BbsDto> getBbsListSearchPage(BbsParam param) {
        return dao.getBbsListSearchPage(param);
    }

    public int getBbsCount(BbsParam param) {
        return dao.getBbsCount(param);
    }

    public void bbsAnswer(BbsDto dto) {            // 추가
        dao.bbsAnswer(dto);
    }

    public int bbsAnswerUpdate(BbsDto dto) {    // 추가
        return dao.bbsAnswerUpdate(dto);
    }

    public void bbsUpdate(BbsDto dto) {            // 추가
        dao.bbsUpdate(dto);
    }

    public int bbsDelete(BbsDto dto) {            // 추가
        return dao.bbsDelete(dto);
    }

}

서비스에서 DAO에 작성한 메소드와 동일한 이름으로 추가해주었다.

Controller

import com.hwangduil.springbootbackend.dto.BbsDto;
import com.hwangduil.springbootbackend.dto.BbsParam;
import com.hwangduil.springbootbackend.service.BbsService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class BbsController {

    public final Logger logger = LoggerFactory.getLogger(BbsController.class);
    private final BbsService service;

    @GetMapping("/getBbsList")
    public List<BbsDto> getBbsList() {
        logger.info("BbsController getBbsList");
        return service.getBbsList();
    }

    @GetMapping("/insertBbs")
    public void insertBbs(BbsDto dto) {
        logger.info("BbsController insertBbs() ");
        service.insertBbs(dto);
    }

    @GetMapping("/getBbsDetail")
    public BbsDto getBbsDetail(int seq) {
        logger.info("BbsController getBbsDetail()");
        service.readcount(seq);
        return service.getBbsDetail(seq);
    }

    @GetMapping("getBbsListSearch")
    public List<BbsDto> getBbsListSearch(BbsParam param) {
        logger.info("BbsController getBbsListSearch");
        return service.getBbsListSearch(param);
    }

    @GetMapping("/getBbsListSearchPage")
    public List<BbsDto> getBbsListSearchPage(BbsParam param) {
        logger.info("BbsController getBbsListSearchPage");

        // 페이지 설정
        int sn = param.getPage();   // 현재 페이지
        int start = sn * 10 + 1;
        int end = (sn + 1) * 10;

        param.setStart(start);
        param.setEnd(end);

        return service.getBbsListSearchPage(param);
    }

    @GetMapping("/getBbsCount")
    public int getBbsCount(BbsParam param) {
        logger.info("BbsController getBbsCount");
        return service.getBbsCount(param);
    }

    @GetMapping("/bbsAnswerUpdate")
    public String bbsAnswerUpdate(BbsDto dto) {            // 추가
        logger.info("seq = " + dto.getSeq());
        logger.info("title = " + dto.getTitle());
        logger.info("id = " + dto.getId());
        logger.info("content = " + dto.getContent());

        service.bbsAnswer(dto);                            // 추가
        int result = service.bbsAnswerUpdate(dto);
        if(result > 0) {
            return "fail";
        }
        return "update";
    }

    @GetMapping("bbsUpdate")
    public void bbsUpdate(BbsDto dto) {                    // 추가
        logger.info("BbsController bbsUpdate()");
        service.bbsUpdate(dto);
    }

    @GetMapping("bbsDelete")
    public int bbsDelete(BbsDto dto) {                    // 추가
        logger.info("BbsController bbsDelete()");
        dto.setDel(1);        // del의 값을 1로 조정
        return service.bbsDelete(dto);
    }

}

화면단 자바스크립트 구성


답글 추가

bbsAnswerUpdate는 프론트엔드단으로부터 아래와 같은 값을 넘겨받는다.
부모글을 찾는 요소가 seq이기 때문에 이 값을 넘겨주는 것이 중요하다.

$(document).ready(function() {
  let dataStr = sessionStorage.getItem("login");
  let data = JSON.parse(dataStr);
  console.log(data);

  const url = new URL(location.href);
  const urlParams = url.searchParams;
  let seq = urlParams.get("seq");

  console.log(seq);

  $("#writer").val(data.id);

  $("#insertBtn").click(function() {
    $.ajax({
      url: "http://localhost:3000/bbsAnswerUpdate",
      type: "GET",
      data: {
        seq: seq,
        title: $("#title").val(),
        id: $("#writer").val(),
        content: $("#content").val()
      },
      success: function(msg) {
        if(msg === "update") {
          alert("등록되었습니다.");
          location.href="boards.html";
        } else {
          alert("등록에 실패하였습니다");
        }
      },
      error: function() {
        alert("404 Error");
      }
    });
  });
});

백엔드로부터 전달받은 값에 대해 조건문으로 등록여부를 알려준다.

글 수정

const url = new URL(location.href);
const urlParams = url.searchParams;
let seq = urlParams.get("seq");

$(document).ready(function() {
  $.ajax({
    url: "http://localhost:3000/getBbsDetail",
    type: "GET",
    data: { seq: seq },
    success: function(data) {
      console.log('good');
      $("#writer").text(data.id);
      $("#title").val(data.title);
      $("#content").val(data.content)
    },
    error: function() {
      console.log('bad');
    }
  });

  $("#updateBtn").click(function() {
    $.ajax({
      url: "http://localhost:3000/bbsUpdate",
      type: "GET",
      data: {
        seq: seq,
        title: $("#title").val(),
        content: $("#content").val()
      },
      success: function() {
        console.log('success');
        alert('수정되었습니다!');
        location.href = "boards.html";
      },
      error: function() {
        console.log('error');
      }
    });
  });
})

seq 값을 날려서 게시글 데이터를 받아온 후 <input>에 뿌려주어 글을 수정할 수 있는 모드로 만들어준다.

수정하기 버튼을 눌렀을 때 수정한 글과 seq를 가지고 다시 컨트롤러로 가서 처리해준다.

삭제


$("#bbsdelete").click(function() {
  $.ajax({
    url: "http://localhost:3000/bbsDelete",
    type: "GET",
    data: { seq: seq },
    success: function(data) {
      let con = confirm('삭제하시겠습니까?');
      if (con === true) {
        location.href = "boards.html";
      }
    },
    error: function() {
      console.log(error);
    }
  })
});

여기서는 bbsDelete 패턴의 컨트롤러를 찾아 seq를 전달하여 해당 글의 DEL 컬럼의 숫자를 1로 바꿔준다.

메인화면에서 리스트로 받아주는 함수에 다소 변화가 필요하다.

function getBbsList(page) {
  let choice = $("#_choice").val();
  let search = $("#_search").val();
  $("#tbody").text("");

  $.ajax({
    url: "http://localhost:3000/getBbsListSearchPage",
    type: "GET",
    data: { choice: choice, search: search, page: page },
    success: function(list) {
      console.log(list);

      $.each(list, function(index, list) {  // index = 배열의 index
        let str;
        if(list.del === 0) {
          str =   `<tr>
                    <th>${index+1}</th>
                    <td>${getArrow(list.depth)} &nbsp;&nbsp; <a href="board-detail.html?seq=${list.seq}">${list.title}</a></td>
                    <td>${list.id}</td>
                    <td>${list.seq}</td>
                  </tr>`;
        } else {
          str =   `<tr>
                      <th>${index+1}</th>
                    <td>삭제된 게시물 입니다.</td>
                    <td>${list.id}</td>
                    <td>${list.seq}</td>
                   </tr>`;
        }
        $("#tbody").append(str);
      })
    },
    error: function() {
      console.log("error");
    }
  })
}

여기에서 조건문을 걸어주었는데, 리스트로부터 받아온 del 변수의 값이 0일 때(초기값) 세부 글 보기를 할 수 있도록 보여주고, 그렇지 않은 경우 삭제된 게시물로 막아버리는 것이다.

답글의 답글에 화살표 및 공백 추가해주기

function getArrow(depth) {
  let resource = "<img src='./img/arrow.png' width='20px' height='20px' />";
  let nbsp = "&nbsp;&nbsp;&nbsp;&nbsp;";

  let str = "";

  for(let i = 0; i < depth; i++) {
    str += nbsp;
  }

  return depth === 0 ? "" : str+resource;
}

자세한 코드는 https://github.com/htwenty-1/spring-boot.git 에서 볼 수 있음.