댓글에 대한 댓글을 작성하는 것을 무한 댓글, 계층형 댓글, 대댓글 이라고 한다.

이 무한 댓글을 개발하기 위해서는 3가지 필드가 필요하다.


먼저, 부모를 표시하는 필드(reparent),

현재 댓글이 게시물을 기준으로 어느 정도 떨어져 있는지,

즉 사람으로 치면 이순신 장순의 몇 대손인지를 나타내는 깊이(redepth),

게시물을 기준으로 몇 번째 글인지를 나타내는 순서(reorder) 필드가 필요하다.

부모 필드(reparent)는 당연이 필요한 것으로 여겨지지만

없어도 기능적인 문제는 발생하지 않는다.

부모 댓글을 삭제 할 때 자식 댓글을 삭제하지 않으면

엉뚱한 댓글에 붙어서 나오게 된다.

자식까지 삭제하기 위해 부모 댓글 필드가 필요한 것이다.

깊이(redepth)는 트리 구조로 보여주기 위해 필요한 필드이다.

부모 댓글 보다 조금 더 들여쓰기 하여 트리처럼 보여 주게 된다.

가장 이해가 어렵고 중요한 필드는 댓글들의 순서를 의미하는 순서(reorder) 필드이다.


하나의 게시물에 2개의 댓글이 달렸다고 하면

댓글 번호(reno)와 순서(reorder)는 1, 2번 일 것이다.

이 상태에서 1번 댓글에 댓글을 달면 새로운 댓글의 댓글 번호는 3이 되지만 순서는 2가 되어야 한다.

그리고, 댓글 번호 2의 순서는 3이 되어야 한다.

그래야지만 순서대로 화면에 출력되어 보여지게 된다.


더 예를 들면,

댓글 번호 1번에 다시 댓글을 달면

새 댓글은 댓글 번호 4가 되고 순서는 2가 된다.

댓글 번호 3가 되고 순서는 3가 되고

댓글 번호 2가 되고 순서는 4가 된다.


즉, 중간에 값이 들어오기 때문에 순서가 하나씩 증가하게 되는 것이다.

새로운 댓글은

기존 댓글 번호의 최대값에 1 더한 값을 가지지만

순서는 부모 댓글의 순서 값에 1 더한 값을 가지고,

부모 댓글의 순서 값보다 큰 값들은 모두 1씩 더한 값을 가지게 된다.



개념을 이해했으면

다음 SQL문을 실행하고 BoardReplyVO에 변수 reparent(부모), redepth(깊이), reorder(순서)를 추가한다.

public class BoardReplyVO {
    private String brdno;
    private String reno;
    private String rewriter;
    private String redeleteflag;
    private String rememo;
    private String redate;
    private String reparent;
    private String redepth;
    private Integer reorder;

   
    ~~ 생략 ~~

    public String getReparent() {
        return reparent;
    }
   
    public void setReparent(String reparent) {
        this.reparent = reparent;
    }

    public String getRedepth() {
        return redepth;
    }

    public void setRedepth(String redepth) {
        this.redepth = redepth;
    }

    public Integer getReorder() {
        return reorder;
    }

    public void setReorder(Integer reorder) {
        this.reorder = reorder;
    }
   
}

BoardReplyVO.java


다음 SQL문을 직접 실행해서 필드를 추가해 주고 (ALTER ),

기존 댓글의 데이터에 값을 넣어준다(UPDATE).

ALTER TABLE TBL_BOARDREPLY ADD(
      REPARENT INT(11),
      REDEPTH INT,
      REORDER INT
);

UPDATE TBL_BOARDREPLY SET REPARENT=RENO, REDEPTH=0, REORDER=RENO;



'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

2. 무한 댓글 리스트  (11) 2016.05.15
3. 무한 댓글 쓰기 – control  (3) 2016.05.15
4. 무한 댓글 쓰기 – JSP  (0) 2016.05.15
5. 무한 댓글 삭제  (1) 2016.05.15
6. 무한 댓글 삭제 II  (0) 2016.05.15
댓글의 댓글은 계층형 구조를 가지고 저장되고

화면에 출력될 때도 계층형(트리 구조)으로 출력된다.

이 계층형 구조는 부모 글과 자식 글을 구분하기 위한 것으로

자식 글을 부모 글 보다 뒤로 들여쓰기 해서 다음 그림과 같이 트리(?)처럼 표현 할 수 있다.


여기서는 하나의 댓글을 둘러싼 박스(DIV)를 뒤로 물러나게 만들었다.

댓글 박스를 만드는 DIV의 좌표 값을(margin-left )값을 20px씩 더 주면 들여쓰기 형태로 구현된다.

즉, 댓글의 깊이(redepth)값에 20씩 곱하여 계산한다.

기존의 코드에 다음 코드를 추가하여 간단하게 처리하였다.

<c:forEach var="replylist" items="${replylist}" varStatus="status">
    <div style="border: 1px solid gray; width: 600px; padding: 5px; margin-top: 5px;
          margin-left: <c:out value="${20*replylist.redepth}"/>px; display: inline-block">   
        <c:out value="${replylist.rewriter}"/> <c:out value="${replylist.redate}"/>
        <a href="#" onclick="fn_replyDelete('<c:out value="${replylist.reno}"/>')">삭제</a>
        <a href="#" onclick="fn_replyUpdate('<c:out value="${replylist.reno}"/>')">수정</a>
        <a href="#" onclick="fn_replyReply('<c:out value="${replylist.reno}"/>')">댓글</a>
        <br/>
        <div id="reply<c:out value="${replylist.reno}"/>"><c:out value="${replylist.rememo}"/></div>
    </div><br/>
</c:forEach>





'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

1. 무한 댓글 시작하기  (14) 2016.05.15
3. 무한 댓글 쓰기 – control  (3) 2016.05.15
4. 무한 댓글 쓰기 – JSP  (0) 2016.05.15
5. 무한 댓글 삭제  (1) 2016.05.15
6. 무한 댓글 삭제 II  (0) 2016.05.15

댓글을 저장하기 위해서는 2가지를 고려해야 한다.

먼저, 게시물에 대하여 댓글을 달은 경우와 댓글에 댓글을 달은 경우이다.

게시물에 대하여 댓글을 달은 경우는 부모가 없는 경우로 깊이(redepth)가 0이된다.

개발자에 따라 차이가 있지만 부모가 없다는 걸 의미하기 위해 깊이를 0으로 주고

그 외에 reparent 값을 0으로 주거나 reparent 값을 reno로 준다.

여기에서는 reparent 값을 reno로 주었다.

순서(reorder)값은 게시물 내에서 최대값을(selectBoard6ReplyMaxOrder)을 가지게 된다.


댓글에 댓글을 달은 경우는

부모 댓글의 정보(selectBoard6ReplyParent)를 가지고 와서

깊이(redepth)는 부모 댓글에 1을 더한 값을 가지고

순서(reorder)도 부모 순서에 1 더한 값을 가진다.

부모(reparent)는 부모 댓글의 댓글 번호(reno)를 지정한다.

여기서는 깊이는 Java에서 1 더했고, 순서는 SQL에서 1을 더했다.

어느 쪽에서 처리해도 되지만 개인적으로 SQL에서 처리하는 것을 선호한다.

    public void insertBoardReply(BoardReplyVO param) {
        if (param.getReno() == null || "".equals(param.getReno())) {
            if (param.getReparent() != null) {
                BoardReplyVO replyInfo = sqlSession.selectOne("selectBoard6ReplyParent", param.getReparent());
                param.setRedepth(replyInfo.getRedepth());
                param.setReorder(replyInfo.getReorder() + 1);
                sqlSession.selectOne("updateBoard6ReplyOrder", replyInfo);
            } else {
                Integer reorder = sqlSession.selectOne("selectBoard6ReplyMaxOrder", param.getBrdno());
                param.setReorder(reorder);
            }
           
            sqlSession.insert("insertBoard6Reply", param);
        } else {
            sqlSession.insert("updateBoard6Reply", param);
        }
    }

Board6Svc.java

   <select id="selectBoard6ReplyParent" resultType="gu.board6.BoardReplyVO" parameterType="String">
        SELECT BRDNO, REDEPTH+1 REDEPTH, REORDER
          FROM TBL_BOARDREPLY
         WHERE RENO=#{reparent}
    </select>
    <select id="selectBoard6ReplyMaxOrder" resultType="Integer" parameterType="String">
        SELECT IFNULL(MAX(REORDER),0)+1
          FROM TBL_BOARDREPLY
         WHERE BRDNO=#{brdno}
    </select>
   <update id="updateBoard6ReplyOrder" parameterType="gu.board6.BoardReplyVO">
        UPDATE TBL_BOARDREPLY
           SET REORDER = REORDER + 1
         WHERE BRDNO=#{brdno} AND REORDER>#{reorder}      
    </update>

board6.xml


마지막으로 저장하기 전에

updateBoard6ReplyOrder를 실행해서 부모 댓글의 순서(reorder)보다 큰 값들을 1씩 증가 시켜야 한다.

이유는 기본 개념에서 설명했다.


저장(INSERT)은 다음과 같이 바꾸어야 한다.

    <insert id="insertBoard6Reply" parameterType="gu.board6.BoardReplyVO" >
        <selectKey resultType="String" keyProperty="reno" order="BEFORE">
            SELECT IFNULL(MAX(RENO),0)+1 FROM TBL_BOARDREPLY
        </selectKey>
   
        INSERT INTO TBL_BOARDREPLY(BRDNO, RENO, REWRITER, REDELETEFLAG, REMEMO, REDATE, REORDER, REPARENT, REDEPTH)
        VALUES (#{brdno}, #{reno}, #{rewriter}, 'N', #{rememo}, NOW(), #{reorder},
                   <choose>
                       <when test="reparent==null">#{reno}, 0</when>
                       <otherwise>#{reparent}, #{redepth}</otherwise>
                   </choose>
               )
    </insert>

board6.xml

순서는 자바에서 처리해서 넘어오기 때문에 그대로 저장한다.

다만, 부모 댓글과 깊이는

게시물에 대한 댓글일 경우(부모가 없는 경우, reparent==null)에는 자기 번호와 0을 주고

댓글의 댓글일 경우는 Java에서 처리된 값을 가지게 된다.

이러한 처리도 Java에서 처리하면 되지만 여기서는 MyBatis에서 처리했다.

개발자의 선택사항이고 이 경우에는 개인적으로는 자바에서 처리하는 걸 선호한다.


수정은 기존 코드 그대로 지정된 댓글번호에 대하여 UPDATE문을 실행한다.


'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

1. 무한 댓글 시작하기  (14) 2016.05.15
2. 무한 댓글 리스트  (11) 2016.05.15
4. 무한 댓글 쓰기 – JSP  (0) 2016.05.15
5. 무한 댓글 삭제  (1) 2016.05.15
6. 무한 댓글 삭제 II  (0) 2016.05.15

댓글의 댓글을 쓰는 화면을 위해서는

게시물에 다는 첫 댓글 작성과 같이 별도의 화면과 4개의 자바스크립트 함수가 필요하다.

기존의 첫 댓글화면과 유사한 구조이지만 다른 이름을 부여 해서

다음과 같이 별도의 화면(replyDialog와 form3)을 만들어야 한다.

폼의 컨트롤은 댓글이 달리는 게시물번호(brdno), 부모글번호(reparent), 수정을 위해 자기 댓글번호(reno)가 숨져진 필드로 저장되고,

사용자가 입력하는 댓글 내용(rememo)과 작성자(rewriter)가 필요하다.

이 컨트롤들의 이름은 게시물에 대한 댓글이나 수정시 사용하는 컨트롤과 이름이 동일하다.

이름이 동일해도 폼 이름이 form3으로 다르다.

<div id="replyDialog" style="width: 99%; display:none">
    <form name="form3" action="board6ReplySave" method="post">
        <input type="hidden" name="brdno" value="<c:out value="${boardInfo.brdno}"/>">
        <input type="hidden" name="reno">
        <input type="hidden" name="reparent">
        작성자: <input type="text" name="rewriter" size="20" maxlength="20"> <br/>
        <textarea name="rememo" rows="3" cols="60" maxlength="500"></textarea>
        <a href="#" onclick="fn_replyReplySave()">저장</a>
        <a href="#" onclick="fn_replyReplyCancel()">취소</a>
    </form>
</div>

BoardRead.jsp

hideDiv 함수는 수정하거나 댓글 작성 화면을 가진 DIV 들을 숨기는 함수로

반복되어 사용하는 것이라 함수로 처리했다.

지정된 DIV 테그의 ID값으로 컨트롤(HTML Element)을 찾아서 화면에서 안보이게(display = "none") 처리한다.

그리고 부모를 document.body로 바꾸어 준다.

현재 있는 위치에는 기존 댓글이 있기 때문에 기존 댓글 수정시 문제를 일으킬 수 있어 다른 곳으로 옮겨 주는 것이다.


나머지 3개의 함수는 댓글 수정에서 사용한 것과 유사하게 작성되었는데

부모 댓글 번호를 가지는 것과

수정에서는 댓글 내용이 있는 위치에 댓글 내용을 지우고 수정 폼이 놓이게 되는데

댓글의 댓글에서는 댓글 내용 뒤에 추가(appendChild)해준다.


function hideDiv(id){
    var div = document.getElementById(id);
    div.style.display = "none";
    document.body.appendChild(div);
}

function fn_replyReply(reno){
    var form = document.form3;
    var reply = document.getElementById("reply"+reno);
    var replyDia = document.getElementById("replyDialog");
    replyDia.style.display = "";
   
    if (updateReno) {
        fn_replyUpdateCancel();
    }
   
    form.rememo.value = "";
    form.reparent.value=reno;
    reply.appendChild(replyDia);
    form.rewriter.focus();
}
function fn_replyReplyCancel(){
    hideDiv("replyDialog");
}

function fn_replyReplySave(){
    var form = document.form3;
   
    if (form.rewriter.value=="") {
        alert("작성자를 입력해주세요.");
        form.rewriter.focus();
        return;
    }
    if (form.rememo.value=="") {
        alert("글 내용을 입력해주세요.");
        form.rememo.focus();
        return;
    }
   
    form.action="board6ReplySave";
    form.submit();   
}

BoardRead.jsp

계층형 댓글의 수정은 일반 댓글과 같이 댓글 번호(reno)를 이용하기 때문에 다른 처리를 하지 않아도 된다.




'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

1. 무한 댓글 시작하기  (14) 2016.05.15
2. 무한 댓글 리스트  (11) 2016.05.15
3. 무한 댓글 쓰기 – control  (3) 2016.05.15
5. 무한 댓글 삭제  (1) 2016.05.15
6. 무한 댓글 삭제 II  (0) 2016.05.15

댓글 삭제는 기존과 동일하게 넘겨 받은 댓글 번호(reno)를 삭제 처리해 주면 된다.

    public boolean deleteBoard6Reply(String param) {
        Integer cnt = sqlSession.selectOne("selectBoard6ReplyChild", param);
       
        if ( cnt > 0) {
            return false;
        }
       
        sqlSession.update("updateBoard6ReplyOrder4Delete", param);
       
        sqlSession.delete("deleteBoard6Reply", param);
       
        return true;
    }

Board6Svc.java

다만, 삭제하기 전에 삭제하려는 대상의 댓글보다 순번이 큰 댓글들의 순서(reorder) 값은 1씩 빼서 순서 값을 맞춰 주는 것이 좋다.

하지 않아도 되지만 값이 들쑥날쑥 한 것이 보기에 좋지 않아서 처리한다.

    <update id="updateBoard6ReplyOrder4Delete" parameterType="gu.board6.BoardReplyVO">
        UPDATE TBL_BOARDREPLY TBR1
         INNER JOIN TBL_BOARDREPLY TBR2 ON TBR2.BRDNO=TBR1.BRDNO AND TBR1.REORDER >TBR2.REORDER AND TBR1.REDELETEFLAG='N'
           SET TBR1.REORDER = TBR1.REORDER - 1
         WHERE TBR2.RENO=#{reno}
    </update>

board6.xml


마지막으로 다음의 SQL 문은 자식 댓글 수를 계산하는 것으로,

자식 댓글이 있으면(cnt > 0) 삭제 하지 않도록 한다.

자식 댓글이 있는데 삭제 하면 화면 리스트에서 트리처럼 보이도록 한 것이 틀어지고

글 내용도 이해 할 수 없게 된다.

    <select id="selectBoard6ReplyChild" resultType="Integer" parameterType="String">
        SELECT COUNT(*)
          FROM TBL_BOARDREPLY
         WHERE REPARENT=#{reparent} AND RENO!=#{reparent} AND REDELETEFLAG='N'
    </select>

board6.xml


자식 댓글이 있으면 서비스에서 false 값을 반환 받게 되고

컨트롤에서는 BoardFailure.jsp 를 호출해서 오류 메시지를 출력해 준다.

    public String board6ReplyDelete(HttpServletRequest request, BoardReplyVO boardReplyInfo) {
       
        if (!boardSvc.deleteBoard6Reply(boardReplyInfo.getReno()) ) {
            return "board6/BoardFailure";
        }

        return "redirect:/board6Read?brdno=" + boardReplyInfo.getBrdno();
    }

Board6Ctr.java

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%>
<script>
        alert("다른 사람의 댓글이 있어서 삭제할 수 없습니다.");
        history.back(-1);
</script>

BoardFailure.jsp



'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

1. 무한 댓글 시작하기  (14) 2016.05.15
2. 무한 댓글 리스트  (11) 2016.05.15
3. 무한 댓글 쓰기 – control  (3) 2016.05.15
4. 무한 댓글 쓰기 – JSP  (0) 2016.05.15
6. 무한 댓글 삭제 II  (0) 2016.05.15

여기서는 다루지 않지만

부모를 지우면 모든 자식을 지우는 것도 방법이 될 수 있다.

익혀두면 도움이 되는 기법이라 적어본다.

부모를 지우면 모든 자식을 지우는 방법은

부모 필드(reparent)를 따라서 나를 부모로 하는 모든 자식을 찾으면 된다.

오라클에서는 connect BY로 쉽게 구현 할 수 있는데

mysql에서는 제공되지 않는 기능으로

대부분 만들어 사용하고 있다.


다음 블로그에서 제공하는 함수를 가져와 게시판 예제에 맞추어 수정했다.

이 함수의 원리는 간단하다.

지정된 댓글을 부모로 하는 자식이 없을 때까지 반복해서 찾는(SELECT) 것이다.

블로그 내용을 잘 읽어보길 바란다.

http://blog.naver.com/PostView.nhn?blogId=endstar7&logNo=100124603859&categoryNo=32&viewDate=&currentPage=1&listtype=0
https://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/

위 블로그의 내용과 다른 점은 테이블명(tbl_boardreply)과

게시물 번호(brdno) 값을 받아서 조건으로 사용한 것이다.

블로그 함수는 테이블의 전체 데이터를 대상으로 하고,

본 예제는 하나의 게시물에 대하여 처리하기 때문이다.


DELIMITER $$

CREATE FUNCTION  board_connect_by_parent(_brdno int) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
    DECLARE _id INT;
    DECLARE _parent INT;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

    SET _parent = @id;
    SET _id = -1;

    IF @id IS NULL THEN
        RETURN NULL;
    END IF;

    LOOP
        SELECT MIN(reno)
          INTO @id
          FROM tbl_boardreply
         WHERE reparent = _parent and brdno=_brdno and reno != reparent
           AND reno > _id;

        IF @id IS NOT NULL OR _parent = @start_with THEN
            SET @level = @level + 1;
            RETURN @id;
        END IF;

        SET @level := @level - 1;

        SELECT reno, reparent
          INTO _id, _parent
          FROM tbl_boardreply
         WHERE reno = _parent and brdno=_brdno and reno != reparent;
    END LOOP;

END

$$
DELIMITER ;


이렇게 작성된 함수를 다음과 같이 실행하면 된다.

다음 예는 모든 자식 리스트를 출력하는 것이고 SELECT 문 대신에 DELETE(UPDATE)문을 사용하면 된다.

SELECT *
 FROM TBL_BOARDREPLY
 WHERE RENO IN (
          SELECT ID
           FROM (
                     SELECT BOARD_CONNECT_BY_PARENT(18) AS ID, @LEVEL AS LEVEL
                      FROM (
                                SELECT @START_WITH := 6,
                                         @ID := @START_WITH,
                                         @LEVEL := 0
                     ) VARS, TBL_BOARDREPLY
                      WHERE @ID IS NOT NULL
          ) DS
)

지정된 부모의 모든 자식을 찾거나

지정된 자식의 모든 부모를 찾는 것은 많이 사용되는 기술이니

게시판 예제에는 적용하지 않았지만 잘 익혀두길 바란다.



'Java > 게시판 6: 무한댓글' 카테고리의 다른 글

1. 무한 댓글 시작하기  (14) 2016.05.15
2. 무한 댓글 리스트  (11) 2016.05.15
3. 무한 댓글 쓰기 – control  (3) 2016.05.15
4. 무한 댓글 쓰기 – JSP  (0) 2016.05.15
5. 무한 댓글 삭제  (1) 2016.05.15

+ Recent posts