728x90
Spring - 게시판 만들기 연습 (댓글 입력 및 목록, 댓글 갯수 보여주기)
1. 실행화면
01) 댓글 입력 및 목록
댓글 입력 및 목록 출력은 RestController를 이용하여 구현
02) 댓글 갯수 보여주기
해당게시물에 댓글이 있으면 갯수 출력
2. 소스코드
00) 라이브러리 추가(JSON)
pom.xml
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.6</version> </dependency> | cs |
01) Controller(흐름제어)
ReplyController - 댓글 입력 및 목록 맵핑
package com.example.spring02.controller.board; import java.util.List; import javax.inject.Inject; import javax.servlet.http.HttpSession; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import com.example.spring02.model.board.dto.ReplyVO; import com.example.spring02.service.board.ReplyService; // REST : Representational State Transfer // 하나의 URI가 하나의 고유한 리소스를 대표하도록 설계된 개념 // http://localhost/spring02/list?bno=1 ==> url+파라미터 // http://localhost/spring02/list/1 ==> url // RestController은 스프링 4.0부터 지원 // @Controller, @RestController 차이점은 메서드가 종료되면 화면전환의 유무 //@Controller @RestController @RequestMapping("/reply/*") public class ReplyController { @Inject ReplyService replyService; // 댓글 입력 @RequestMapping("insert.do") public void insert(@ModelAttribute ReplyVO vo, HttpSession session){ String userId = (String) session.getAttribute("userId"); vo.setReplyer(userId); replyService.create(vo); } // 댓글 목록(@Controller방식 : veiw(화면)를 리턴) @RequestMapping("list.do") public ModelAndView list(@RequestParam int bno, ModelAndView mav){ List<ReplyVO> list = replyService.list(bno); // 뷰이름 지정 mav.setViewName("board/replyList"); // 뷰에 전달할 데이터 지정 mav.addObject("list", list); // replyList.jsp로 포워딩 return mav; } // 댓글 목록(@RestController Json방식으로 처리 : 데이터를 리턴) @RequestMapping("listJson.do") @ResponseBody // 리턴데이터를 json으로 변환(생략가능) public List<ReplyVO> listJson(@RequestParam int bno){ List<ReplyVO> list = replyService.list(bno); return list; } } | cs |
02) Service(비지니스 로직, DB연동 이외의 작업처리)
ReplyService(인터페이스)
package com.example.spring02.service.board; import java.util.List; import com.example.spring02.model.board.dto.ReplyVO; public interface ReplyService { // 댓글 목록 public List<ReplyVO> list(Integer bno); // 댓글 입력 public void create(ReplyVO vo); // 댓글 수정 public void update(ReplyVO vo); // 댓글 삭제 public void delete(Integer rno); } | cs |
ReplyServiceImpl(인터페이스 구현 클래스)
package com.example.spring02.service.board; import java.util.List; import javax.inject.Inject; import org.springframework.stereotype.Service; import com.example.spring02.model.board.dao.ReplyDAO; import com.example.spring02.model.board.dto.ReplyVO; @Service public class ReplyServiceImpl implements ReplyService { @Inject ReplyDAO replyDao; // 댓글 목록 @Override public List<ReplyVO> list(Integer bno) { return replyDao.list(bno); } // 댓글 작성 @Override public void create(ReplyVO vo) { replyDao.create(vo); } // 댓글 수정 @Override public void update(ReplyVO vo) { // TODO Auto-generated method stub } // 댓글 삭제 @Override public void delete(Integer rno) { // TODO Auto-generated method stub } } | cs |
03) Model(비지니스 로직, DB연동 작업처리)
ReplyVO
package com.example.spring02.model.board.dto; import java.util.Date; public class ReplyVO { private Integer rno; // 댓글 번호 private Integer bno; // 게시글 번호 private String replytext; // 댓글 내용 private String replyer; // 댓글 작성자 private String userName; // 댓글 작성자의 이름(회원의 이름) private Date regdate; // 댓글 작성일자 private Date updatedate; // 댓글 수정일자 // Getter/Setter public Integer getRno() { return rno; } public void setRno(Integer rno) { this.rno = rno; } public Integer getBno() { return bno; } public void setBno(Integer bno) { this.bno = bno; } public String getReplytext() { return replytext; } public void setReplytext(String replytext) { this.replytext = replytext; } public String getReplyer() { return replyer; } public void setReplyer(String replyer) { this.replyer = replyer; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Date getRegdate() { return regdate; } public void setRegdate(Date regdate) { this.regdate = regdate; } public Date getUpdatedate() { return updatedate; } public void setUpdatedate(Date updatedate) { this.updatedate = updatedate; } // toString() @Override public String toString() { return "ReplyVO [rno=" + rno + ", bno=" + bno + ", replytext=" + replytext + ", replyer=" + replyer + ", userName=" + userName + ", regdate=" + regdate + ", updatedate=" + updatedate + "]"; } } | cs |
BoardVo - 게시글 목록에 댓글의 수를 출력하기 위해 추가
package com.example.spring02.model.board.dto; import java.util.Date; public class BoardVO { private int bno; // 게시글 번호 private String title; // 게시글 제목 private String content; // 게시글 내용 private String writer; // 게시글 작성자 private String userName; // 게시글 작성자의 이름(회원이름) private Date regdate; // 게시글 작성일자 util.Date private int viewcnt; // 게시글 조회수 private int recnt; // **게시글 댓글의 수 추가 // Getter/Setter public int getBno() { return bno; } public void setBno(int bno) { this.bno = bno; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getWriter() { return writer; } public void setWriter(String writer) { this.writer = writer; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Date getRegdate() { return regdate; } public void setRegdate(Date regdate) { this.regdate = regdate; } public int getViewcnt() { return viewcnt; } public void setViewcnt(int viewcnt) { this.viewcnt = viewcnt; } public int getRecnt() { return recnt; } public void setRecnt(int recnt) { this.recnt = recnt; } // toString() @Override public String toString() { return "BoardVO [bno=" + bno + ", title=" + title + ", content=" + content + ", writer=" + writer + ", userName=" + userName + ", regdate=" + regdate + ", viewcnt=" + viewcnt + ", recnt=" + recnt + "]"; } } | cs |
ReplyDAO(인터페이스)
package com.example.spring02.model.board.dao; import java.util.List; import com.example.spring02.model.board.dto.ReplyVO; public interface ReplyDAO { // 댓글 목록 public List<ReplyVO> list(Integer bno); // 댓글 입력 public void create(ReplyVO vo); // 댓글 수정 public void update(ReplyVO vo); // 댓글 삭제 public void delete(Integer rno); } | cs |
ReplyDAOImpl(인터페이스 구현 클래스)
package com.example.spring02.model.board.dao; import java.util.List; import javax.inject.Inject; import org.apache.ibatis.session.SqlSession; import org.springframework.stereotype.Repository; import com.example.spring02.model.board.dto.ReplyVO; @Repository public class ReplyDAOImpl implements ReplyDAO { @Inject SqlSession sqlSession; // 댓글 목록 @Override public List<ReplyVO> list(Integer bno) { return sqlSession.selectList("reply.listReply", bno); } // 댓글 작성 @Override public void create(ReplyVO vo) { sqlSession.insert("reply.insertReply", vo); } // 댓글 수정 @Override public void update(ReplyVO vo) { // TODO Auto-generated method stub } // 댓글 삭제 @Override public void delete(Integer rno) { // TODO Auto-generated method stub } } | cs |
replyMapper.xml - 댓글 입력 insert쿼리, 댓글 목록 select 쿼리 추가
<?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와 중복되지 않도록 네임스페이스 기재 --> <mapper namespace="reply"> <!-- 댓글 입력 --> <insert id="insertReply"> INSERT INTO tbl_reply (rno, bno, replytext, replyer) VALUES (reply_seq.NEXTVAL, #{bno}, #{replytext}, #{replyer}) </insert> <!-- 댓글 목록 --> <select id="listReply" resultType="com.example.spring02.model.board.dto.ReplyVO"> SELECT rno, bno, replytext, replyer, user_name AS userName, r.regdate, r.updatedate FROM tbl_reply r, tbl_member m WHERE r.replyer = m.user_id AND bno=#{bno} ORDER BY rno </select> </mapper> | cs |
boardMapper.xml - select 쿼리에 댓글의 숫자를 구하는 서브쿼리 추가
<?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"> <!-- 01. 게시글 전체 목록 조회 및 검색조회까지 --> <select id="listAll" resultType="com.example.spring02.model.board.dto.BoardVO"> <include refid="pagingHeader"></include> SELECT ROWNUM, bno, title, content, b.regdate, viewcnt, user_name AS userName, <!-- ** 댓글숫자표시 --> (SELECT COUNT(*) FROM tbl_reply WHERE bno=b.bno) AS recnt FROM tbl_board b, tbl_member m <include refid="search"></include> ORDER BY bno DESC, b.regdate DESC <include refid="pagingFooter"></include> </select> <!-- sql code 조각 --> <!-- 반복되는 sql의 일부를 sql태그를 이용하여 따로 빼둘수 있다. --> <!-- 검색 조건 sql --> <sql id="search"> <choose> <!-- 검색옵션이 전체 검색일 경우 --> <when test="searchOption == 'all'"> WHERE b.writer = m.user_id AND (user_name like '%'||#{keyword}||'%' OR content like '%'||#{keyword}||'%' OR title like '%'||#{keyword}||'%') </when> <!-- 전체 검색이 아닐 경우 --> <otherwise> WHERE b.writer = m.user_id AND ${searchOption} like '%'||#{keyword}||'%' </otherwise> </choose> </sql> <!-- 페이징 sql --> <sql id="pagingHeader"> SELECT * FROM ( SELECT ROWNUM AS rn, A.* FROM ( </sql> <sql id="pagingFooter"> ) A ) WHERE rn BETWEEN #{start} AND #{end} </sql> </mapper> | cs |
04) View(화면처리)
view.jsp(게시글 상세보기 화면)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>게시글 작성</title> <%@ include file="../include/header.jsp" %> <script> $(document).ready(function(){ //listReply(); // **댓글 목록 불러오기 listReply2(); // ** json 리턴방식 // ** 댓글 쓰기 버튼 클릭 이벤트 (ajax로 처리) $("#btnReply").click(function(){ var replytext=$("#replytext").val(); var bno="${dto.bno}" var param="replytext="+replytext+"&bno="+bno; $.ajax({ type: "post", url: "${path}/reply/insert.do", data: param, success: function(){ alert("댓글이 등록되었습니다."); listReply2(); } }); }); // 게시글 목록 버튼 클릭 이벤트 : 버튼 클릭시 상세보기화면에 있던 페이지, 검색옵션, 키워드 값을 가지로 목록으로 이동 $("#btnList").click(function(){ location.href="${path}/board/list.do?curPage=${curPage}&searchOption=${searchOption}&keyword=${keyword}"; }); // 게시글 삭제 버튼 클릭이벤트 $("#btnDelete").click(function(){ if(confirm("삭제하시겠습니까?")){ document.form1.action = "${path}/board/delete.do"; document.form1.submit(); } }); // 게시글 수정 버튼 클릭이벤트 $("#btnUpdete").click(function(){ //var title = document.form1.title.value; ==> name속성으로 처리할 경우 //var content = document.form1.content.value; //var writer = document.form1.writer.value; var title = $("#title").val(); var content = $("#content").val(); //var writer = $("#writer").val(); if(title == ""){ alert("제목을 입력하세요"); document.form1.title.focus(); return; } if(content == ""){ alert("내용을 입력하세요"); document.form1.content.focus(); return; } /* if(writer == ""){ alert("이름을 입력하세요"); document.form1.writer.focus(); return; } */ document.form1.action="${path}/board/update.do" // 폼에 입력한 데이터를 서버로 전송 document.form1.submit(); }); }); // Controller방식 // **댓글 목록1 function listReply(){ $.ajax({ type: "get", url: "${path}/reply/list.do?bno=${dto.bno}", success: function(result){ // responseText가 result에 저장됨. $("#listReply").html(result); } }); } // RestController방식 (Json) // **댓글 목록2 (json) function listReply2(){ $.ajax({ type: "get", //contentType: "application/json", ==> 생략가능(RestController이기때문에 가능) url: "${path}/reply/listJson.do?bno=${dto.bno}", success: function(result){ console.log(result); var output = "<table>"; for(var i in result){ output += "<tr>"; output += "<td>"+result[i].userName; output += "("+changeDate(result[i].regdate)+")<br>"; output += result[i].replytext+"</td>"; output += "<tr>"; } output += "</table>"; $("#listReply").html(output); } }); } // **날짜 변환 함수 작성 function changeDate(date){ date = new Date(parseInt(date)); year = date.getFullYear(); month = date.getMonth(); day = date.getDate(); hour = date.getHours(); minute = date.getMinutes(); second = date.getSeconds(); strDate = year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second; return strDate; } </script> </head> <body> <%@ include file="../include/menu.jsp" %> <h2>게시글 보기</h2> <form name="form1" method="post"> <div> <!-- 원하는 날짜형식으로 출력하기 위해 fmt태그 사용 --> 작성일자 : <fmt:formatDate value="${dto.regdate}" pattern="yyyy-MM-dd a HH:mm:ss"/> <!-- 날짜 형식 => yyyy 4자리연도, MM 월, dd 일, a 오전/오후, HH 24시간제, hh 12시간제, mm 분, ss 초 --> </div> <div> 조회수 : ${dto.viewcnt} </div> <div> 제목 <input name="title" id="title" size="80" value="${dto.title}" placeholder="제목을 입력해주세요"> </div> <div> 내용 <textarea name="content" id="content" rows="4" cols="80" placeholder="내용을 입력해주세요">${dto.content}</textarea> </div> <div> 이름 <%-- <input name="writer" id="writer" value="${dto.writer}" placeholder="이름을 입력해주세요"> --%> ${dto.userName} </div> <div style="width:650px; text-align: center;"> <!-- 게시물번호를 hidden으로 처리 --> <input type="hidden" name="bno" value="${dto.bno}"> <!-- 본인이 쓴 게시물만 수정, 삭제가 가능하도록 처리 --> <c:if test="${sessionScope.userId == dto.writer}"> <button type="button" id="btnUpdete">수정</button> <button type="button" id="btnDelete">삭제</button> </c:if> <!-- 상세보기 화면에서 게시글 목록화면으로 이동 --> <button type="button" id="btnList">목록</button> </div> </form> <div style="width:650px; text-align: center;"> <br> <!-- **로그인 한 회원에게만 댓글 작성폼이 보이게 처리 --> <c:if test="${sessionScope.userId != null}"> <textarea rows="5" cols="80" id="replytext" placeholder="댓글을 작성해주세요"></textarea> <br> <button type="button" id="btnReply">댓글 작성</button> </c:if> </div> <!-- **댓글 목록 출력할 위치 --> <div id="listReply"></div> </body> </html> | cs |
replyList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <%@ include file="../include/header.jsp" %> </head> <body> <table style="width:700px"> <c:forEach var="row" items="${list}"> <tr> <td> ${row.userName}(<fmt:formatDate value="${row.regdate}" pattern="yyyy-MM-dd HH:mm:ss"/>) <br> ${row.replytext} </td> </tr> </c:forEach> </table> </body> </html> | cs |
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>게시글 목록</title> <%@ include file="../include/header.jsp" %> <script> $(document).ready(function(){ $("#btnWrite").click(function(){ // 페이지 주소 변경(이동) location.href = "${path}/board/write.do"; }); }); // 원하는 페이지로 이동시 검색조건, 키워드 값을 유지하기 위해 function list(page){ location.href="${path}/board/list.do?curPage="+page+"&searchOption-${map.searchOption}"+"&keyword=${map.keyword}"; } </script> </head> <body> <%@ include file="../include/menu.jsp" %> <h2>게시글 목록</h2> <form name="form1" method="post" action="${path}/board/list.do"> <select name="searchOption"> <!-- 검색조건을 검색처리후 결과화면에 보여주기위해 c:out 출력태그 사용, 삼항연산자 --> <option value="all" <c:out value="${map.searchOption == 'all'?'selected':''}"/> >제목+이름+제목</option> <option value="user_name" <c:out value="${map.searchOption == 'user_name'?'selected':''}"/> >이름</option> <option value="content" <c:out value="${map.searchOption == 'content'?'selected':''}"/> >내용</option> <option value="title" <c:out value="${map.searchOption == 'title'?'selected':''}"/> >제목</option> </select> <input name="keyword" value="${map.keyword}"> <input type="submit" value="조회"> <!-- 로그인한 사용자만 글쓰기 버튼을 활성화 --> <c:if test="${sessionScope.userId != null}"> <button type="button" id="btnWrite">글쓰기</button> </c:if> </form> <!-- 레코드의 갯수를 출력 --> ${map.count}개의 게시물이 있습니다. <table border="1" width="600px"> <tr> <th>번호</th> <th>제목</th> <th>이름</th> <th>작성일</th> <th>조회수</th> </tr> <c:forEach var="row" items="${map.list}"> <tr> <td>${row.bno}</td> <!-- 게시글 상세보기 페이지로 이동시 게시글 목록페이지에 있는 검색조건, 키워드, 현재페이지 값을 유지하기 위해 --> <td> <a href="${path}/board/view.do?bno=${row.bno}&curPage=${map.boardPager.curPage}&searchOption=${map.searchOption}&keyword=${map.keyword}">${row.title} <!-- ** 댓글이 있으면 게시글 이름 옆에 출력하기 --> <c:if test="${row.recnt > 0}"> <span style="color: red;">(${row.recnt}) </span> </c:if> </a> </td> <td>${row.userName}</td> <td> <!-- 원하는 날짜형식으로 출력하기 위해 fmt태그 사용 --> <fmt:formatDate value="${row.regdate}" pattern="yyyy-MM-dd HH:mm:ss"/> </td> <td>${row.viewcnt}</td> </tr> </c:forEach> <tr> <td colspan="5"> <!-- 처음페이지로 이동 : 현재 페이지가 1보다 크면 [처음]하이퍼링크를 화면에 출력--> <c:if test="${map.boardPager.curBlock > 1}"> <a href="javascript:list('1')">[처음]</a> </c:if> <!-- 이전페이지 블록으로 이동 : 현재 페이지 블럭이 1보다 크면 [이전]하이퍼링크를 화면에 출력 --> <c:if test="${map.boardPager.curBlock > 1}"> <a href="javascript:list('${map.boardPager.prevPage}')">[이전]</a> </c:if> <!-- **하나의 블럭 시작페이지부터 끝페이지까지 반복문 실행 --> <c:forEach var="num" begin="${map.boardPager.blockBegin}" end="${map.boardPager.blockEnd}"> <!-- 현재페이지이면 하이퍼링크 제거 --> <c:choose> <c:when test="${num == map.boardPager.curPage}"> <span style="color: red">${num}</span> </c:when> <c:otherwise> <a href="javascript:list('${num}')">${num}</a> </c:otherwise> </c:choose> </c:forEach> <!-- 다음페이지 블록으로 이동 : 현재 페이지 블럭이 전체 페이지 블럭보다 작거나 같으면 [다음]하이퍼링크를 화면에 출력 --> <c:if test="${map.boardPager.curBlock <= map.boardPager.totBlock}"> <a href="javascript:list('${map.boardPager.nextPage}')">[다음]</a> </c:if> <!-- 끝페이지로 이동 : 현재 페이지가 전체 페이지보다 작거나 같으면 [끝]하이퍼링크를 화면에 출력 --> <c:if test="${map.boardPager.curPage <= map.boardPager.totPage}"> <a href="javascript:list('${map.boardPager.totPage}')">[끝]</a> </c:if> </td> </tr> </table> </body> </html> | cs |