감 잃지말고 개발하기

[JSP][MVC][MySQL] Select 박스 옵션에 따른 페이징 처리 구현하기 #2. 로직 구현 본문

JSP/MVC

[JSP][MVC][MySQL] Select 박스 옵션에 따른 페이징 처리 구현하기 #2. 로직 구현

persii 2023. 5. 27. 03:38

도서 목록 페이지에서 <Select> 박스 옵션에 따른 페이징 처리 로직을 기록하고자 한다.

 

1탄에서 기본적인 설정을 끝냈으니, 이번 포스팅에서는 본격적인 로직을 정리해 보도록 하겠다.

 


♠ 해당 구현의 로직 흐름이 궁금하면 아래 포스팅을 참고하세요 ♠

 

2023.05.27 - [JSP/MVC] - [JSP][MVC][MySQL] Select 박스 옵션에 따른 페이징 처리 구현하기 #1. 기본 설정

 

[JSP][MVC][MySQL] Select 박스 옵션에 따른 페이징 처리 구현하기 #1. 기본 설정

도서 목록 페이지에서 박스 옵션에 따른 페이징 처리 로직을 총 2개의 포스팅에 걸쳐 기록하고자 한다. 이번 포스팅에서는 구현 흐름과 필요한 테이블 및 클래스를 정리해 보도록 하겠다. 목표

persimmon-ary-stepbystep.tistory.com


 

목표

♠ 페이징 처리를 구현할 수 있다.

 

 

로직 코드 및 실행화면

1. 도서 목록 페이지 요청

페이지 상단 내비게이션 바 HTML 일부분이다.

<li>최신 도서</li>를 클릭하면 도서 목록 페이지를 요청한다.

 

<ul class="nav navbar-nav menu_nav ml-auto">
    <li class="nav-item active">
        <a class="nav-link" href="${request.getContextPath() }/bookShopMain.ok">홈</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="${request.getContextPath() }/bookList.ok">최신 도서</a>
    </li>
</ul>

 

2. 컨트롤러(BookFrontController.java)

*.ok 로 들어오는 URL은 해당 컨트롤러에서 처리한다.

/bookList.ok로 요청되면 BookListAction 클래스를 호출한다.

 

package controller;

import java.io.IOException;
import java.rmi.ServerException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import action.Action;
import action.BookCartListAction;
import vo.ActionForward;

@WebServlet("*.ok")
public class BookFrontController extends HttpServlet{
	
	protected void doProcess(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		req.setCharacterEncoding("UTF-8");
		
		/* 1. 요청 주소 파악 */	
		String requestURI = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		/* 2. 각 요청 주소의 매핑 처리 */
		ActionForward forward = null;
		Action action = null;
		
		// 도서 목록 페이지 요청
		if(command.equals("/bookList.ok")) {
			action = new BookListAction();
			try {
				forward = action.execute(req, resp);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		/* 3. 포워딩 처리 */
		if(forward != null) {
			if(forward.isRedirect()) {
				// redirect 방식
				resp.sendRedirect(forward.getPath());
			} else {
				// forward 방식
				RequestDispatcher dispatcher = req.getRequestDispatcher(forward.getPath());
				dispatcher.forward(req, resp);
			}
		}
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}
}

 

3. Action 클래스(BookListAction.java)

☞ int page = 1;

위의 1번에 따라 도서 목록 페이지의 최초 요청 URL의 모습은 http://localhost:.../bookList.ok이지만,

페이징 처리가 들어간 URL은 http://localhost:.../bookList.ok?page=2와 같은 모습을 가진다.

파라미터 page에는 해당 목록 화면의 페이지 번호가 저장되는데, 이 값을 저장할 변수 page를 초기화해 준다.

 

☞ service.getListCount(); 

DB에서 도서 총개수를 가져온다.

 

☞ service.getBookList(page, limit);

도서 목록 화면에서 page번에 출력될 도서를 limit개만 가져온다.

만약 page가 3이면, 3번째 페이지에서 출력될 도서를 9개만 가져오게 된다.

 

☞ listCount / limit + (listCount % limit == 0 ? 0 : 1);

필요한 총 페이지 수를 계산한다.

만약 도서 총 개수(listCount)가 82개이고 한 화면에 출력될 도서 개수(limit)가 9개일 때, 필요한 페이지 수는 10개이다.

즉, 나머지가 생겼을 때 이를 올림처리해서 페이지 수를 계산한다.

 

if(endPage > maxPage) endPage = maxPage;

계산된 endPage 값을 존재하는 페이지의 마지막 페이지 번호(maxPage)로 지정하는 부분이다.

 

예를 들어, 글이 72개가 있고 한 화면에 출력될 도서 개수가 9개일 때,

필요한 페이지 수(maxPage)는 8개인데 마지막 페이지 번호(endPage)는 10이 되게 된다. 

이에 대한 처리를 위해 if문을 넣어 만약 마지막 페이지 번호가 필요 페이지 수보다 많을 때 마지막 페이지 번호를 필요 페이지 번호로 설정해 준다.

 

☞ req.setAttribute("pageInfo", pageInfo);

페이징 처리에 대한 정보를 저장한 pageInfo 객체를 속성으로 공유한다.

bookList.jsp 페이지에서 해당 정보를 가져다 사용할 것이다.

 

package action;

import java.util.ArrayList;

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

import svc.BookListService;
import vo.ActionForward;
import vo.Book;
import vo.PageInfo;

/** 책 상품 목록보기 요청을 처리하는 Action 클래스*/
public class BookListAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		
                /* 필요 변수 초기화 */
		int page = 1;	
        	int limit = 9;

		/* URL 파라미터 설정 */		
		if(req.getParameter("page") != null) {
			page = Integer.parseInt(req.getParameter("page"));
		}
		
		/* DB 처리 
		 * 1. DB에 저장된 총 책 개수 GET
		 * 2. 지정한 페이지에 출력될 책 목록을 GET */
		BookListService service = new BookListService();
		int listCount = service.getListCount(); 
		ArrayList<Book> bookList = service.getBookList(page, limit);
        
                /* 페이징 처리 
                 * 1. 총 페이지 수(나머지가 있는 경우에만 올림처리) 계산
                 * 2. 페이징 부분에 출력되는 페이지 번호 중 첫 번째 페이지 번호(1, 11, 21 등) 계산
                 * 3. 페이징 부분에 출력되는 페이지 번호 중 마지막 페이지 번호(10, 20, 30 등) 계산 */
		int maxPage = listCount / limit + (listCount % limit == 0 ? 0 : 1);
		int startPage = (((int)((double)page / 10 + 0.9)) - 1) * 10 + 1;
		int endPage = startPage + 10 - 1;
		
		if(endPage > maxPage) endPage = maxPage;
		
		/* 페이징에 관한 정보를 저장할 PageInfo 객체 생성 */		
		PageInfo pageInfo = new PageInfo();
		pageInfo.setEndPage(endPage);
		pageInfo.setListCount(listCount);
		pageInfo.setMaxPage(maxPage);
		pageInfo.setPage(page);
		pageInfo.setStartPage(startPage);
		
		/* 포워딩할 때 가져갈 정보 저장 
		 * 1. 책 상품 목록 정보를 속성으로 공유 
		 * 2. pageInfo 객체를 request 영역에 속성 값으로 공유
		 * .ok -> .jsp : Forward
		 * */
		req.setAttribute("bookList", bookList);
		req.setAttribute("pageInfo", pageInfo);
		
		ActionForward forward = new ActionForward();
		forward.setPath("/book/bookList.jsp");
		forward.setRedirect(false);
		return forward;
	}
}

 

4. Service 클래스(BookListService.java)

☞ getListCount();

해당 메서드는 DB에 저장된 총 책 개수(count(*))를 구해 리턴한다.

 

☞ getBookList(int page, int limit);

해당 메서드는 두 개의 인자를 가지고 페이지에서 출력될 책 상품 목록을 ArrayList 객체 타입으로 리턴한다.

 

package svc;

import java.sql.Connection;
import java.util.ArrayList;

import dao.BookDAO;
import vo.Book;

import static db.JdbcUtil.*;

/** 책 상품 목록보기 요청을 처리하는 비즈니스 로직을 구현하는 Service 클래스*/
public class BookListService {

	/** DB에 저장된 총 책 개수(count(*))를 구하는 메서드 */
	public int getListCount() {

		int listCount = 0;
		
		/* DB 처리 */
		Connection conn = getConnection();
		BookDAO boardDAO = BookDAO.getInstance();
		boardDAO.setConnection(conn);
		listCount = boardDAO.selectListCount();
		close(conn);

		return listCount;
	}
	
	/** 파라미터로 넘어온 페이지에서 출력될 책 상품 목록을 ArrayList 객체 타입으로 반환하는 메서드 */
	public ArrayList<Book> getBookList(int page, int limit) throws Exception{
    
		/* DB 처리 */
		BookDAO bookDAO = BookDAO.getInstance();
		Connection conn = getConnection();
		bookDAO.setConnection(conn);
		ArrayList<Book> bookList = bookDAO.selectBookList(page, limit);
		close(conn);

		return bookList;
	}
	
}

 

5. DAO 클래스(BookDAO.java)

☞ SELECT *

     FROM jspbookshop.book

     ORDER BY b_publish_date DESC

     LIMIT ?, ?

해당 쿼리문은 가장 최신에 출판된 도서부터 정렬하여,

읽기 시작할 레코드 인덱스를 첫 번째 ?에, 조회할 레코드 수를 두 번째 ?로 매핑해 해당 데이터를 가져온다.

 

☞ int startrow = (page-1)*limit;

읽기 시작할 row(해당 페이지에서 출력되어야 하는 시작 레코드의 인덱스) 번호를 설정하는 변수이다.

만약 현재 페이지가 1이고 limit가 9라면, 읽기 시작해야 할 인덱스 번호는 0이다.
만약 현재 페이지가 2라면, 읽기 시작해야 할 인덱스 번호는 9이다. 

 

package dao;

import static db.JdbcUtil.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

import vo.Book;

/** MySQL DB로 SQL구문을 전송하는 클래스*/
public class BookDAO {

	Connection conn;
	private static BookDAO bookDAO;
	
	private BookDAO() {
		// TODO Auto-generated constructor stub
	}

	public static BookDAO getInstance() {
		if(bookDAO == null) {
			bookDAO = new BookDAO();
		}
		return bookDAO;
	}
	
	/** BookDAO 객체에 Connection 객체를 주입하는 메서드 */
	public void setConnection(Connection conn) {
		this.conn = conn;
	}
	
    	/** 전체 책 개수 구하는 메서드 */
	public int selectListCount() {
		
		int listCount = 0;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		String sql = "SELECT COUNT(*) FROM jspbookshop.book";

		try {
			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
				listCount = rs.getInt(1);
			}
		} catch (Exception e) {
			System.out.println(" selectListCount ERROR : "+e);
		} finally {
			close(rs);
			close(pstmt);
		}

		return listCount;
	}
    
	/** 해당 페이지에 출력될 책 상품 목록을 반환하는 메서드 */
	public ArrayList<Book> selectBookList(int page, int limit) {

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		ArrayList<Book> bookList = null;
		String book_sql = "SELECT * "
					+ "FROM jspbookshop.book "
					+ "ORDER BY b_publish_date DESC "
					+ "LIMIT ?, ? ";

		int startrow = (page-1)*limit;	// 읽기 시작할 row 번호(해당 페이지에서 출력되어야 하는 시작 레코드의 인덱스 번호)

		try {
			pstmt = conn.prepareStatement(book_sql);
			pstmt.setInt(1, startrow);
			pstmt.setInt(2, limit);
			rs = pstmt.executeQuery();
			
			if (rs.next()) {
				bookList = new ArrayList<>();
				
				do {
					bookList.add(new Book(
							rs.getInt("b_id"),
							rs.getString("b_name"),
							rs.getString("b_writer"),
							rs.getString("b_translator"),
							rs.getString("b_publisher"),
							rs.getString("b_catgy"),
							rs.getInt("b_price"),
							rs.getString("b_image"),
							rs.getInt("b_page"),
							rs.getString("b_publish_date"),
							rs.getString("b_content"),
							rs.getInt("b_readcount")));
				} while (rs.next());
			}
		} catch (Exception e) {
			System.out.println(" B.DAO : selectBookList ERROR : "+e);
		} finally {
			close(rs);
			close(pstmt);
		}

		return bookList;
	}


}

 

6. bookList.jsp

아래 페이징 처리 HTML 코드를 추가한다.

 

  • 필요한 페이지 수가 10개 이상이면 왼쪽 방향 화살표가 나타나도록 설정한다.
  • 필요한 페이지 수가 마지막 페이지 번호(10, 20, 30, ...)보다 크면 오른쪽 방향 화살표가 나타나도록 설정한다.
  • 해당 페이지로 넘어가는 <a> 태그의 href 속성 값으로 파라미터 page를 추가한다.

 

<c:if test="${pageInfo.listCount != null }">
    <div class="pagination">

        <!-- 이전 화살표 -->							
        <c:if test="${pageInfo.page > 10 }">
            <a href="/bookList.ok?page=${pageInfo.page-1 }" class="prev-arrow">
            	<i class="fa fa-long-arrow-left pt-3" aria-hidden="true"></i>
            </a>
        </c:if>

        <c:forEach var="a" end="${pageInfo.endPage }" step="1" begin="${pageInfo.startPage }">
            <c:if test="${a == pageInfo.page}">
                <a>${a }</a>
            </c:if>
            <c:if test="${a != pageInfo.page}">
                <a href="/bookList.ok?page=${a }">${a }</a>
            </c:if>
        </c:forEach>

        <!-- 다음 화살표 -->		
        <c:if test="${pageInfo.endPage < pageInfo.maxPage }">
            <a href="/bookList.ok?page=${pageInfo.endPage+1 }" class="next-arrow">
                <i class="fa fa-long-arrow-right pt-3" aria-hidden="true"></i>
            </a>
        </c:if>
    </div>
</c:if>

 


 

 추가 설명 

1. LIMIT ?, ?

예시를 통해 이해해보자.

 

먼저 DB의 book 테이블을 아래 쿼리문으로 조회해보자.

 

SELECT * FROM jspbookshop.book
order by b_publish_date desc;

 

▼ 조회 확인

LIMIT 전 레코드

 

이젠 LIMIT를 걸어 다시 조회해보자.

 

select * 
from book
order by b_publish_date desc
limit 3, 9;

 

▼ 조회 확인

레코드 인덱스 3부터 9개의 데이터가 조회된 것을 확인할 수 있다.

LIMIT 후

 


 

 

이렇게 기본적인 페이징 처리 로직이 구현되었다.

 

다음 포스팅에서는 도서 목록 화면에 <select> 박스 옵션을 추가해 추가적인 정보에 따른 페이징 처리 구현을 정리해 봄으로써 이번 구현을 마무리해 보도록 하겠다.

 

 

끝.