- Outsider's Dev Story
- 흔한 개발자의 개발 노트
- 내 아이큐 80, 너도 80, 둘이 합쳐 160 - 내…
- 조대협의 블로그
- 자바캔(Java Can Do IT) / 최범균 님
- Sangon, Han – 개발자; 읽고 생각하고 쓰고 …
- The Evolution of the Web
- NAVER D2
- Dashboard - SLiPP
- ITWorld Korea - 테크놀로지 리더를 위한 글…
- OKKY - All That Developer
- 웹Frameworks
- 오픈튜토리얼스
- 위키독스 / 온라인 책 제작 공유 플랫폼 서비스
- 블로터
- IT OnAir
- 한 처음에 / 백창92
- Divide my knowledge / 완프최
- SERI.org
Bigfat
[Web] Ajax를 이용한 댓글 쓰기 구현하기 (게시판 구현) 본문
Ajax를 이용해 비동기 통신으로 댓글 쓰기를 구현해보자
기존 웹 애플리케이션은 브라우저에서 채운 form을 웹 서버로 제출(submit)하는 요청으로 웹 서버의 중복되는 HTML 코드의 전송으로 대역폭의 낭비를 야기할 수 있다. Ajax(Asynchronous JavaSript and XML, 에이잭스)는 페이지 이동 없이 필요한 데이터만을 웹서버에 요청해서 받은 후 클라이언트에서 데이터에 대한 처리를 할 수 있는 기술이기 때문에 기존 웹 애플리케이션의 단점을 극복할 수 있다.
웹 서버와 비동기적으로 데이터를 교환하고 조작하기 위해 XML을 이용했기 때문에 붙여진 이름이지만, Ajax 애플리케이션은 XML 대신하는 데이터 포맷으로 JSON(JavaScript Object Notation)을 이용할 수 있다.
프로젝트 구조는 아래와 같이 진행된다. com.edu.comment패키지를 생성해 기존 패키지와 분리하였다. JSON 라이브러리를 사용하기 위해 메이블 리퍼지토리에서 다운로드 받은 후 /WEB-INF/lib에 추가한다.
댓글(comment)에 관한 테이블과 시퀀스를 생성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | --댓글 테이블 생성 CREATE TABLE comments( comment_number NUMBER PRIMARY KEY, id VARCHAR2(15) NOT NULL, comment_content VARCHAR2(200) NOT NULL, comment_date DATE NOT NULL, article_number NUMBER NOT NULL, --글이 지워지면 해당 답글도 지워져야 한다. --article_number > delete option : cascade CONSTRAINT comment_fk FOREIGN KEY(article_number) REFERENCES bbs(article_number) ON DELETE CASCADE); --댓글 시퀀스 생성 CREATE SEQUENCE comment_seq START WITH 1 INCREMENT BY 1; | cs |
bbs.properties파일에 /commentWrite.bbs, /commentRead.bbs를 추가해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #bbs.properties /writeForm.bbs=com.edu.bbs.WriteFormImpl /write.bbs=com.edu.bbs.WriteImpl /list.bbs=com.edu.bbs.ListImpl /content.bbs=com.edu.bbs.ContentImpl /login.bbs=com.edu.bbs.LoginImpl /logout.bbs=com.edu.bbs.LogoutImpl /updateForm.bbs=com.edu.bbs.UpdateFormImpl /update.bbs=com.edu.bbs.UpdateImpl /delete.bbs=com.edu.bbs.DeleteImpl /replyForm.bbs=com.edu.bbs.ReplyFormImpl /reply.bbs=com.edu.bbs.ReplyImpl /commentWrite.bbs=com.edu.comment.CommentWriteImpl /commentRead.bbs=com.edu.comment.CommentReadImpl |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.edu.comment; public class CommentDto { private int commentNumber; private String id; private String commentContent; private String commentDate; private int articleNumber; public int getCommentNumber() { return commentNumber; } public void setCommentNumber(int commentNumber) { this.commentNumber = commentNumber; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getCommentContent() { return commentContent; } public void setCommentContent(String commentContent) { this.commentContent = commentContent; } public String getCommentDate() { return commentDate; } public void setCommentDate(String commentDate) { this.commentDate = commentDate; } public int getArticleNumber() { return articleNumber; } public void setArticleNumber(int articleNumber) { this.articleNumber = articleNumber; } @Override public String toString() { return "CommentDto [commentNumber=" + commentNumber + ", id=" + id + ", commentContent=" + commentContent + ", commentDate=" + commentDate + ", articleNumber=" + articleNumber + "]"; } } | cs |
BBSDto클래스에 commentCount 멤버 변수를 선언해주고, getter, setter메서드를 추가하고, 오버라이딩한 toString메서드에 commentCount가 추가될 수 있도록 변경해주자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | package com.edu.bbs; import java.sql.Timestamp; public class BBSDto { private int articleNumber; private String id; private String title; private String content; private int groupId; private int depth; private int pos; private int hit; private Timestamp writeDate; private String fileName; private long commentCount; // 추가한다. public int getArticleNumber() { return articleNumber; } public void setArticleNumber(int articleNumber) { this.articleNumber = articleNumber; } public String getId() { return id; } public void setId(String id) { this.id = id; } 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 int getGroupId() { return groupId; } public void setGroupId(int groupId) { this.groupId = groupId; } public int getDepth() { return depth; } public void setDepth(int depth) { this.depth = depth; } public int getPos() { return pos; } public void setPos(int pos) { this.pos = pos; } public int getHit() { return hit; } public void setHit(int hit) { this.hit = hit; } public Timestamp getWriteDate() { return writeDate; } public void setWriteDate(Timestamp writeDate) { this.writeDate = writeDate; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public long getCommentCount() { return commentCount; } public void setCommentCount(long commentCount) { this.commentCount = commentCount; } @Override public String toString() { return "BBSDto [articleNumber=" + articleNumber + ", id=" + id + ", title=" + title + ", content=" + content + ", groupId=" + groupId + ", depth=" + depth + ", pos=" + pos + ", hit=" + hit + ", writeDate=" + writeDate + ", fileName=" + fileName + ", commentCount=" + commentCount + "]"; } } | cs |
BBSOracleDao클래스에 comment, comments 멤버 변수를 선언한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class BBSOracleDao implements LoginStatus { private static BBSOracleDao bbsOracleDao = new BBSOracleDao(); private OracleDBConnector orclDbc = OracleDBConnector.getInstacne(); Connection conn; PreparedStatement pstmt; ResultSet rs; StringBuffer query; BBSDto article; ArrayList<BBSDto> articleList; int totalCount; // 멤버 변수를 추가한다. CommentDto comment; ArrayList<CommentDto> comments; ... | cs |
BBSOracleDao클래스의 selectArticle메서드에 해당 글의 comment 총 개수를 조회하는 코드를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public BBSDto selectArticle(String articleNumber) throws ClassNotFoundException, SQLException { conn = orclDbc.getConnection(); query = new StringBuffer(); query.append("SELECT * FROM bbs WHERE article_number = ?"); pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, articleNumber); rs = pstmt.executeQuery(); if(rs.next()) { article = new BBSDto(); article.setArticleNumber(rs.getInt("article_number")); article.setId(rs.getString("id")); article.setTitle(rs.getString("title")); article.setDepth(rs.getInt("depth")); article.setContent(rs.getString("content")); article.setHit(rs.getInt("hit")); article.setGroupId(rs.getInt("group_id")); article.setPos(rs.getInt("pos")); article.setWriteDate(rs.getTimestamp("write_date")); article.setFileName(rs.getString("file_name")); } // Comment Counting query = new StringBuffer(); query.append("SELECT count(*) FROM comments WHERE article_number = ?"); pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, articleNumber); rs = pstmt.executeQuery(); if(rs.next()) { article.setCommentCount(rs.getLong(1)); } disconnect(); return article; } | cs |
BBSOracleDao클래스에 아래 메서드를 추가한다. insertComment메서드는 return값을 JSONObject의 생성자 파라미터로 넣어주기 위해 타입을 HashMap으로 선언하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | /** * BBSOracleDao클래스에 추가한다. * @param articleNumber * @param commPageSize * @return */ public ArrayList<CommentDto> selectComments(String articleNumber, int commPageSize) throws ClassNotFoundException, SQLException { conn = orclDbc.getConnection(); query = new StringBuffer(); query.append("SELECT * "); query.append(" FROM (SELECT id, comment_content, comment_date, article_number "); query.append(" FROM comments "); query.append(" WHERE article_number = ? "); query.append(" ORDER BY comment_number DESC"); query.append(" ) comments "); query.append(" WHERE rownum BETWEEN 1 AND ?"); pstmt = conn.prepareStatement(query.toString()); pstmt.setString(1, articleNumber); pstmt.setInt(2, commPageSize); rs = pstmt.executeQuery(); comments = new ArrayList<>(); while(rs.next()) { comment = new CommentDto(); comment.setId(rs.getString("id")); comment.setCommentContent(rs.getString("comment_content")); comment.setCommentDate(rs.getString("comment_date")); comment.setArticleNumber(rs.getInt("article_number")); comments.add(comment); } disconnect(); return comments; } public synchronized HashMap<String, Object> insertComment(String id, String commentContent, String articleNumber) throws ClassNotFoundException, SQLException { conn = orclDbc.getConnection(); pstmt = conn.prepareStatement("INSERT INTO comments VALUES(comment_seq.nextval, ?, ?, sysdate, ?)"); pstmt.setString(1, id); pstmt.setString(2, commentContent); pstmt.setString(3, articleNumber); int result = pstmt.executeUpdate(); ArrayList<CommentDto> comments = selectComments(articleNumber, 10); HashMap<String, Object> hm = new HashMap<>(); hm.put("result", result); hm.put("comments", comments); disconnect(); return hm; } | cs |
/commentWrite.bbs 요청을 처리할 CommentWriteImpl클래스를 아래와 같이 작성한다. resp의 getWriter메서드로 PrintWriter객체를 얻어 JSONObject를 output한다. getWriter메서드는 HttpServletResponse의 부모인 ServletResponse인터페이스에 선언되어있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package com.edu.comment; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.json.JSONObject; import com.edu.bbs.BBSOracleDao; import com.edu.bbs.BBSService; public class CommentWriteImpl implements BBSService { @Override public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); String id = req.getSession().getAttribute("id").toString(); String commentContent = req.getParameter("commentContent"); String articleNumber = req.getParameter("articleNumber"); HashMap<String, Object> result = null; try { result = BBSOracleDao.getInstance().insertComment(id, commentContent, articleNumber); } catch (Exception e) { e.printStackTrace(); } JSONObject jsonObj = new JSONObject(result); PrintWriter pw = resp.getWriter(); pw.println(jsonObj); return null; } } | cs |
/commentRead.bbs 요청을 처리할 CommentRead클래스를 아래와 같이 작성한다. JSONArray객체의 생성자 파라미터로 selectComments메서드의 return값인 ArrayList<CommentDto>를 넣어주고, 위와 동일하게 output한다. 오버라이딩한 메서드명이 잘못 작성되어있어 bbs()에서 bbsService()로 변경하였고, 패키지 구조를 프로젝트에 맞게 com.edu.comment로 변경하였습니다(17.05.29.). 이클립스나 STS와 같은 IDE에서는 Refactor를 이용해 파일명을 변경하거나 파일을 이동시키도록 합시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package com.edu.comment; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.json.JSONArray; import com.pknu.bbs.BBSOracleDao; import com.pknu.bbs.BBSService; public class CommentReadImpl implements BBSService { @Override public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // resp.setContentType("text/html;charset=utf-8"); resp.setCharacterEncoding("utf-8"); // JSON 한글 깨짐 해결 int commPageNum = Integer.parseInt(req.getParameter("commPageNum")); String articleNumber = req.getParameter("articleNumber"); ArrayList<CommentDto> comments = null; try { comments = BBSOracleDao.getInstance().selectComments(articleNumber, commPageNum); } catch (Exception e) { e.printStackTrace(); } JSONArray jsonArr = new JSONArray(comments); // 스프링에선 애노테이션(?) PrintWriter pw = resp.getWriter(); pw.println(jsonArr); return null; } } | cs |
content.jsp에 comment를 추가해줄 부분에 아래 코드를 삽입한다. 댓글 입력 textarea 코드가 누락되어 추가했습니다(17.05.29.).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <div class="input-group" role="group" aria-label="..." style="margin-top: 10px; width: 100%;"> <textarea class="form-control" rows="3" id="commentContent" placeholder="댓글을 입력하세요." style="width: 100%;" ></textarea> <div class="btn-group btn-group-sm" role="group" aria-label="..."> <c:if test="${id == null}"> <input type="button" class="btn btn-default" value="댓글 쓰기" disabled="disabled"> </c:if> <c:if test="${id != null}"> <input type="button" class="btn btn-default" value="댓글 쓰기" id="commentWrite"> </c:if> <input type="button" class="btn btn-default" value="댓글 읽기(${article.commentCount})" onclick="getComment(1, event)" id="commentRead"> </div> </div> <!-- Comment 태그 추가 --> <div class="input-group" role="group" aria-label="..." style="margin-top: 10px; width: 100%;"> <div id="showComment" style="text-align: center;"></div> </div> | cs |
content.jsp에 아래 스크립트를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | <script> jQuery(document).ready(function() { if(${id== null}) { alert("게시판을 이용하시려면 로그인하셔야 합니다."); location.href="/bbs/login.bbs"; } }); // Perform an asynchronous HTTP (Ajax) request. // 비동기 통신 Ajax를 Setting한다. $.ajaxSetup({ type:"POST", async:true, dataType:"json", error:function(xhr) { console.log("error html = " + xhr.statusText); } }); $(function() { $("#commentWrite").on("click", function() { $.ajax({ url:"/bbs/commentWrite.bbs", // data:{}에서는 EL을 ""로 감싸야 한다. 이외에는 그냥 사용한다. data:{ commentContent:$("#commentContent").val(), articleNumber:"${article.articleNumber}" }, beforeSend:function() { console.log("시작 전..."); }, complete:function() { console.log("완료 후..."); }, success:function(data) { // 서버에 대한 정상응답이 오면 실행, callback if(data.result == 1) { // 쿼리 정상 완료, executeUpdate 결과 console.log("comment가 정상적으로 입력되었습니다."); $("#commentContent").val(""); showHtml(data.comments, 1); } } }) }); }); function showHtml(data, commPageNum) { let html = "<table class='table table-striped table-bordered' style='margin-top: 10px;'><tbody>"; $.each(data, function(index, item) { html += "<tr align='center'>"; html += "<td>" + (index+1) + "</td>"; html += "<td>" + item.id + "</td>"; html += "<td align='left'>" + item.commentContent + "</td>"; let presentDay = item.commentDate.substring(0, 10); html += "<td>" + presentDay + "</td>"; html += "</tr>"; }); html += "</tbody></table>"; commPageNum = parseInt(commPageNum); // 정수로 변경 // commentCount는 동기화되어 값을 받아오기 때문에, 댓글 insert에 즉각적으로 처리되지 못한다. if("${article.commentCount}" > commPageNum * 10) { nextPageNum = commPageNum + 1; html +="<input type='button' class='btn btn-default' onclick='getComment(nextPageNum, event)' value='다음 comment 보기'>"; } $("#showComment").html(html); $("#commentContent").val(""); $("#commentContent").focus(); } function getComment(commPageNum, event) { $.ajax({ url:"/bbs/commentRead.bbs", data:{ commPageNum:commPageNum*10, articleNumber:"${article.articleNumber}" }, beforeSend:function() { console.log("읽어오기 시작 전..."); }, complete:function() { console.log("읽어오기 완료 후..."); }, success:function(data) { console.log("comment를 정상적으로 조회하였습니다."); showHtml(data, commPageNum); let position = $("#showComment table tr:last").position(); $('html, body').animate({scrollTop : position.top}, 400); // 두 번째 param은 스크롤 이동하는 시간 } }) } </script> | cs |
아래는 댓글을 쓰고 읽어들이는 시연 화면이다.
아래는 해당 글에 대한 댓글을 commPageSize(댓글 개수)에 따라 읽어들이는 시연 화면이다.
[Ajax, 위키백과 참고1]
'Java > web' 카테고리의 다른 글
[Spring] Overview of Spring MVC Architecture (0) | 2017.04.18 |
---|---|
[Spring] 스프링 프로젝트 생성 및 웹 애플리케이션 실행하기 (0) | 2017.04.17 |
[Web] 답글 쓰기 구현하기 (게시판 구현) (1) | 2017.04.13 |
[Web] 글 삭제 구현하기 (게시판 구현) (0) | 2017.04.13 |
[Web] 부트스트랩 적용하기, Pagination (게시판 구현) (0) | 2017.04.12 |