본문 바로가기
Java/Servlet & JSP

JSP) 기본 페이징 처리

by 박채니 2022. 6. 28.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


페이징

페이지에 보여지는 목록이 너무 많다면, 한 눈에 보기가 어렵고 스압이 일어납니다.

만일 회원이 100명이 넘어간다면, 회원관리 탭은 100건이 넘는 건이 한 페이지에 나타나게 됩니다.

보기에도 어렵고, 구분이 어렵기 때문에 페이징 작업은 필수입니다.


content 영역

 

페이징 쿼리 작성

-- 페이징 쿼리 작성 (top-n분석)
-- 1. rownum
-- rownum을 새로 부여하려면, where 조건절 변경/inline view를 사용해야 함
-- offset이 있는 경우, inline view를 한 레벨 더 사용해야됨
select 
    *
from (
    select
        rownum rnum,
        m.*
    from (
        select
            *
        from
            member
        order by
            enroll_date desc
        ) m
    ) m
where
    rnum between 11 and 20;

-- 2. row_number 윈도우함수
-- numPerPage = 10
-- cPage = 1, start = 1, end = 10
-- cPage = 2, start = 11, end = 20
-- cPage = 3, start = 21, end = 30
select 
    *
from (
    select
        row_number() over (order by enroll_date desc) rnum,
        m.*
    from
        member m
    ) m
where
    rnum between 11 and 20;

페이징 쿼리 함수를 이용하여 페이징 처리를 할 수 있습니다.

간편하게 row_number 함수를 이용하여 처리해주도록 하겠습니다.

 

Controller

AdminMemberListServlet

@WebServlet("/admin/memberList")
public class AdminMemberListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private MemberService memberService = new MemberService();

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			// 1. 사용자입력값
			int cPage = 1;
			int numPerPage = 10; // 10개씩 보여줌
			
			// 기본 값을 1로 세팅해두고 try~catch로 감쌌기 때문에 오류가 발생하면 값 대입이 이뤄지지 않고 기본값을 그대로 유지!
			// cPage가 null이라면 null을 Integer로 변환하기 때문에 NumberFormatException 발생!
			try {
				cPage = Integer.valueOf(request.getParameter("cPage"));
			} catch (NumberFormatException e) {}
			
			// 2. 업무로직
			// a. content 영역
			int start = ((cPage - 1) * numPerPage) + 1;
			int end = cPage * numPerPage;
			Map<String, Object> param = new HashMap<>();
			param.put("start", start);
			param.put("end", end);
			
			List<Member> memberList = memberService.findAll(param);
			
			// 3. view단 응답처리
			request.setAttribute("memberList", memberList);
			request.getRequestDispatcher("/WEB-INF/views/admin/memberList.jsp").forward(request, response);
		} catch(Exception e) {
			e.printStackTrace();
			throw e;
		}
	}
}

cPage(current Page) - 현재 페이지numPerPage - 보여줄 content의 개수를 지정해주었습니다.

cPage는 기본 값을 1로 세팅하고, 만일 넘어온 cPage가 있다면 대입되도록 하였고 처음 페이지를 로딩할 때 cPage는 null이기 때문에 null값을 Integer 타입으로 변환 시에는 NumberFormatException이 발생하므로 try~catch로 잡아주었습니다.

만일 발생한다면, 기본 값 1이 계속 세팅 되어있어야 하므로 catch절에서 예외 작업을 하지 않았으므로 cPage는 그대로 1이 될 것입니다.

 

cPage가 1일 때, start는 1, end는 10

cPage가 2일 때, start는 11, end는 20 ... 이 되는 규칙을 통해 공식을 이용하여 계산해주었습니다.

(보여줄 content영역의 순서에 의해 정해짐 - SQL 참고!! between start and end)

 

Service

MemberService

public List<Member> findAll(Map<String, Object> param) {
    Connection conn = getConnection();
    List<Member> memberList = memberDao.findAll(conn, param);
    close(conn);
    return memberList;
}

 

Dao

MemberDao

// 회원 전체 조회
// findAll = select * from (select row_number() over (order by enroll_date desc) rnum, m.* from member m) m where rnum between ? and ?
public List<Member> findAll(Connection conn, Map<String, Object> param) {
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    List<Member> memberList = new ArrayList<>();
    String sql = prop.getProperty("findAll");

    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, (int)param.get("start"));
        pstmt.setInt(2, (int)param.get("end"));
        rset = pstmt.executeQuery();
        while(rset.next()) {
            Member member = handleMemberResultSet(rset);
            memberList.add(member);
        }
    } catch (SQLException e) {
        throw new MemberException("회원 전체 조회 오류!", e);
    } finally {
        close(rset);
        close(pstmt);
    }
    return memberList;
}

이처럼 content영역은 정보가 10개씩 쪼개어져서 나타나는 것을 확인할 수 있으며, cPage의 값에 따라 페이지 이동이 일어나 보여지는 정보가 다른 것을 확인할 수 있습니다.

 


pagebar 처리

 

Controller

AdminMemberListServlet

@WebServlet("/admin/memberList")
public class AdminMemberListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private MemberService memberService = new MemberService();

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			// 1. 사용자입력값
			int cPage = 1;
			int numPerPage = 10; // 10개씩 보여줌
			
			// 기본 값을 1로 세팅해두고 try~catch로 감쌌기 때문에 오류가 발생하면 값 대입이 이뤄지지 않고 기본값을 그대로 유지!
			// cPage가 null이라면 null을 Integer로 변환하기 때문에 NumberFormatException 발생!
			try {
				cPage = Integer.valueOf(request.getParameter("cPage"));
			} catch (NumberFormatException e) {}
			
			// 2. 업무로직
			// a. content 영역
			int start = ((cPage - 1) * numPerPage) + 1;
			int end = cPage * numPerPage;
			Map<String, Object> param = new HashMap<>();
			param.put("start", start);
			param.put("end", end);
			
			List<Member> memberList = memberService.findAll(param);
			
			// b. pagebar 영역
			int totalContent = memberService.getTotalContent();
			String url = request.getRequestURI();
			String pagebar = HelloMvcUtils.getPagebar(cPage, numPerPage, totalContent, url);
			System.out.println("pagebar = " + pagebar);
			
			// 3. view단 응답처리
			request.setAttribute("memberList", memberList);
			request.setAttribute("pagebar", pagebar);
			request.getRequestDispatcher("/WEB-INF/views/admin/memberList.jsp").forward(request, response);
		} catch(Exception e) {
			e.printStackTrace();
			throw e;
		}
	}
}

회원 수를 알아야 페이지 수를 구할 수 있기 때문에 totalContent로 전체 회원 수를 받아왔습니다.

 

Service

MemberService

public int getTotalContent() {
    Connection conn = getConnection();
    int totalContent = memberDao.getTotalContent(conn);
    close(conn);
    return totalContent;
}

 

Dao

MemberDao

// getTotalContent = select count(*) from member
public int getTotalContent(Connection conn) {
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    int totalContent = 0;
    String sql = prop.getProperty("getTotalContent");

    try {
        pstmt = conn.prepareStatement(sql);
        rset = pstmt.executeQuery();
        if(rset.next()) {
            totalContent = rset.getInt(1);
        }
    } catch (SQLException e) {
        throw new MemberException("전체 회원 수 조회 오류!", e);
    } finally {
        close(rset);
        close(pstmt);
    }
    return totalContent;
}

컬럼 별칭을 따로 지정하지 않은 경우에는 인덱스 값으로 가져올 수도 있습니다.

또한, 단일 컬럼인 것이 확실하다면 while문이 아닌 if문으로도 대체 가능합니다.

 

HelloMvcUtils

/**
 * @param cPage
 * @param numPerPage
 * @param totalContent
 * @param url
 * 
 * totalPage 전체 페이지 수
 * pagebarSize 한 페이지에 표시할 페이지 개수
 * pagebarStart ~ pagebarEnd
 * pageNo 증감변수
 * 
 * 1. 이전영역
 * 2. pageNo 영역
 * 3. 다음영역
 */
public static String getPagebar(int cPage, int numPerPage, int totalContent, String url) {
    StringBuilder pagebar = new StringBuilder();
    url += "?cPage=";
    int totalPage = (int)Math.ceil((double)totalContent / numPerPage);
    int pagebarSize = 5;
    int pagebarStart = ((cPage - 1) / pagebarSize * pagebarSize) + 1;
    int pagebarEnd = pagebarStart + pagebarSize - 1;
    int pageNo = pagebarStart;

    // 이전영역
    if(pageNo == 1) {

    } else {
        pagebar.append("<a href='" + url + (pageNo -1) + "'>이전</a>\n");
    }

    // pageNo영역
    while(pageNo <= pagebarEnd && pageNo <= totalPage) {
        // 현재페이지
        if(pageNo == cPage) {
            pagebar.append("<span class='cPage'>" + pageNo + "</span>\n");
        }
        // 현재페이지가 아닌 경우
        else {
            pagebar.append("<a href='" + url + pageNo + "'>" + pageNo + "</a>\n");
        }
        pageNo++;
    }

    // 다음영역
    if(pageNo > totalPage) {

    } else {
        pagebar.append("<a href='" + url + pageNo + "'>다음</a>\n");
    }
    return pagebar.toString();
}

@콘솔출력값
pagebar = <span class='cPage'>1</span>
<a href='/mvc2/admin/memberList?cPage=2'>2</a>
<a href='/mvc2/admin/memberList?cPage=3'>3</a>
<a href='/mvc2/admin/memberList?cPage=4'>4</a>
<a href='/mvc2/admin/memberList?cPage=5'>5</a>
<a href='/mvc2/admin/memberList?cPage=6'>다음</a>

pagebar = <a href='/mvc2/admin/memberList?cPage=5'>이전</a>
<a href='/mvc2/admin/memberList?cPage=6'>6</a>
<span class='cPage'>7</span>
<a href='/mvc2/admin/memberList?cPage=8'>8</a>
<a href='/mvc2/admin/memberList?cPage=9'>9</a>
<a href='/mvc2/admin/memberList?cPage=10'>10</a>
<a href='/mvc2/admin/memberList?cPage=11'>다음</a>

총 페이지 개수, pagebar의 시작 값, pagebar의 마지막 값을 공식을 이용하여 구했습니다.

이전영역/content영역/다음영역으로 나눠서 분기처리를 통해 a태그로 링크를 걸거나, 다음/이전을 표시할 수 있도록 하였습니다.

 

totalPage = 12

1~5 내에 있는 페이지에 접속했을 때,

- 이전영역 if문

pageNo = pagebarStart이므로, pageNo == 1 → '이전'영역은 표시 되지 않음

 

- content영역 while문

while문을 돌면서 현재페이지인 경우에는 span태그, 그렇지 않다면 a태그를 가진 content 영역이 생성

 

- 다음영역 if문

while문을 돌고 pageNo은 ++이 된 채로 탈출하므로, 다음영역의 if문을 돌 때 pageNo = 6 → 6페이지로 이동하는 '다음'영역 표시

 

5~10 내에 있는 페이지에 접속했을 때,

이전영역 if문

pageNo = pagebarStart이므로, pageNo = 6 → pageNo-1 즉 5페이지로 이동하는 a태그를 가진 '이전'영역 표시

 

content영역 while문

while문을 돌면서 현재페이지인 경우에는 span태그, 그렇지 않다면 a태그를 가진 content 영역 생성

 

다음영역 if문

while문을 돌고 pageNo은 ++이 된 채로 탈출하므로, 다음영역의 if문을 돌 때 pageNo = 11 → 11페이지로 이동하는 '다음'영역 표시

 

10~12 내에 있는 페이지에 접속했을 때,

이전영역 if문

pageNo = pagebarStart이므로, pageNo = 10 → pageNo-1 즉 9페이지로 이동하는 a태그를 가진 '이전'영역 표시

 

content영역 while문

while문을 돌면서 현재페이지인 경우에는 span태그, 그렇지 않다면 a태그를 가진 content 영역 생성

이 때 pageNo <= totalPage 즉, pageNo이 13이 되어버리면 while문을 탈출!

 

다음영역 if문

다음영역의 if문을 돌 때 pageNo = 13 →  pageNo > totalPage (13 > 12) 참이므로 '다음'영역이 표시 되지 않음

 

memberList

- pagebar 추가

<div id="pagebar">
    <%= request.getAttribute("pagebar") %>
</div>