• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Java EE 정리 37

18 Apr 2019

Reading time ~9 minutes

Java EE 정리 37 - 게시판, 댓글 만들기


게시판, 글보기

  • 게시판 - 동기
  • 게시글보기 - 동기
  • 댓글달기 - 비동기(AJAX)
  • 이전에 만든 diary 테이블 내용을 이용하여 게시판, 게시글 만들고 댓글기능을 추가할 것
  • 댓글을 위한 테이블 생성

01

CREATE TABLE diary_reply(
  num NUMBER,
  num_ref NUMBER,
  writer VARCHAR2(60),
  content VARCHAR2(600),
  input_date DATE DEFAULT SYSDATE,
  CONSTRAINT pk_diary_repply PRIMARY KEY (num),
  CONSTRAINT fk_num_ref FOREIGN KEY (num_ref)
      REFERENCES diary(num)
);
<!-- main_menu.jsp -->
...
<li><a href="diary/list.do">일정 보기</a></li>
...
<!-- mybatis_config.xml -->
...
    <mapper resource="kr/co/sist/mapper/diary_mapper.xml"/>
  </mappers>
</configuration>
package kr.co.sist.vo;
public class DiaryVO {
      private int begin, end, currPage;
      // getter, setter 생성
package kr.co.sist.domain;
public class DiaryDomain {
      private String subject, writer, e_year, e_month, e_date, w_date;
      private int num;
      // getter, setter 생성
<!-- diary_mapper.xml -->
...
<mapper namespace="kr.co.sist.diary">
      <select id="diaryTotalCnt" resultType="int">
            SELECT count(*) cnt
            FROM diary
      </select>
      <resultMap type="kr.co.sist.domain.DiaryDomain" id="diaryResult">
            <result column="num" property="num"/>
            <result column="subject" property="subject"/>
            <result column="writer" property="writer"/>
            <result column="e_year" property="e_year"/>
            <result column="e_month" property="e_month"/>
            <result column="e_date" property="e_date"/>
            <result column="w_date" property="w_date"/>
      </resultMap>
      <select id="diaryList" parameterType="kr.co.sist.vo.DiaryVO" resultMap="diaryResult">
            SELECT num, subject, writer, e_year, e_month, e_date, to_char(w_date, 'yyyy-mm-dd hh24:mi') w_date
            FROM (SELECT num, subject, writer, e_year, e_month,
                    e_date, w_date, ROW_NUMBER() OVER(ORDER BY w_date DESC) r_num
                  FROM diary)
            WHERE r_num BETWEEN #{ begin } AND #{ end }
      </select>
</mapper>


// MyBatisDAO
...
      public int selectTotalCnt() {
            SqlSession ss =  MyBatisDAO.getInstance().getSessionFactory().openSession();
            int cnt = ss.selectOne("diaryTotalCnt");
            ss.close();
            return cnt;
      }

      public List<DiaryDomain> selectList(DiaryVO dv) {
            SqlSession ss =  MyBatisDAO.getInstance().getSessionFactory().openSession();
            List<DiaryDomain> list = ss.selectList("diaryList",dv);
            ss.close();
            return list;
      }
}
/**
* 시작번호와 끝번호 사이의 게시물을 조회하기 위한 모든 작업을 정의
*/
public class DiaryService {
      private MyBatisDAO mb_dao;

      public DiaryService() {
            mb_dao = MyBatisDAO.getInstance();
      }
      /**
       * 총 게시물의 수 얻기
       *
       * @return
       */
      public int totalCnt() {
            int cnt = 0;
            cnt = mb_dao.selectTotalCnt();
            return cnt;
      }
      /**
       * 한 화면에 보여줄 게시물의 수
       * @return
       */
      public int pageScale() {
            int pageScale = 10;
            return pageScale;
      }
      /**
       * 모든 게시물을 보여주기 위한 페이지 수
       * @param totalCnt
       * @return
       */
      public int totalPage(int totalCnt) {
            int totalPage = totalCnt / pageScale();
            if (totalCnt % pageScale() != 0) {
                  totalPage++;
            }
            return totalPage;
      }
      /**
       * 선택한 인덱스 리스트에서 조회할 시작 번호
       * @param currentPage
       * @return
       */
      public int startNum(int currentPage) {
            int startNum = 1;
            startNum = currentPage * pageScale() - pageScale() + 1;
            return startNum;
      }
      /**
       * 선택한 인덱스 리스트에서 조회할 끝 번호
       * @param startNum
       * @return
       */
      public int endNum(int startNum) {
            int endNum = startNum + pageScale() - 1;
            return endNum;
      }

       public List<DiaryDomain> searchDiaryList(DiaryVO dv) {
            List<DiaryDomain> list = null;
            list = mb_dao.selectList(dv);

            DiaryDomain temp = null;
            String subject = "";
            // 글의 제목은 24자 까지만 보여준다.
            for(int i=0; i<list.size(); i++) {
                  temp = list.get(i);
                  subject = temp.getSubject();
                  
                  if( subject.length() > 25 ) {
                        subject = subject.substring(0, 24)+"...";
                        temp.setSubject(subject);
                  }
            }

            return list;
      }

      /**
       * 인덱스 리스트 [<<]...[1][2][3]...[>>]
       * @param current_page
       * @param total_page
       * @param list_url
       * @return
       */
      // 현재 게시판의 페이지 인덱스 설정
      public String indexList(int current_page, int total_page, String  list_url) {
            int pagenumber; // 화면에 보여질 페이지 인덱스 수
            int startpage; // 화면에 보여질 시작페이지 번호
            int endpage; // 화면에 보여질 마지막페이지 번호
            int curpage; // 이동하고자 하는 페이지 번호
            String strList = ""; // 리턴될 페이지 인덱스 리스트
            pagenumber = 10; // 한 화면의 페이지 인덱스 수
            // 시작 페이지번호 구하기
            startpage = ((current_page - 1) / pagenumber) * pagenumber +  1;
            // 마지막 페이지번호 구하기
            endpage = (((startpage - 1) + pagenumber) / pagenumber) *  pagenumber;
            // 총 페이지 수가 계산된 마지막페이지 번호보다 작을경우
            // 총 페이지 수가 마지막페이지 번호가 됨
            if (total_page <= endpage) {
                  endpage = total_page;
            } // end if

            // 첫번째 페이지 인덱스 화면이 아닌경우
            if (current_page > pagenumber) {
                  curpage = startpage - 1; // 시작페이지 번호보다 1 적은  페이지로 이동
                  strList = strList + "[ <a href=" + list_url +  "?currPage=" + curpage + ">&lt;&lt;</a> ]";
            } else {
                  strList = strList + "[<<]";
            }
            strList = strList + " ... ";
            // 시작페이지 번호부터 마지막페이지 번호까지 화면에 표시
            curpage = startpage;
            while (curpage <= endpage) {
                  if (curpage == current_page) {
                        strList = strList + "[ " + current_page + " ]";
                  } else {
                        strList = strList + "[ <a href=" + list_url +  "?currPage=" + curpage + ">" + curpage + " </a> ]";
                  } // end else
                  curpage++;
            } // end while
            strList = strList + " ... ";
            // 뒤에 페이지가 더 있는경우
            if (total_page > endpage) {
                  curpage = endpage + 1;
                  strList = strList + "[ <a href=" + list_url +  "?currPage=" + curpage + ">&gt;&gt;</a> ]";
            } else {
                  strList = strList + "[>>]";
            } // end else
            return strList;
      }// indexList
}
// BbsController
...
import static org.springframework.web.bind.annotation.RequestMethod.GET;
...
@Controller
public class BbsController {
      @RequestMapping(value="/diary/list.do", method=GET)
      public String diaryList(DiaryVO dv, Model model) {
            
            DiaryService ds = new DiaryService();
            
            // 게시판에 필요한 정보를 service를 사용해서 얻음
            int totalCnt = ds.totalCnt(); // 총 게시물의 수
            int pageScale = ds.pageScale(); // 한 화면에 보여줄 게시물의  수
            int totalPage = ds.totalPage(totalCnt); // 전체 게시물을  보여주기 위한 총 페이지 수
            // 최초 호출 시, web parameter에 값이 없을 때
            if (dv.getCurrPage() == 0) {
                  dv.setCurrPage(1); // 시작페이지를 1로 설정
            }
            
            int startNum = ds.startNum(dv.getCurrPage()); // 시작번호
            int endNum = ds.endNum(startNum);
            
            dv.setBegin(startNum);
            dv.setEnd(endNum);
            
            // 리스트 목록 조회
            List<DiaryDomain> diaryList = ds.searchDiaryList(dv);
            String indexList = ds.indexList(dv.getCurrPage(), totalPage,  "list.do");
            
            model.addAttribute("diaryList",diaryList);
            model.addAttribute("indexList",indexList);
            
            model.addAttribute("pageScale",pageScale);
            model.addAttribute("currentPage",dv.getCurrPage());
            model.addAttribute("totalCnt",totalCnt);

            return "diary/list";
      }
}
<!-- diary/list.jsp -->
...
<!-- css도 diary쓰던거 그대로 복붙해서 사용 -->
...
<div>
      <div id="diary">
            <div id="diaryHeader">
            이벤트 목록
            </div>
            <div id="diaryContent">
                  <table id="listTab">
                        <tr>
                              <th id="numTitle">번호</th>
                              <th id="subjectTitle">이벤트 제목</th>
                              <th id="writerTitle">작성자</th>
                              <th id="eDateTitle">이벤트일자</th>
                              <th id="wDateTitle">작성일자</th>
                        </tr>
                        <c:if test="${ empty diaryList }">
                        <tr>
                              <td colspan="5">
                                    이벤트가 존재하지 않습니다.<br/>
                                    <a href="diary.jsp">이벤트 작성</a>
                              </td>
                        </tr>
                        </c:if>
                        <c:forEach var="data" items="${ diaryList }">
                        <c:set var="i" value="${ i+1 }"/>
                        <tr>
                              <td><c:out value="${  (totalCnt-(currentPage-1)*pageScale-i)+1 }"/></td>
                              <td><a href="bbs_read.do?num=${ data.num }"><c:out value="${ data.subject }"/></a></td>
                              <td><c:out value="${ data.writer }"/></td>
                              <td><c:out value="${ data.e_year }-${  data.e_month }-${ data.e_date }"/></td>
                              <td><c:out value="${ data.w_date }"/></td>
                              <c:set var="bbsIdx" value="${ bbsIdx-1  }"/>
                        </tr>
                        </c:forEach>
                  </table>
            </div>
            <div id="diaryIndexList">
            <c:out value="${ indexList }" escapeXml="false"/>
            </div>
      </div>
</div>

02

  • 읽기기능 추가
<!-- diary_mapper.xml -->
...
      <select id="diaryDetail" parameterType="int"  resultType="kr.co.sist.domain.DiaryDetailDomain">
            SELECT WRITER, SUBJECT, CONTENT, TO_CHAR(W_DATE, 'YYYY-MM-DD  DY HH24:MI') W_DATE, IP
            FROM DIARY
            WHERE NUM = #{ num }
      </select>
</mapper>
package kr.co.sist.domain;
public class DiaryDetailDomain {
      private String writer, subject, content, w_date, ip;
      // getter, setter 생성
...
// MyBatisDAO
...
      public DiaryDetailDomain selectDiaryDetail(int num) {
            DiaryDetailDomain ddd = null;
            
            SqlSession ss =  MyBatisDAO.getInstance().getSessionFactory().openSession();
            ddd = ss.selectOne("diaryDetail",num);
            ss.close();
            
            return ddd;
      }
...
// DiaryService
...
      public DiaryDetailDomain serachBbs(int num) {
            DiaryDetailDomain ddd = mb_dao.selectDiaryDetail(num);
            return ddd;
      }
...
// BbsController
...
      @RequestMapping(value="/diary/bbs_read.do", method=GET)
      public String bbsRead(int num, Model model) {
            
            DiaryService ds = new DiaryService();
            DiaryDetailDomain ddd = ds.serachBbs(num);
            
            model.addAttribute("searchData", ddd);
            
            return "diary/bbs_read";
      }
}
<!-- diary/bbs_read.jsp -->
...
<div id="readFrm">
      <table id="readTab">
            <tr>
                  <th colspan="2" style="text-align: center;">
                        <span style="font-size: 20px;">이벤트 읽기</span>
                  </th>
            </tr>
            <tr>
                  <td style="width:80px;">제목</td>
            <td style="width:400px">
                  <div id="subject" ><strong>${ searchData.subject }</strong></div>
            </td>
            </tr>
            <tr>
                  <td style="width:80px;">내용</td>
                  <td style="width:400px">
                        ${ searchData.content }
                  </td>
            </tr>
            <tr>
                  <td style="width:80px;">작성자</td>
                  <td style="width:400px">
                        <div id="writer"><strong>${ searchData.writer }</strong></div>
                  </td>
            </tr>
            <tr>
                  <td style="width:80px;">작성일</td>
                  <td style="width:400px">
                        <div id="wDate"><strong>${ searchData.w_date }</strong></div>
                  </td>
            </tr>
            <tr>
                  <td style="width:80px;">작성IP</td>
                  <td style="width:400px">
                        <div id="ip"><strong>${ searchData.ip }</strong></div>
                  </td>
            </tr>
            <tr>
            <td colspan="2" align="center">
                  <a href="#void" onclick="history.back();">리스트</a>
            </td>
            </tr>
      </table>
</div>

03

댓글 기능 추가

  • AJAX를 이용해서 추가 후 새로운 댓글을 조회해오는게 아니라 요청으로 DB에 추가하고, 화면 이동 없이 입력된 값을 화면에 그릴 것
<!-- diary_mapper.xml -->
...
      <select id="diaryReply" resultType="kr.co.sist.domain.DiaryReplyDomain" parameterType="int">
            SELECT num, writer, content, TO_CHAR(input_date, 'yyyy-mm-dd  hh24:mi') input_date
            FROM diary_reply
            WHERE num_ref = #{ num_ref }
      </select>
</mapper>
package kr.co.sist.domain;
public class DiaryReplyDomain {
      private String writer, content, input_date;
      private int num;
      // getter, setter
      ...
  • 댓글번호 사용할 시퀀스를 생성
CREATE SEQUENCE seq_reply
    INCREMENT BY 1
    START WITH 1
    MAXVALUE 99999999;
  • 댓글을 가져오기전에 가데이터를 넣음
insert into diary_reply(num, num_ref, writer, content)
values(seq_reply.nextval, 64, '노진경', '안돼!!');

insert into diary_reply(num, num_ref, writer, content)
values(seq_reply.nextval, 64, '이재찬', '정의의 이름으로 용서치 않겠다!');

insert into diary_reply(num, num_ref, writer, content)
values(seq_reply.nextval, 64, '오영근', '코딩테스트는 어떻게 준비하나..');
  • 원글의 내용이 보여졌을 때 모든 댓글을 가져와서 보여줄 것
// MyBatisDAO
...
      public List<DiaryReplyDomain> selectReply(int num) {
            SqlSession ss =  MyBatisDAO.getInstance().getSessionFactory().openSession();
            List<DiaryReplyDomain> list =  ss.selectList("diaryReply",num);
            ss.close();
            return list;
      }
...
// DiarySearch
...
      public List<DiaryReplyDomain> searchReplyList(int num) {
            List<DiaryReplyDomain> list = null;
            list = mb_dao.selectReply(num);
            return list;
      }
...
// BbsController
...
      @RequestMapping(value="/diary/bbs_read.do", method=GET)
      public String bbsRead(int num, Model model) {
            
            DiaryService ds = new DiaryService();
            DiaryDetailDomain ddd = ds.serachBbs(num); // 원글의 내용을  조회
            // 원글의 댓글들을 조회
            List<DiaryReplyDomain> replyList = ds.searchReplyList(num);
            
            model.addAttribute("searchData", ddd);
            model.addAttribute("replyList", replyList);
            
            return "diary/bbs_read";
      }
}
<!-- bbs_read.jsp -->
...
      <table>
            <tr>
                  <td>댓글</td>
                  <td>
                        <input type="text" name="reply" class="inputBox"  placeholder="내용을 입력하세요" style="width:400px;"/>
                  </td>
                  <td>
                        <input type="text" name="writer"  class="inputBox" placeholder="작성자" style="width:100px;"/>
                        <input type="button" value="쓰기" class="btn"/>
                  </td>
            </tr>
      </table>
      <div id="reply">
            <c:forEach var="reply" items="${ replyList }">
            <div style="margin-top:10px; border:1px solid #dfdfdf;  width:600px;">
                  <c:out value="${ reply.content }"/><br/>
                  <c:out value="${ reply.writer }"/> / <c:out value="${  reply.input_date }"/>
            </div>
            </c:forEach>
      </div>
</div>

04

  • 닫혀있다가 “열기”를 클릭하면 댓글 내용이 생기게 처리
#reply{ display: none; }
<!-- bbs_read.jsp -->
...
<script type="text/javascript">
      $(function() {
            $("#replyView").click(function(){
                        var txt = $("#replyView").text();
                        
                        if(txt == "열기") {
                              txt="접기";
                        } else {
                              txt="열기";
                        }
                        
                        $("#replyView").text(txt);
                        $("#reply").toggle();
            });
      });
</script>
...
      <a href="#" id="replyView">열기</a>
      <div id="reply">
            <c:forEach var="reply" items="${ replyList }">
            <div style="margin-top:10px; border:1px solid #dfdfdf;  width:600px;">
                  <c:out value="${ reply.content }"/><br/>
                  <c:out value="${ reply.writer }"/> / <c:out value="${  reply.input_date }"/>
            </div>
            </c:forEach>
      </div>
</div>

05

  • 새로운 댓글 쓰기
<!-- bbs_read.jsp -->
...
<script type="text/javascript">
      $(function() {
            $("#replyView").click(function(){
                  var txt = $("#replyView").text();
                  
                  if(txt == "열기") {
                        txt="접기";
                  } else {
                        txt="열기";
                  }
                  
                  $("#replyView").text(txt);
                  $("#reply").toggle();
            });
            
            $("#replyBtn").click(function() {
                  var writer = $("[name='writer']").val();
                  if(writer == "") {
                        alert("작성자는 필수 입력항목입니다.");
                        $("[name='writer']").focus();
                        return;
                  }
                  var reply = $("[name='reply']").val();
                  if(reply == "") {
                        alert("댓글내용은 필수 입력항목입니다.");
                        $("[name='reply']").focus();
                        return;
                  }
                  
                  var queryString =  "num_ref="+$("[name='num_ref']").val()
                              +"&content="+reply+"&writer="+writer;
                  
                  $.ajax({
                        url:"add_reply.do",
                        data:queryString,
                        type:"get",
                        dataType:"json",
                        error:function(xhr) {
                              alert("댓글작성 실패");
                              console.log(xhr.status+" /  "+xhr.statusText);
                        },
                        success:function(json) {
                              if (json.result) {
                                    // <div>의 자식 노드로 작성한 값을  추가(append())
                                    // 자식노드 전에 추가(prepend())
                                    var date = new Date();
                                    var output = "<div  style='margin-top:10px; border:1px solid #dfdfdf; width:600px;'>"
                                                +reply+"<br/>"+writer+"("+date.getFullYear()+"-"+(date.getMonth()+1)+"-"
                                                +date.getDate()+"</div>";
                                    $("#reply").prepend(output);
                                                
                                    alert("댓글이 정상적으로  등록되었습니다.");
                                    
                                    $("[name='writer']").val(""); // 초기화
                                    $("[name='reply']").val("");
                              }
                        }
                  });
            });
      });
</script>
...
      <table>
            <tr>
                  <td>댓글</td>
                  <td>
                        <input type="text" name="reply" class="inputBox"  placeholder="내용을 입력하세요" style="width:400px;"/>
                  </td>
                  <td>
                        <input type="text" name="writer"  class="inputBox" placeholder="작성자" style="width:100px;"/>
                        <input type="button" value="쓰기" class="btn"  id="replyBtn"/>
                        <input type="hidden" name="num_ref" value="${  param.num }"/>
                  </td>
            </tr>
      </table>
      <a href="#" id="replyView">열기</a>
      <div id="reply">
            <c:forEach var="reply" items="${ replyList }">
            <div style="margin-top:10px; border:1px solid #dfdfdf;  width:600px;">
                  <c:out value="${ reply.content }"/><br/>
                  <c:out value="${ reply.writer }"/> / <c:out value="${  reply.input_date }"/>
            </div>
            </c:forEach>
      </div>
</div>
...
package kr.co.sist.vo;
public class ReplyVO {
      
      private String writer, content;
      private int num_ref;
      // getter, setter
...
<!-- diary_mapper.xml -->
...
      <insert id="addReply" parameterType="kr.co.sist.vo.ReplyVO">
            INSERT INTO diary_reply(num, num_ref, writer, content)
            VALUES(seq_reply.nextval, #{ num_ref }, #{ writer }, #{  content })
      </insert>
</mapper>
// MyBatisDAO
...
      public int insertReply(ReplyVO r_vo) {
            SqlSession ss =  MyBatisDAO.getInstance().getSessionFactory().openSession();
            int cnt = ss.insert("addReply",r_vo);
            if (cnt == 1) {
                  ss.commit();
            }
            ss.close();
            
            return cnt;
      }
}
// DiaryService
...
      public JSONObject addReply(ReplyVO r_vo) {
            JSONObject json = new JSONObject();
            int cnt = mb_dao.insertReply(r_vo);
            json.put("result", cnt == 1);
            
            return json;
      }
...
// BbsController
...
      @ResponseBody
      @RequestMapping(value="/diary/add_reply.do", method=GET)
      public String writeReply(ReplyVO r_vo) {
            JSONObject json = null;
            
            DiaryService ds = new DiaryService();
            json = ds.addReply(r_vo);
            
            return json.toJSONString();
      }
}

06



Java EESpring