Bigfat

[Web] 페이징 처리하기 (게시판 구현) 본문

Java/web

[Web] 페이징 처리하기 (게시판 구현)

kyou 2017. 4. 7. 10:53

게시판 리스트 화면에 페이징 처리를 해보자

  첫 번째로 web.xml에 <context-param>엘리먼트를 아래와 같이 추가해주자. 서블릿 초기화 파라미터로 프로젝트 내에서 사용되는 설정 값들을 정의해놓을 수 있다. pageSize는 한 화면에 보여줄 게시글의 개수이며, pageBlock은 하단 링크 page의 개수다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>bbs</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 프로젝트에 사용되는 파라미터 -->
  <!-- application 내장 객체가 읽어들인다. -->
  <context-param>
	  <param-name>pageSize</param-name>
	  <param-value>10</param-value>
  </context-param>
  <context-param>
	  <param-name>pageBlock</param-name>
	  <param-value>10</param-value>
  </context-param>
  
  <servlet>
    <servlet-name>bbsServlet</servlet-name>
    <servlet-class>com.edu.bbs.BBSServlet</servlet-class>
    <init-param>
      <param-name>bbsProperties</param-name>
      <param-value>D:\Dev\education\bbs\WebContent\bbs.properties</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>bbsServlet</servlet-name>
    <url-pattern>*.bbs</url-pattern>
  </servlet-mapping>
</web-app>


  이제 페이징을 처리할 Page클래스를 작성하자. 알고리즘은 주석을 참고하도록 한다.

package com.edu.bbs;

public class Page {
	private static Page page = new Page();
	private int startRow, endRow;
	private StringBuffer sb;

	private Page() {}
	
	public static Page getInstance() {
		if(page == null) {
			page = new Page();
		}
		return page;
	}
	
	// pageSize:리스트 row 개수, pageBlock: 하단 페이지 개수
	public synchronized void paging(int pageNum, int totalCount, int pageSize, int pageBlock) {
		// totalPage : 총 페이지 수
		int totalPage = (int)Math.ceil((double)totalCount/pageSize);		// Math.ceil() : 올림
		// startRow, endRow : 한 페이지의 start, end row 계산
		startRow = (pageNum - 1) * pageSize + 1;
		endRow = pageNum * pageSize;
		
		// 연산자 우선순위: 단항연산자(casting 등) > 산술연산자(* 등)
		// 현재 페이지에 관한 하단부에 출력할 start, end 페이지 계산한다.
		// 예를 들면, 3페이지는 1~10페이지 속에 포함된다.
		int startPage = (int)((pageNum - 1)/pageBlock) * pageBlock + 1;
		int endPage = startPage + pageBlock - 1;
		
		// 만약 계산된 마지막 페이지가 실제 마지막 페이지보다 많다면,
		// 예를 들면, 총 77페이지까지 존재하지만 계산된 페이지는 80까지이므로 77로 변경해준다.
		if(endPage > totalPage) {
			endPage = totalPage;
		}
		
		// html코드 삽입할 StringBuffer인스턴스 생성
		sb = new StringBuffer();
		
		if(startPage < pageBlock) {		// 예를 들면, startPage=1, pageBlock=10이라면, '<'기능 필요없다.
//			sb.append("<img src='images/hot.gif' width='30' height='9'>");
		} else {		// 예를 들면, startPage=11, pageBlock=10이라면, '<'기능 필요.
//			sb.append("<img src='images/hot.gif' width='30' height='9' ");
			sb.append("<span width='30' height='9' ");
			sb.append("onclick='location.href=\"list.bbs?pageNum=");
			sb.append(startPage - pageBlock);		// 예를 들면, startPage-pageBlock=11-10=1 이라면, 1페이지로 돌아감
			sb.append("\"' style='cursor:pointer'>&lt;</span>");
			sb.append("&nbsp;&nbsp;|&nbsp;&nbsp;");
		}
		
		// pageBlock에 포함되는 페이지 숫자 출력하고, 링크를 달아준다.
		for(int i = startPage; i <= endPage; i++) {
			if(i == pageNum) {
				sb.append("&nbsp;&nbsp;<b><font color='#91B7EF'>");
				sb.append(i);
				sb.append("</font></b>");
			} else {
				sb.append("&nbsp;&nbsp;<a href='list.bbs?pageNum=");
				sb.append(i);
				sb.append("'>");
				sb.append(i);
				sb.append("</a>");
			}
		}
		
		// '>'기능 구현으로, '<'기능과 반대로 구현한다.
		if(endPage < totalPage) {
			sb.append("&nbsp;&nbsp;|&nbsp;&nbsp;");
//			sb.append("<img src='images/hot.gif' width='30' height='9' ");
			sb.append("<span width='30' height='9' ");
			sb.append("onclick='location.href=\"list.bbs?pageNum=");
			sb.append(startPage + pageBlock);
			sb.append("\"' style='cursor:pointer'>&gt;</span>");
		} else {
//			sb.append("<img src='images/hot.gif' width='30' height='9'>");
		}
	}
	
	public StringBuffer getSb() {
		return sb;
	}

	public int getStartRow() {
		return startRow;
	}

	public int getEndRow() {
		return endRow;
	}
}


  ListImpl클래스에 아래와 같은 코드를 추가한다. Page클래스의 인스턴스를 얻고, paging메서드로 페이징 처리한 후 만들어진 문자열을 pageCode에 심는다.

package com.edu.bbs;

import java.util.ArrayList;

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

public class ListImpl implements BBSService {

	@Override
	public String bbsService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
		String pageNum = req.getParameter("pageNum");
		
		// web.xml에 선언한 context-param에서 값을 읽어온다.
		int pageSize = Integer.parseInt(req.getServletContext().getInitParameter("pageSize"));
		int pageBlock = Integer.parseInt(req.getServletContext().getInitParameter("pageBlock"));
		
		int totalCount = 0;
		ArrayList<BBSDto> articles = null;
		Page page = Page.getInstance();
		
		try {
			totalCount = BBSOracleDao.getInstance().getArticleTotalCount();
//			articles = BBSOracleDao.getInstance().selectArticles(1, 10);
			page.paging(Integer.parseInt(pageNum), totalCount, pageSize, pageBlock);
			articles = BBSOracleDao.getInstance().selectArticles(page.getStartRow(), page.getEndRow());
		} catch (Exception e) {
			e.printStackTrace();
		}

		req.setAttribute("totalCount", totalCount);
		req.setAttribute("articles", articles);
		
		req.setAttribute("pageNum", pageNum);
		req.setAttribute("pageCode", page.getSb().toString());
		
		return "/list.jsp";
	}

}

  list.jsp를 아래와 같이 수정하자. ${pageCode}은 Page클래스에서 만든 문자열인 HTML 코드를 받아오게 된다. '글쓰기'버튼의 링크는 주석 제거하지 않아도 될 것 같다. 새로운 글을 쓰면 첫 번째 페이지로 돌아오도록 해놨기 때문에 쿼리스트링으로 pageNum을 던져주지 않아도 되기 때문이다. 왜 pageNum을 던져줘야 하는 지 소스를 수정하다가 깨닫게 되었다. 만약 7번째 페이지에서 '글쓰기' 링크를 눌렀다가 글을 쓰지 않고 목록으로 돌아가게 된다면 이전에 있던 페이지 번호(pageNum)을 알고 있어야 하기 때문이다(17. 04. 11. 수정).

  ListImpl 클래스에서 request attribute의 name으로 "articles"라고 심었기 때문에 list.jsp에서 ${articles}로 받는 것을 수정하였습니다. 기존 코드에서는 ${articleList}로 되어있었습니다(17.05.26 수정).

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>BBS List</title>
</head>
<body>
	<div align="right">
		<!-- Login 검증 -->
		<!-- jstl if문은 else가 없어서 따로 검증해야함. -->
		<c:if test="${id != null}">
			<%@include file="loginOk.jsp" %>
		</c:if>
		<c:if test="${id == null}">
			<%@include file="login.jsp" %>
		</c:if>
	</div>

	<div align="center">
		<b>글목록(전체 글:${totalCount})</b>
		<table width="700">
		  <tr>
		    <td align="right" >
		       <a href="/bbs/writeForm.bbs?pageNum=${pageNum}">글쓰기</a>
		    </td>
		  </tr>
		</table>
	
		<table border="2" width="700">
			<tr>
				<th width="50">번호</th>
				<th width="250">제목</th>
				<th width="100">작성자</th>
				<th width="150">작성일</th>
				<th width="50">조회</th>
			</tr>
			<c:forEach var="article" items="${articles}" varStatus="status">
				<tr align="center" height="30">
					<td>${article.articleNumber}</td>
					<td align="left">
						<c:if test="${article.depth > 0}">
							<img src="" width="${10 * article.depth}" height="16">
							<img src="">
						</c:if>
						<c:if test="${article.depth == 0}">
							<img src="" width="0" height="16">
						</c:if>
						<!-- URL query의 파라미터들은 request에 자동으로 심어지는 듯 하다. -->
						<a href="/bbs/content.bbs?articleNum=${article.articleNumber}&pageNum=${pageNum}">${article.title}</a>
						<c:if test="${article.hit >= 20}">
							<span class="hit">hit!</span>
							<!-- <img src="" border="0" height="16"> -->
						</c:if>
					</td>
					<td>${article.id}</td>
					<td>${article.writeDate}</td>
					<td>${article.hit}</td>
				<tr>
			</c:forEach>
			
			<tr>	  
				<td colspan="5" align="center" height="40">	 
					${pageCode}
				</td>
			</tr>
		</table>
	</div>
</body>
</html>


  WriteFormImpl클래스를 아래와 같이 수정하자(2017. 4. 11. 추가)

package com.pknu.bbs;

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

public class WriteFormImpl implements BBSService {

	@Override
	public String bbs(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
		// 로그인 유무를 확인
		HttpSession session = req.getSession();
		session.setAttribute("id", "kim");		// 임의로 세션에 id를 심는다.
//		if(session.getAttribute("id") == null) {
//			return "/login.jsp";
//		}
		
		req.setAttribute("pageNum", req.getParameter("pageNum"));
		req.setAttribute("depth", req.getParameter("depth"));
		req.setAttribute("pos", req.getParameter("pos"));
		req.setAttribute("groupId", req.getParameter("pos"));
		
		return "/writeForm.jsp";
	}

}


  WriteImpl클래스의 sendRedirect메서드의 파라미터를 /bbs/list.bbs?pageNum=1로 변경하자.

package com.edu.bbs;

import java.io.IOException;

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

public class WriteImpl 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"));
		
		try {
			if(BBSOracleDao.getInstance().insertArticle(article) == 1) {
				System.out.println("게시글이 삽입되었습니다.");
			} else {
				System.out.println("게시글 삽입이 실패하였습니다.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

//		resp.sendRedirect("/bbs/writeForm.bbs");
//		resp.sendRedirect("/bbs/list.bbs");
		resp.sendRedirect("/bbs/list.bbs?pageNum=1");
		
		return null;
	}

}


  톰캣을 구동한 후 http://localhost/bbs/list.bbs?pageNum=1로 접속하면 아래와 같은 화면을 확인할 수 있다. 이제 기존 url인 http://localhost/bbs/list.bbs에서 pageNum=1이라는 쿼리스트링을 붙여서 접근해야 한다. pageBlock이 잘 계산되었는 지 확인하기 위해 임의의 데이터를 100개 이상 넣어줬다.


  아래는 다음 페이지 블럭으로 넘어갔을 때의 화면이다.


  다음 글에서는 작성된 게시글을 확인하고, 수정, 삭제 기능을 추가할 계획이다.