본문 바로가기

JSP&Servlet

계층형 게시판(1)

이번 포스팅은 계층형 게시판 프로그램을 구현하도록 하겠다. 게시판은 그 나름대로의 의미도 있겠지만 게시판에서 구현되는 내용들은 다른 프로그램에 많이 사용된다. 예를 들어 글쓰기, 읽기, 삭제하기, 수정하기, 답변하기는 데이터를 조작하는 기본 SQL문이 모두 사용되므로 이와 비슷한 기능들을 하는 프로그램들은 쉽게 접근해서 프로그래밍을 할 수가 있다. 내용이 꽤 많기 때문에 여러 차례로 나누어서 글을 올릴 계회이다.

 

1. 데이터베이스 설계

먼저 프로그래밍에 앞서 필요한 SQL 테이블을 만들어 보겠다. 게시판에 필요한 테이블은 1개이다. 작성자의 이름과 이메일, 홈페이지, 제목, 본문 내용, 날짜, 조회수 등이 저장되는 테이블이다.

 

 

2. 설계 및 구현

계층형 게시판은 쓰기, 읽기, 삭제, 수정, 답변 등의 기능들이 있다. 한꺼번에 모든 기능들을 구현하는 것보다는 단계적으로 흐름에 맞게 구현하는 것이 처음 JSP 프로그램을 하시는 분들께는 좋은 방법이라고 생각한다. 지금부터 2단계로 나누어서 첫 번째는 쓰기, 읽기, 삭제를 구현하고 두 번째는 수정, 답변을 단계적으로 구현하겠다.

 

1) 게시물의 쓰기, 읽기, 삭제 만들기

먼저 게시판의 쓰기 기능이다. post.jsp 페이지에서 게시물을 입력하고 BoardPostServlet.java 서블릿에서 데이터베이스에 저장되는 부분을 처리한다. BoardPostServlet.java 서블릿에서는 게시물의 저장이 완료되면 자동적으로 게시판 읽기 기능을 담당하는 list.jsp 페이지로 이동한다. list.jsp 페이지는 게시물을 가져오는 역할 뿐만 아니라 검색 기능과 페이징 및 블럭 처리 부분을 담당하는 페이지이다.

 

list.jsp 페이지에서 게시물의 제목을 클릭하면 게시판의 읽기 기능을 담당하는 read.jsp 페이지로 이동한다. read.jsp 페이지에서는 삭제, 수정, 답변의 기능들을 담당하는 페이지의 링크가 있지만 첫 번째 단계에서는 삭제만 다루도록 하겠다. read.jsp 페이지에서 삭제를 클릭하여 delete.jsp 페이지로 이동하면 게시물의 비밀번호 입력란이 나온다. 입력한 비밀번호가 일치하면 데이터베이스에 저장되어 있는 게시물은 삭제되고 list.jsp 페이지로 이동한다. 그러나 일치하지 않으면 다시 비밀번호를 묻는다. 여기까지가 첫 번째 단계에서 구현할 흐름이다. 지금부터 흐름을 잘 생각하면서 본격적으로 게시판을 구현하겠다.

 

[그림 1] 게시물의 쓰기, 읽기, 삭제 흐름도

 

[list.jsp]

<%@ page contentType="text/html; charset=UTF-8" %>
<%@page import="BoardPack.BoardBean"%>
<%@page import="java.util.Vector"%>

<!-- useBean 액션 태그로 BoardMgr 빈즈 객체를 생성하고 있다. -->
<jsp:useBean id="bMgr" class="BoardPack.BoardMgr" />
<%	
	  request.setCharacterEncoding("UTF-8");
	  
      int totalRecord=0; 		// 전체 레코드수
	  int numPerPage=10; 		// 페이지당 레코드 수 
	  int pagePerBlock=15; 		// 블럭당 페이지수 
	  
	  int totalPage=0; 			// 전체 페이지 수
	  int totalBlock=0; 		// 전체 블럭수 

	  int nowPage=1; 			// 현재 페이지
	  int nowBlock=1;  			// 현재 블럭
	  
	  // 게시물을 가져오는 쿼리문인 select문의 마지막에 limit를 사용하면 한 페이지에 필요한 만큼의
	  // 게시물만을 가져올 수 있다. limit에 가져올 게시물의 개수를 지정하기 위햇 start와 end 변수를 선언하였다.
	  int start=0; 				// DB의 select 시작번호
	  int end=10; 				// 시작번호로부터 가져올 select 개수
	  
	  int listSize=0; 			// 현재 읽어온 게시물의 수

	String keyWord = "", keyField = "";
	// getBoardList() 메소드의 리턴타입을 Vector<BoardBean>으로 선언함
	Vector<BoardBean> vlist = null;
	if (request.getParameter("keyWord") != null) {
		keyWord = request.getParameter("keyWord");
		keyField = request.getParameter("keyField");
	}
	
	// reload 값이 true면 위에서 받아온 두 값을 비워줌
	if (request.getParameter("reload") != null){
		if(request.getParameter("reload").equals("true")) {
			keyWord = "";
			keyField = "";
		}
	}
	
	if (request.getParameter("nowPage") != null) {
		nowPage = Integer.parseInt(request.getParameter("nowPage"));
	}
	// ( 3 * 10) - 10
	// start = 20
	// end = 10
	 start = (nowPage * numPerPage)-numPerPage;
	 end = numPerPage;
	 
	totalRecord = bMgr.getTotalCount(keyField, keyWord);
	
	// 전체 페이지 개수를 계산하는 부분이다. 만약 122개의 레코드가 있다면 
    // 122/10의 결괏값의 12.2를 절상(소수점이 나오면
	// 무조건 올림)을 시켜 13개의 페이지가 만들어지는 것이다. 
    // 제일 마지막 페이지는 2개의 레코드를 가지고 있는 것이다.
	// double 쓰는 이유 : 정수끼리 나누면 실수 부분은 버리기 때문에 
    // 그걸 살리기 위함(자료형의 승격)
	totalPage = (int)Math.ceil((double)totalRecord / numPerPage); // 전체페이지수
	
	nowBlock = (int)Math.ceil((double)nowPage/pagePerBlock); 	// 현재블럭 계산
	
	// 전체 블럭 수를 계산하는 부분이다. 
    // 전체 블럭의 개수는 전체 페이지의 개수를 블럭 당 페이지 수로 나누어서 계산한다.
	// 원리는 전체 페이지 개수를 구하는 방법과 같다.
	totalBlock = (int)Math.ceil((double)totalPage / pagePerBlock); // 전체블럭계산
%>
<html>
<head>
<title>JSP Board</title>
<!-- 이 페이지에 관련된 스타일시트를 style.css에서 처리를 할 수 있도록 지정하였다. -->
<link href="style.css" rel="stylesheet" type="text/css">
<script type="text/javascript">
	function list() {
		document.listFrm.action = "list.jsp";
		document.listFrm.submit();
	}
	
	function pageing(page) {
		document.readFrm.nowPage.value = page;
		document.readFrm.submit();
	}
	
	function block(value){
		 document.readFrm.nowPage.value=<%=pagePerBlock%>*(value-1)+1;
		 document.readFrm.submit();
	} 
	
	function read(num){
		// reaFrm의 num 변수에다가 num을 할당하고 read.jsp로 넘어가서 submit 해 줌
		document.readFrm.num.value=num;
		document.readFrm.action="read.jsp";
		document.readFrm.submit();
	}
	
	function check() {
		// 검색폼의 키워드가 비워져 있으면 alert 창 띄우고 focus 해 줌
	     if (document.searchFrm.keyWord.value == "") {
			alert("검색어를 입력하세요.");
			document.searchFrm.keyWord.focus();
			return;
	     }
	  document.searchFrm.submit();
	 }
</script>
</head>
<body bgcolor="#FFFFCC">
<div align="center">
	<br/>
	<h2>JSPBoard</h2>
	<br/>
	<table align="center" width="600">
			<tr>
                // 전체 게시물 수와 현재 페이지와 전체 페이지 수를 보여줌
				<td>Total : <%=totalRecord%>Articles(<font color="red">
				<%=nowPage%>/<%=totalPage%>Pages</font>)</td>
			</tr>
	</table>
	<table align="center" width="600" cellpadding="3">
		<tr>
			<td align="center" colspan="2">
			<%
				 // 전체 게시물을 리턴하기 위해서 BoardMgr 클래스에 있는 
                 // getBoardList() 메소드를 호출한다.
				 // getBoardList() 메소드의 매개변수는 한 페이지 당 
                 // 출력할 게시물의 개수를 지정하는 start와 end,
				 // 그리고 검색을 위해서 넘겨받은 keyWord 값과 keyField 값이다. 
                 // 만약 검색하지 않고 전체 게시물을 리턴한다면 keyWord 값과 
                 // keyField 값은 공백값으로 처리가 된다. 
				  vlist = bMgr.getBoardList(keyField, keyWord, start, end);
			
				 // 등록된 게시물이 없거나 검색 후에 조건에 맞는 결괏값이 없으면 
                 // 실행되는 부분임
				  listSize = vlist.size(); //브라우저 화면에 보여질 게시물 번호
				  if (vlist.isEmpty()) {
					out.println("등록된 게시물이 없습니다.");
				  } else {
			%>
				  <table width="100%" cellpadding="2" cellspacing="0">
					<tr align="center" bgcolor="#D0D0D0" height="120%">
						<td>번 호</td>
						<td>제 목</td>
						<td>이 름</td>
						<td>날 짜</td>
						<td>조회수</td>
					</tr>
					<%
						// 페이지별 뿌려줄 게시물의 시작번호부터 페이지 당 
                        // 레코드 수만큼 for문이 반복됨
						  for (int i = 0;i<numPerPage; i++) {
						// for문 수행 중에 페이지별 뿌려줄 게시물의 시작번호가 
                        // 전체 게시물과 같다면 for문을 빠져나옴
							if (i == listSize) break;
							
							BoardBean bean = vlist.get(i);
							int num = bean.getNum();
							String name = bean.getName();
							String subject = bean.getSubject();
							String regdate = bean.getRegdate();
							int depth = bean.getDepth();
							int count = bean.getCount();
					%>
					<tr>
						<td align="center">
							<!-- 게시번호를 보여준다.(최신 게시물일수록 앞에 오기 때문에 
                            현재 페이지가 낮을수록 게시번호가 커진다. -->
							<%=totalRecord-((nowPage-1)*numPerPage)-i%>
						</td>
						<td>
						<%
						// 게시물에서 답변 글을 달 때 앞에 띄어쓰기를 해 주는 기능
							  if(depth>0){
								for(int j=0;j<depth;j++){
									out.println("&nbsp;&nbsp;");
									}
								}
						%>
						  <a href="javascript:read('<%=num%>')"><%=subject%></a>
						</td>
						<td align="center"><%=name%></td>
						<td align="center"><%=regdate%></td>
						<td align="center"><%=count%></td>
						</tr>
					<%}//for%>
				</table> <%
 			}//if
 		%>
			</td>
		</tr>
		<tr>
			<td colspan="2"><br /><br /></td>
		</tr>
		<tr>
			<td>
			<!-- 페이징 및 블럭 처리 Start--> 
			<%
   				  int pageStart = (nowBlock -1)*pagePerBlock + 1 ; //하단 페이지 시작번호
   				  int pageEnd = ((pageStart + pagePerBlock ) <= totalPage) ? 
                  (pageStart + pagePerBlock): totalPage+1; //하단 페이지 끝번호
   				  if(totalPage !=0){
    			  	if (nowBlock > 1) {%>
    			  		<!-- 이전 블록으로 이동 -->
    			  		<a href="javascript:block('<%=nowBlock-1%>')">prev...</a><%}%>&nbsp; 
    			  		<%for ( ; pageStart < pageEnd; pageStart++){%>
     			     	<a href="javascript:pageing('<%=pageStart %>')"> 
     			     	<!-- 현재페이지가 보고 있는 페이지면 그 부분만 blue로 표시 -->
     					<%if(pageStart==nowPage) {%><font color="blue"> <%}%>
     					[<%=pageStart %>] 
     					<%if(pageStart==nowPage) {%></font> <%}%></a> 
    					<%}//for%>&nbsp;
    					
    					<!-- 현재 블록을 기준으로 뒤에 볼 게 남았으면 다음 블록으로 이동 -->
    					<%if (totalBlock > nowBlock ) {%>
    					
    					<!-- 다음 블록으로 이동 -->
    					<a href="javascript:block('<%=nowBlock+1%>')">.....next</a>
    				<%}%>&nbsp;  
   				<%}%>
 				<!-- 페이징 및 블럭 처리 End-->
				</td>
				<td align="right">
					<a href="post.jsp">[글쓰기]</a> 
					<a href="javascript:list()">[처음으로]</a>
				</td>
			</tr>
		</table>
	<hr width="600"/>
	<!-- 게시물 검색을 위한 form이다. 먼저 select box에서 검색하고자 하는 목록을 선택하고 
	검색어를 입력학 후에 [찾기] 버튼을 클릭하면 hidden 타입의 page의 value 값은 
	정수 0의 값을 안고 현재의 페이지로 호출되면서 검색한 게시물을 보여줌 -->
	<form  name="searchFrm"  method="get" action="list.jsp">
	<table width="600" cellpadding="4" cellspacing="0">
 		<tr>
  			<td align="center" valign="bottom">
   				<select name="keyField" size="1" >
    				<option value="name"> 이 름</option>
    				<option value="subject"> 제 목</option>
    				<option value="content"> 내 용</option>
   				</select>
   				<input size="16" name="keyWord">
   				<input type="button"  value="찾기" onClick="javascript:check()">
   				<input type="hidden" name="nowPage" value="1">
  			</td>
 		</tr>
	</table>
	</form>
	
	<!-- 다른 페이지로 파라미터를 넘겨주려면 아래와 같은 형식으로 작성해야 함 -->
	<!-- 게시물 검색을 하고 난 후에 [처음으로]를 클릭하면 함께 넘어가는 값들을 
    hidden 타입으로 넘기는 form이다. -->
	<form name="listFrm" method="post">
		<input type="hidden" name="reload" value="true"> 
		<input type="hidden" name="nowPage" value="1">
	</form>
	<form name="readFrm" method="get">
		<input type="hidden" name="num"> 
		<input type="hidden" name="nowPage" value="<%=nowPage%>"> 
		<input type="hidden" name="keyField" value="<%=keyField%>"> 
		<input type="hidden" name="keyWord" value="<%=keyWord%>">
	</form>
</div>
</body>
</html>

 

전체 레코드 수(int totalRecord = 0) ~ Vector<BoardBean> vlist = null;

 

: 페이지 및 블럭 처리와 검색에 필요한 검색 필드명과 검색어, 한 페이지 당 출력할 게시물의 개수 그리고 전체 게시물을 리턴 받을 Vector를 선언문에서 선언하였다. request 객체로부터 가져올 page 값과 nowBlock 값이 없기 때문에 변수 초기화 과정에서 둘 다 0이 들어가게 되어 있다. 그래서 가장 첫 페이지의 값과 가장 첫 페이지의 블럭은 0이 됨을 명심하기 바란다. 

 

if (request.getParameter("keyWord") != null) ~ keyField = request.getParameter("keyField");

 

: 검색을 위해서 호출받은 keyWord() 값이 null 값이 아니면 요청받은 keyWord 값과 keyField 값을 받아서 검색을 처리한다.

 

if (i == listSize) break; ~ int count = bean.getCount();

 

:  Vector<BoardBean> 타입으로 반환한 tblBoard 테이블 게시물을 boardList에서 get() 메소드를 이용하여 BoardBean 클래스 타입으로 반환하였다. BoardBean 객체를 getXxx 메소드로 저장되어 있는 각각의 컬럼 값으로 반환하였다.

 

 

※ 블럭 당 페이지 수란?

'JSP&Servlet' 카테고리의 다른 글

계층형 게시판(3)  (0) 2022.04.11
계층형 게시판(2)  (0) 2022.04.05
회원가입 및 로그인  (0) 2022.03.30
세션(Session)과 쿠키(Cookie)_추가 내용  (0) 2022.03.22
세션(Session)과 쿠키(Cookie)  (0) 2022.03.21