게시글이 1000개가 있을 때 한페이지 내에 전부 출력 된다면 굉장히 비효율적일 것입니다. 그래서 서버단에서 항상 페이징 처리를 해주어야 합니다.
필자는 프로젝트 때 만들어 놓은 CRUD 게시판을 이용하여 작업을 했기 때문에 본인의 게시판에 맞게 적절히 맟춰주면 되겠습니다.
1. 페이징의 이해
페이징이 어떻게 처리 되는지 이해해야 합니다. 이 글에선 한 페이지당 10개의 글을 보여주고 버튼은 최대 10개로 예시를 들겠습니다.
- 총 게시글 수 91개인 경우
총 게시글이 91개 일 때 마지막 페이지는 9페이지가 되어야 합니다.[1][2][3][4][5][6][7][8][9]
이때 페이지의 끝 번호를 알기 위해서 총 게시글의 개수가 필요 합니다. - 총 게시글 수 223개
페이지 수가 100개를 넘어가면 다음 버튼이 있어야 하고 첫 페이지가 1이 아니라면[1][2][3][4][5][6][7][8][9][10][다음] [이전][11][12][13][14][15][16][17][18][19][20][다음] [이전][21][22]
이전 버튼이 있어야 합니다. 또한 현재 페이지가 13번 이면 페이지 버튼은 11~20 이여 하겠죠?
2. 페이징 규칙
- 페이징 처리는 반드시 GET 방식만을 이용해야 한다.
- 게시글 목록 페이지의 하단에 페이지들의 번호를 보여주고 원하는 번호를 선택하면 해당 페이지로 이동해서 목록을 보여줘야 한다.
- 페이징은 반드시 필요한 페이지 번호만 출력해야 한다. 만약 페이지당 10개의 게시글을 출력하는데 전체 게시글의 수가 42개라면 페이지의 번호는 5페이지까지여야 한다.
- 이전과 다음 버튼이 존재해야 한다. 게시글이 1000개인데 1페이지당 10개의 게시글을 볼 수 있게 했다면 페이징 버튼은 100개가 필요하다. 그런데 이전과 다음 버튼이 존재하지 않는다면 100개의 버튼을 한번에 보여줘야 한다. 그렇기때문에 하단 페이지에 보여줄 버튼의 갯수를 정하고 더 많은 데이터가 있다면 이전과 다음 버튼으로 표시해줘야 한다.
- 게시글을 조회하거나 수정, 삭제를 하고 난 뒤, 다시 원래의 목록 페이지로 이동해야 한다. 예를 들면 게시판에 5페이지에 있는 어떠한 게시물을 조회하거나 수정, 삭제를 했다고 한다면, '목록으로'버튼을 이용해 목록으로 돌아갈 땐 5페이지로 이동해야 한다.
3. SQL (Oracle)
페이징 처리를 위한 데이터를 DB에 넣어 두었습니다. 오라클에선 페이징 처리를 위한 쿼리를 짤 땐 ROWNUM을 이용합니다.
게시판에 뿌려질 데이터를 충분히 많이 넣어두고 SELECT 문으로 가져 옵니다.
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT
FROM (
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT,
ROW_NUMBER() OVER(ORDER BY BNO DESC) AS RNUM
FROM MP_BOARD
)
WHERE RNUM BETWEEN 시작열 AND 마지막열
ORDER BY BNO DESC
위와 같이 인라인뷰 쿼리와 ROWNUM을 이용하여 시작열과 마지막열을 정해서 사용자게 뿌려줄 특정 게시글만 가져올 수 있습니다.
- 1페이지 -> WHERE RNUM BETWEEN 1 AND 10
- 2페이지 -> WHERE RNUM BETWEEN 11 AND 20
- 3페이지 -> WHERE RNUM BETWEEN 21 AND 30
4. 페이징 처리 클래스
SQL query의 시작열과 마지막열의 데이터를 Criteria라는 클래스가 전달해줍니다.
public class Criteria {
private int page; // 페이지 번호
private int perPageNum; // 페이지당 게시글 갯수
private int rowStart; // 페이지 한 행의 첫번째 게시물 rowNum
private int rowEnd; // 페이지 한 행의 마지막 게시물 rowNum
public Criteria() { //디폴트 생성자
this.page = 1; // 페이지 1 로 초기화
this.perPageNum = 10; // 페이지당 게시글 10개
}
public int getPage() {
return page;
}
public void setPage(int page) { // 페이지 번호 set
if (page <= 0) {
this.page = 1;
}
this.page = page;
}
public int getPerPageNum() {
return perPageNum;
}
public void setPerPageNum(int perPageNum) {
// 페이지당 게시물 갯수 set
if (perPageNum <= 0 || perPageNum > 100)
this.perPageNum = 10;
this.perPageNum = perPageNum;
}
public int getPageStart() {
// 현재 페이지의 페이지당 게시글 수를 곱하여
// 현재 페이지의 시작 게시글 ROWNUM수를 구하는것
return (this.page - 1) * perPageNum;
}
public int getRowStart() {
rowStart = ((page - 1) * perPageNum) + 1;
return rowStart;
}
public int getRowEnd() {
rowEnd = rowStart + perPageNum - 1;
return rowEnd;
}
@Override
public String toString() {
return "Criteria [page=" + page + ", perPageNum=" + perPageNum + ", rowStart=" + rowStart + ", rowEnd=" + rowEnd
+ "]";
}
}
게시글 조회 쿼리에 전달해줄 데이터를 담게 될 클래스라고 생각하시면 됩니다.
int page
: 현재 페이지 번호int perPageNum
: 한 페이지 당 보여줄 게시글 수int getPageStart()
: 특정 페이지의 시작 게시글 번호
이번엔 화면에서 버튼 생성을 도와줄 계산 클래스입니다. 수학적 계산이 필요한 부분입니다.
public class PageMaker {
private int totalCount; // 게시글 총 갯수
private int startPage; // 10개의 페이지중 첫번째
private int endPage; // 10개의 페이지중 마지막
private boolean prev; // 페이지 이전 버튼
private boolean next; // 페이지 다음 버튼
private int displayPageNum = 10;
private Criteria cri; // 페이지 정보 객체
public void setCri(Criteria cri) {
this.cri = cri;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
calcData();
}
public int getTotalCount() {
return totalCount;
}
public int getStartPage() {
return startPage;
}
public int getEndPage() {
return endPage;
}
public boolean isPrev() {
return prev;
}
public boolean isNext() {
return next;
}
public int getDisplayPageNum() {
return displayPageNum;
}
public Criteria getCri() {
return cri;
}
private void calcData() { // 페이지 데이터 처리
// 1~10 페이지는 endPage가 10으로 고정되고 11~20 페이지는 endPage가 20으로 고정되는 방식
endPage = (int) (Math.ceil(cri.getPage() / (double)displayPageNum) * displayPageNum);
// startPage는 매 첫번째 페이지
startPage = (endPage - displayPageNum) + 1;
int tempEndPage = (int) (Math.ceil(totalCount / (double)cri.getPerPageNum()));
if (endPage > tempEndPage) {
endPage = tempEndPage;
// 마지막 게시물이 있는 페이지가 endPage로 다시 할당해준다.
}
prev = startPage == 1 ? false : true;
// 첫번째 페이지가 1이면 false를 반환하여 이전버튼이 사라지게 한다.
next = endPage * cri.getPerPageNum() >= totalCount ? false : true; // 마지막페이지의 게시글이 10개 이하면 false를 반환.
}
public String makeQuery(int page) {
// 원하는 페이지로 페이지 쿼리문을 날려준다.
UriComponents uriComponents =
UriComponentsBuilder.newInstance()
.queryParam("page", page) // page번호를 파라미터값으로 날려준다.
// .queryParam("perPageNum", cri.getPerPageNum()) // page당 게시글 갯수를 파라미터값으로 날려준다.
.build();
return uriComponents.toUriString();
}
}
Criteria cri
: 위에서 만든 page와 perPageNum을 가지고 있는 객체int totalCount
: 전체 게시글 수int startPage
: 시작 페이지, 현재 페이지가 12쪽이면 11쪽이 startPage 현재 페이지가 28쪽이면 21쪽이 startPage가 됩니다.int endPage
: 마지막 페이지boolean prev
: [이전] 버튼의 생성 조건이 되는 필드boolean next
: [다음] 버튼의 생성 조건이 되는 필드makeQuery()
: 이 메소드는 스프링에서 지원 해주는 객체를 가지고 있습니다 UriComponent라는 객체입니다. 이 놈은<a href="/list?page=2&perPageNum=10">
을 작성할 때 파라미터를 동적으로 넘겨줄 수 있는 객체입니다.
PageMaker 클래스를 사용할 땐 totalCount(전체 게시글 수)와 Criteria가 먼저 셋팅 되어 있어야 합니다.
5. 스프링 컨트롤러
// 게시물 목록 컨트롤러
@RequestMapping(value ="/list", method = RequestMethod.GET)
public String listView(Model model, Criteria cri) throws Exception{
logger.info("list");
List<BoardVO> vo = service.list(scri);
model.addAttribute("list", vo);
model.addAttribute("cri", cri);
pageMaker.setCri(cri);
pageMaker.setTotalCount(service.listCount(scri));
model.addAttribute("pageMaker", pageMaker);
return "board/list";
}
- 여기서 pageMaker 객체에 먼저 cri 객체를 셋팅 해주는것을 볼 수 있습니다. 총 게시글 수와 현재 페이지, 페이지 당 게시글 수의 값을 가져오는 겁니다.
6. 게시글 목록을 보여줄 화면 작업
<!-- 페이징 -->
<div>
<ul>
<c:if test="${pageMaker.prev}">
<li>
<a href="list${pageMaker.makeQuery(pageMaker.startPage - 1)}">이전</a>
</li>
</c:if>
<c:forEach begin="${pageMaker.startPage}" end="${pageMaker.endPage}" var="idx">
<li>
<a href="list${pageMaker.makeQuery(idx)}">${idx}</a>
</li>
</c:forEach>
<c:if test="${pageMaker.next && pageMaker.endPage > 0}">
<li><a href="list${pageMaker.makeQuery(pageMaker.endPage + 1)}">다음</a></li>
</c:if>
</ul>
</div>
== [이전] 버튼 ==
- 이전 버튼이 클릭가능한 조건이면, a태그를 이용해 이전 버튼이 뜨도록 하고, href로 링크를 걸되
아까 만든 makeQuery 메서드를 이용해서 쿼리문자열을 만들어 줍니다.?page=3&perPageNum=10
이 부분을 생성해서 넣게 되는데 단 이전 버튼을 클릭하면현재 페이지가 시작페이지의 -1
이 되도록 되어야 함으로 그 부분만 신경써 주면 됩니다.
== [1],[2],[3]...[10] 버튼==
- jstl 이용해
for문
을 돌면서 startPage ~ endPage까지 표시해주고, idx에 현재 페이지를 지정하여 a링크를 통해 원하는 페이지로 이동한다.
==[다음] 버튼==
- 이전 버튼과 마찬가지로 +1를 해주어 현재 페이지가 끝 페이지보다 + 1해주어 이동한다.
사용자 화면
'스프링' 카테고리의 다른 글
검색 처리와 동적 SQL (2) | 2020.07.19 |
---|---|
스프링의 AOP 란? (0) | 2020.06.23 |
스프링의 @ModelAttribute 어노테이션 (0) | 2020.06.15 |
AJAX란? (0) | 2020.06.15 |
인터셉터(Interceptor)란? (0) | 2020.06.13 |