본문 바로가기
다양한 기술들/레거시 Web 실습

[08] 게시판심화1 : 페이징처리

by 예스p 2023. 1. 11.

페이징처리

샘플데이터를 다량으로 만들어서

하단에 페이징 처리를 해보자


 

<< 이전포스팅에서 완료된 요청     |    이번포스팅에서 구현할 요청 >>

기능 url요청 요청시 전달값   응답페이지 또는 url재요청
  /web     index.jsp
로그인요청 /login.me userId=?&userPwd=? 실패시 views/common/errorPage.jsp
성공시 /web  url재요청=> index.jsp
로그아웃요청 /logout.me     /web  url재요청=> index.jsp
회원가입페이지 /enrollForm.me     views/member/memberEnrollForm.jsp
회원가입요청 /insert.me userId=? ... 등  실패시 views/common/errorPage.jsp
성공시 /web  url재요청 => index.jsp
마이페이지요청 /myPage.me   로그인전 /web   url재요청 => index.jsp
로그인후 views.member/myPage.jsp
 정보변경요청

/update.me 

userId=? ... 등 실패시 view/common/errorPage.jsp
성공시 /myPage.me   url재요청 
공지사항 목록 /list.no     views/notice/noticeListView.jsp
공지사항글쓰기 /enrollForm.no     views/noticeEnrollForm.jsp
공지사항등록 /insert.no title=?&content=? 실패시 views/common/errorPage.jsp
성공시 /list.no   url재요청
공지 - 상세페이지 /detail.no no=? (수기기재) 실패시 views/common/errorPage.jsp
성공시 views/notice/noticeDetailView.jsp
공지 - 수정페이지 /updateForm.no no=?   views/notice/noticeUpdateForm.jsp
공지 - 수정요청 /update.no no=?&title=?&content=? 실패시 views/common/errorPage.jsp
성공시 /detail.no?no=XX   url재요청
일반게시판 목록 /list.bo cpage=?   views/board/boardListView.jsp

 


준비하기

데이터를 만들어 db에 삽입하고,

페이징을 위한 일반게시판을 만들어본다.


STEP1) 다량의 데이터(엑셀) INSERT하기

<<엑셀 데이터 정리 형태>>

  • 엑셀의 열은 테이블의 칼럼을 순서대로 쓴다
    데이터값으로 SEQUENCE 발생 구문을 넣을 수 있다.
  • sqldeveloper 데이터삽입 :
    테이블 오른클릭 - [데이터임포트] - [찾아보기] - 파일 경로 지정후 열기
    >> [헤더]를 체크해서 첫번째 행(컬럼명 쓴 행)은 제외
    >> 워크시트가 여러개라면 해당되는것으로 지정
    >> [다음] - [임포트방식] : 삽입
    >> [다음] - [다음]
        - 소스데이터열을 클릭해서 데이터가 잘 들어갔는지 확인
           형식이 다른경우 수정하기(Date타입 등)
        - SEQUENCE 쓴 칼럼이 넘버타입이라고 경고가 되어있을 것.
           (무시해도됨)
    >> 완료 누르면 삽입실패 팝업 - 경고무시에 [예] 
    >> 자동 삽입이 안되고 스크립트 구문이 뜸
    >> 스크립트 실행 및 commit
  • 공공데이터 로 다량의 데이터 얻기 : 
    https://www.data.go.kr/ 
    >> 정부에서 제공하는 무료데이터
        검색했을 때 나오는 CSV가 엑셀파일이다.

 

 

STEP2) Board.java

  • 생성위치 : com.br.board.model.vo
  • 게시글을 저장할 수 있는 vo클래스를 작성한다.
  • 필드의 타입은 보통 db와 일치시키지만, 숫자와 문자열 둘다 활용할 경우 String으로 지정한다.
    - 작성기능시에는 번호 사용, 조회시에는 명칭을 사용하는 경우
    - 이번 예제에서는 category와 boardWriter
  • 오라클의 Date타입에 매칭되는 필드는 Date타입이 아닌 String으로도 지정할 수 있다.
    (이때, 조회 시 to_char 함수를 사용해서 문자열로 변환후 가져올수있다.) 

 

 

STEP3) Category.java

  • 생성위치 : com.br.board.model.vo
  • 카테고리를 저장할 수 있는 vo클래스를 작성한다.

 

 

STEP4) menubar.jsp(수정)

  • 메뉴바의 일반게시판 버튼 클릭시 목록으로 가게끔 href를 작성한다.
    처리할 서블릿의 매핑값 list.bo
  • 요청하는 페이지에 대한 정보 cpage (current page)에 담아 쿼리스트링을 보낸다.
    >> 메뉴바에서 버튼클릭시에는 cpage=1로 설정한다.
<div class="menu"><a href="<%=contextPath%>/list.bo?cpage=1">일반게시판</a></div>

 

 


서블릿 : 페이징처리에 필요한 변수들 설정하기

페이징에 필요한 변수들을 알아보고,

해당 변수들을 담아 이동시킬 수 있는 vo객체를 만들어

다른게시판의 페이징처리에도 활용한다.


STEP1 ) PageInfo.java

  • 생성위치 : com.br.common.model.vo
  • 페이징 처리할때마다 데이터들을 담아둘 객체가 필요할때가 있다.
    >> 이때 사용할 용도로 vo클래스 생성
  • 페이징용 필드 :
    1) 값을 마음대로 정하는 정하는 필드
    int listCount  :   현재 게시글 총 갯수
    int currentPage  :  사용자가 요청한 페이지(== 현재페이지)
    int pageLimit  :  페이지 하단에 보여질 페이징바의 페이지 최대갯수(몇개 단위씩 보여지게 할건지)
    int boardLimit  :  한 페이지에 보여질 게시글의 최대갯수(몇개 단위씩 보여지게 할건지)
    2) 상위 4개를 연산하여 구하는 필드
    int maxPage  :  가장 마지막 페이지 번호 (총 페이지 수)
    int startPage  :  사용자가 요청한 페이지로 갔을때, 하단의 페이징바의 시작수
    int endPage  :  사용자가 요청한 페이지로 갔을때, 하단의 페이징바의 끝수

 

 

STEP2) BoardListController.java

  • 생성위치 : com.br.board.controller
    서블릿 / 매핑값 list.bo
  • 일반게시판 목록을 요청할때 db에서 필요한 데이터를 가져와 포워딩하는 서블릿
    이때, cpage라는 키값으로 현재페이지 정보 넘어와야함
  • listCount :
    db에서 count(*)로 총 게시글 갯수를 구한다.
  • currentPage :
    해당 서블릿 요청시에 쿼리스트링으로 함께 넘어온 값(cpage)을 넣는다.
    사용자가 요청한 페이지(== 현재페이지)
  • pageLimit :
    페이징바에 몇개의 페이지씩 표시할건지 정해서 값을 넣는다.
    현재 페이지 갯수가 최대값보다 적을시 더 적게 표현된다.
  • boardLimit
    한 페이지당 몇개의 게시글을 표시할건지 갯수를 정한다.
  • 나머지 변수는 이어서..
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // * listCount : 총 게시글 갯수
    listCount = new BoardService().selectListCount();
    // * currentPage : 사용자가 요청한 페이지 (현재 페이지)
    currentPage = Integer.parseInt(request.getParameter("cpage"));
    // * pageLimit : 페이징바의 페이지 최대 갯수
    pageLimit = 10;
    // * boardLimit : 한 페이지당 보여질 게시글의 최대 갯수
    boardLimit = 10;
.....(계속)
}

 

STEP3) board-mapper.xml

  • 총 게시글 갯수를 구할 sql문을 작성해놓는다.
	<entry key="selectListCount">
		SELECT
		       COUNT(*) count
		  FROM BOARD    
		 WHERE BOARD_TYPE=1
		   AND STATUS='Y'
	</entry>

 

 

STEP4) BoardService.java

  • 생성위치 : com.br.board.model.service
  • 총 게시글 수를 반환할 selectListCount() 메소드 생성
	public int selectListCount() {
		Connection conn = getConnection();
		int listCount = new BoardDao().selectListCount(conn);
		close(conn);
		return listCount;		
	}

 

STEP5) BoardDao.java

  • 생성위치 : com.br.board.model.dao
  • 필드로 Properties 객체 만들고 기본생성자에서 board-mapper.xml 연결시켜놓기
  • selectListCount(conn) 메소드 생성
  • 데이터를 꺼낼때는 db에서 조회되는 칼럼명을 그대로 사용해야하는데,
    컬럼명이 함수식(ex : COUNT(*) )인 경우 까다로워져서 별칭을 부여하곤 함.
public int selectListCount(Connection conn) {
    //select => ResultSet(한행)=> 값 1개로 반환되므로 vo객체에 담을게 아니라 int에 담기
    int listCount = 0;
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    String sql = prop.getProperty("selectListCount");
    try {
        pstmt=conn.prepareStatement(sql);
        rset = pstmt.executeQuery();
        if(rset.next()) {
            listCount = rset.getInt("count");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(rset);
        close(pstmt);
    }
    return listCount;
}

 

 

STEP6) BoardListController.java (수정)

  • 나머지 변수들 3개의 값 구하기
  • maxPage
    - 제일 마지막 페이지 수
    >> 총 게시글(listCount)과 한페이지에 나오는 페이지수(boardLimit)에 따라 마지막 페이지 번호가 결정된다.
    >>예) 총 게시글이 100개라면 페이지수는 총10개, 105개라면 11개
    >>공식 : (총게시글 / 한페이지당게시글)을 실수값으로 구한 뒤 올림처리
      실수값으로 연산하기 위해 하나만 실수형태로 바꾸면 된다.(나머지값은 자동형변환)
       Math.ceil() 올림처리한 값을 실수로 반환하므로 다시 int 강제형변환
maxPage = (int)Math.ceil((double)listCount/boardLimit);

 

  • startPage
    - 페이징바의 시작수
    >> 한번에 보여지는 페이징바의 최대갯수(pageLimit)와 사용자가 요청한 현재페이지(currentPage)에 따라 번호가 결정된다
    >> 예) 페이징바가 10개씩 보인다면 startPage는 1, 11, 21, 31, ...
               20개씩 보인다면 startPage는 1, 21, 41, 61, ...
    >>공식 : startPage = n*pageLimit + 1
    **n은 사용자의 요청값(currentPage==cpage)과 관련된 숫자로 요청값이 1~10 이면 n은 0, 11~20이면 1여야함.(페이지리밋은 10일때)
    >>이때, n은 나눗셈의 몫(나머지제외)으로 구할 수 있다.  n = (currentPage-1)/pageLimit
startPage = (currentPage-1)/pageLimit * pageLimit+1;

 

  • endPage
    - 사용자 요청페이지에서의 페이징바 끝수
    >>startPage가 1이고 pageLimit이 10이라면 페이징바의 끝수는 11
    단, endPage가 maxPage보다 작을경우 maxPage가 endPage가 됨.(페이징바의 끝부분)
endPage = startPage + pageLimit -1;
if(endPage>maxPage) {endPage = maxPage;}

 

 


포워딩 및 페이징바 구현

서블릿에서 포워딩을 마치고

목록 페이지 화면구현에서 페이징바를 표현시켜보자


 

STEP1) board-mapper.xml (수정)

  • 현재 요청한 페이지(currentPage)에 보여질 게시글 리스트 boardLimit수 만큼 조회하면 된다.
  • date타입을 vo때 계획했던대로 TO_CHAR()로 변경해준다.(함수사용 후 별칭필수)
  • ROWNUM 이 1부터 체크하지 않으면 조회 안되는 케이스발생
    >> ROWNUM은 데이터들을 걸러낸 후 '1부터' 부여되기 때문에 WHERE절에 1을 제외하면 조회되는 칼럼이 없어진다.(구글 키워드 'rownum=2 is not working')
    >> 이때는 별칭을 부여한 후 인라인뷰로 사용하면 된다!
SELECT *
  FROM (
        SELECT ROWNUM RNUM, A.*
          FROM (
                SELECT
                       BOARD_NO
                     , CATEGORY_NAME
                     , BOARD_TITLE
                     , USER_ID
                     , COUNT
                     , TO_CHAR(CREATE_DATE, 'YYYY/MM/DD') "CREATE_DATE"
                 FROM BOARD B
                 JOIN CATEGORY USING (CATEGORY_NO)
                 JOIN MEMBER ON (BOARD_WRITER=USER_NO)
                WHERE BOARD_TYPE=1
                  AND B.STATUS='Y'
                ORDER 
                   BY BOARD_NO DESC
                ) A
       )
WHERE RNUM BETWEEN ? AND ?

 

 

 

STEP2) BoardListController.java (수정)

  • 만들어둔 PageInfo vo클래스로 페이지 데이터 담은 후 요청처리한 후
  • 포워딩하기
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    .....(이전내용 : 변수들 값 구하기)
    PageInfo pi = new PageInfo(listCount, currentPage, pageLimit, boardLimit, maxPage, startPage, endPage);

    // 현재 요청한 페이지(currentPage)에 보여질 게시글 리스트 boardLimit수 만큼 조회
    ArrayList<Board> list = new BoardService().selectList(pi);

    request.setAttribute("pi", pi);
    request.setAttribute("list", list);
		
    //응답페이지 (views/board/boardListView.jsp)
    request.getRequestDispatcher("views/board/boardListView.jsp").forward(request, response);
}

 

 

STEP3) BoardService.java(수정)

  • 필요한 게시글들 조회해오는 selectList(PageInfo pi) 메소드 생성
	public ArrayList<Board> selectList(PageInfo pi) {
		Connection conn = getConnection();
		ArrayList<Board> list = new BoardDao().selectList(conn,pi);
		return list;
	}

 

STEP4) BoardDao.java(수정)

  • selectList(conn,pi)메소드 생성
  • sql을 완성하기 위해 필요한 변수
    startRow
    :
    가져올 게시글의 첫번째 글
    (currentPage - 1)*boardLimit + 1
    endRow :
    가져올 게시글의 마지막 글
    startRow + boardLimit + 1
  • ResultSet 다수를 받을 것이므로 ArrayList<Board>로 담는다.
public ArrayList<Board> selectList(Connection conn, PageInfo pi) {
    ArrayList<Board> list = new ArrayList<>();
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    String sql = prop.getProperty("selectList");
    try {
        pstmt = conn.prepareStatement(sql);
        /*
        * boardLimit이 10일때, 보이는 게시글의 rownom 은
        * currentPage : 1 => 시작값 1, 끝값 10
        * currentPage : 2   => 시작값 11, 끝값 20
        */
        int startRow = (pi.getCurrentPage()-1)*pi.getBoardLimit() + 1;
        int endRow = startRow + pi.getBoardLimit() -1;
        pstmt.setInt(1, startRow);
        pstmt.setInt(2, endRow);
        rset = pstmt.executeQuery();
        while(rset.next()) {
            list.add(new Board(rset.getInt("board_no")
                             , rset.getString("category_name")
                             , rset.getString("board_title")
                             , rset.getString("user_id")
                             , rset.getInt("count")
                             , rset.getString("create_date")));
			}
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(rset);
        close(pstmt);
    }
    return list;
}

 

 

STEP5) boardListView.jsp

  • 생성위치 : webProject/src/main/webapp/views/board
  • 상단에 전달받은 데이터들 꺼내놓기
  • 사용자가 요청한 페이지에 따라 목록도 달라지 페이징바도 달라지는것 구현
    >> 이때 첫페이지에는 이전버튼(<)이 안보이고 마지막페이지에서는 다음버튼(>)이 안보이도록 한다.
.....
<%
    PageInfo pi = (PageInfo)request.getAttribute("pi");
    <Board> list = (ArrayList<Board>)request.getAttribute("list");
%>
.....
<tr>
    <th width="70">글번호</th>
    <th width="80">카테고리</th>
    <th width="300">제목</th>
    <th width="100">작성자</th>
    <th width="50">조회수</th>
    <th width="100">작성일</th>
</tr>

<% if(list.isEmpty()) { %>
<!-- case1. 게시글이 없을 경우 -->
    <tr>
        <td colspan="6">조회된 게시글이 없습니다.</td>
    </tr>
<%} else { %>
<!-- case2. 게시글이 있을 경우 -->
    <% for(Board b : list) { %>
        <tr>
            <td><%=b.getBoardNo() %></td>
            <td><%=b.getCategory() %></td>
            <td><%=b.getBoardTitle() %></td>
            <td><%=b.getBoardWriter() %></td>
            <td><%=b.getCount() %></td>
            <td><%=b.getCreateDate() %></td>
        tr>
    <%} %>
<%} %>
.....
<!-- 페이징바 영역 -->
<% if(pi.getCurrentPage()>1){ %>
    <button onclick="location.href=<%=contextPath%>/list.bo?cpage=<%=pi.getCurrentPage()-1%>">&lt;</button>
<%} %>
<% for(int p=pi.getStartPage(); p<=pi.getEndPage(); p++ ) {%>
    <button onclick="location.href='<%=contextPath%>/list.bo?cpage=<%=p%>';"><%=p %></button>
<%} %>
<% if(pi.getCurrentPage() != pi.getMaxPage()) {%>
    <button onclick="location.hred=<%=contextPath%>/list.bo?cpage=<%=pi.getCurrentPage()+1%>">&gt;</button>
<%} %>

 

 

 

 

 

 

댓글