Java EE 정리 37
18 Apr 2019
Reading time ~9 minutes
Java EE 정리 37 - 게시판, 댓글 만들기
게시판, 글보기
- 게시판 - 동기
- 게시글보기 - 동기
- 댓글달기 - 비동기(AJAX)
- 이전에 만든 diary 테이블 내용을 이용하여 게시판, 게시글 만들고 댓글기능을 추가할 것
- 댓글을 위한 테이블 생성
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 + "><<</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 + ">>></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>
- 읽기기능 추가
<!-- 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>
댓글 기능 추가
- 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>
- 닫혀있다가 “열기”를 클릭하면 댓글 내용이 생기게 처리
#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>
- 새로운 댓글 쓰기
<!-- 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();
}
}