Bigfat

[Web] 답글 쓰기 구현하기 (게시판 구현) 본문

Java/web

[Web] 답글 쓰기 구현하기 (게시판 구현)

kyou 2017. 4. 13. 14:59

게시글에 답글을 쓰는 기능을 구현해보자

  이전과 동일하게 bbs.properties파일에 요청에 대해 처리할 클래스를 매핑한다.

#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


  content.jsp에서 가지고 있던 해당 게시글에 대한 pageNum, depth, groupId, pos 값을 받아 다시 HttpServletRequest 객체에 심는다.

package com.edu.bbs;

import java.io.UnsupportedEncodingException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ReplyFormImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, UnsupportedEncodingException {
		
		req.setAttribute("pageNum", req.getParameter("pageNum"));
		req.setAttribute("depth", req.getParameter("depth"));
		req.setAttribute("groupId", req.getParameter("groupId"));
		req.setAttribute("pos", req.getParameter("pos"));
		
		return "/replyForm.jsp";
	}

}

  replyForm.jsp를 생성하고 아래와 같이 작성한다. '취소'버튼은 로그인, 로그아웃을 구현하면서 공부한 브라우저 히스토리 객체를 사용하였다. 뭐든 모르는 것보단 역시 알고 있는게 좋다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>BBS Reply</title>
	<link href="bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet">
	<style>
		#contentForm {
			width: 40%;
			margin: 0 auto;
			padding-top: 12%;
		}

		.table > thead > tr > th, .table > tbody > tr > th {
			background-color: #e6ecff;
			text-align: center;
		}
	</style>
</head>
<body>
	<form action="/bbs/reply.bbs" method="post">
		<div id="contentForm">
		    <input type="hidden" name="pageNum" value="${pageNum}">
		    <input type="hidden" name="groupId" value="${groupId}">
		    <input type="hidden" name="depth" value="${depth}">
		    <input type="hidden" name="pos" value="${pos}">
		    
		    <div class="input-group input-group-sm" role="group" aria-label="...">
				<table class="table table-striped table-bordered">
					<thead>
						<tr>
							<th width="30%">글쓴이</th>
							<td width="70%">${id}</td>
						</tr>
						<tr>
							<th style="padding-bottom: 15px;">제목</th>
							<td><input type="text" name="title" value="[Re]&nbsp;"
											class="form-control" aria-describedby="basic-addon1"></td>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td colspan="2">
								<textarea class="form-control" rows="10" name="content" placeholder="글을 적어 주세요."></textarea>
							</td>
						</tr>
						<tr>
							<th style="padding-top: 15px">첨부파일</th>
							<td><input type="file" class="btn btn-default" name="fileName"></td>
						</tr>
					</tbody>
				</table>
			</div>
			<div class="btn-group btn-group-sm" role="group" aria-label="...">
				<input type="submit" class="btn btn-default" value="글쓰기">
				<input type="reset" class="btn btn-default" value="초기화">
				<input type="button" class="btn btn-default" value="취소" onclick="window.history.back()">
				</div>
		</div>
	</form>
</body>
</html>


  /bbs/reply.bbs 요청을 처리하는 ReplyImpl클래스를 작성한다.

package com.edu.bbs;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ReplyImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		req.setCharacterEncoding("utf-8");
		
		BBSDto article = new BBSDto();
		
		article.setId(req.getSession().getAttribute("id").toString());
		article.setTitle(req.getParameter("title"));
		article.setContent(req.getParameter("content"));
		article.setGroupId(Integer.parseInt(req.getParameter("groupId")));
		article.setDepth(Integer.parseInt(req.getParameter("depth")));
		article.setPos(Integer.parseInt(req.getParameter("pos")));
		article.setFileName(req.getParameter("fileName"));

		try {
			BBSOracleDao.getInstance().replyArticle(article);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// 이중 submit을 막기 위해 redirect 해준다.
		resp.sendRedirect("/bbs/list.bbs?pageNum="+req.getParameter("pageNum"));
		
		return null;
	}
}

  BBSOracleDao클래스에 아래 두 메서드를 작성한다. group_id는 부모글의 article_number이며, 이 부모글의 답글(자식글)은 같은 group_id를 가진다. depth는 답글의 깊이이며 부모글은 0, 이 부모글의 답글은 1, 이 답글의 답글은 depth가 2가 된다. pos는 이 답글들의 순서를 조절하게 된다. 같은 depth의 답글은 존재할 수 있지만 pos값으로 순서를 조절할 수 있다는 것이다. group_id, depth 그리고 pos는 직접 연습장에 그려가면서 공부해보는 것이 좋다. 예전에 지금과 같이 무한 댓글 기능을 구현하면서 오라클의 계층형 구조 쿼리를 사용하였었는데 스프링프레임워크로 다시 게시판을 구현할 때를 위해 현재의 방법을 택했다.
/**
 * BBSOracleDao클래스에 추가한다.
 * @param groupId
 * @param pos
 * @return
 */
public synchronized int upPos(int groupId, int pos) throws ClassNotFoundException, SQLException {
	conn = orclDbc.getConnection();
	query = new StringBuffer();
	query.append("UPDATE bbs");
	query.append("     SET pos = pos + 1");
	query.append(" WHERE group_id = ?");
	query.append("     AND pos > ?");
	pstmt = conn.prepareStatement(query.toString());
	pstmt.setInt(1, groupId);
	pstmt.setInt(2, pos);
	int result = pstmt.executeUpdate();

	disconnect();
	
	return result;
}

public synchronized int replyArticle(BBSDto article) throws ClassNotFoundException, SQLException {
	BBSOracleDao.getInstance().upPos(article.getGroupId(), article.getPos());
	
	conn = orclDbc.getConnection();
	query = new StringBuffer();
	query.append("INSERT INTO bbs ");
	query.append("VALUES(bbs_seq.nextval, ?, ?, ?, ?, ?, ?, 0, sysdate, ?)");
	pstmt = conn.prepareStatement(query.toString());
	pstmt.setString(1, article.getId());
	pstmt.setString(2, article.getTitle());
	pstmt.setString(3, article.getContent());
	// groupId는 그대로 삽입한다.
	pstmt.setInt(4, article.getGroupId());
	// depth는 1 증가시킨다.
	pstmt.setInt(5, article.getDepth() + 1);
	// pos은 부모글 아래에 이미 답글이 있으면 모두 1씩 증가시킨 후(upPos메서드가 수행),
	// 현재 삽입하는 게시글의 pos를 1 증가시킨다.
	pstmt.setInt(6, article.getPos() + 1);
	pstmt.setString(7, article.getFileName());
	
	int result = pstmt.executeUpdate();
	
	disconnect();
	
	return result;
}


  코드를 확인하면서 list.jsp에서 depth에 따라 공백을 주는 코드가 잘못된 것을 발견하고 수정하였다. <tbody>태그 내용을 아래와 같이 수정하자.

<tbody>
	<c:forEach var="article" items="${articles}" varStatus="status">
		<tr>
			<td>${article.articleNumber}</td>
			<td id="title">
				<c:if test="${article.depth > 0}">
					<c:forEach var="i" begin="1" end="${article.depth}">
						<span>&nbsp;&nbsp;</span>
					</c:forEach>
				</c:if>
				<a href="/bbs/content.bbs?articleNumber=${article.articleNumber}&pageNum=${pageNum}" id="writeLink">${article.title}</a>
				<c:if test="${article.hit >= 20}">
					<span class="hit">hit!</span>
				</c:if>
			</td>
			<td>${article.id}</td>
			<td>${article.writeDate}</td>
			<td>${article.hit}</td>
		<tr>
	</c:forEach>
</tbody>


  아래 답글 쓰기 기능 구현에 대한 시연 화면이다. 사실은 답글 쓰기를 제대로 구현하려면 삭제 기능에 추가적인 작업을 해줘야한다. 네이버처럼 부모글을 지우면 그 부모글의 자식글을 남기는 대신 부모글이 삭제되었다는 정보를 남겨줘야 하는데, 나중에 시간이 되면 추가할 계획이 있다.