Spring legacy 프로젝트 게시판 만들기(5 - 1) - 게시판 페이징
Spring legacy 프로젝트 게시판 만들기(4) - 수정하기
Spring legacy 프로젝트 게시판 만들기(3) - 상세조회 이전글 Spring legacy 프로젝트 게시판 만들기(2) - 전체 조회 이전 글 Spring legacy 프로젝트 게시판 만들기(1) - 설정 spring으로 간단한 흐름과 기능적으
cronex.tistory.com
수정에 이어서 이번에는 게시판 페이징을 진행 하도록 하겠습니다.
목표는 한페이지에 게시물 10개씩 페이징 버튼은 10개 씩 보여주도록 하겠습니다.
먼저 페이징정보를 담을 객체를 만들도록 하겠습니다.
- PageInfoDTO.java
package com.js.board.model.dto;
public class PageInfoDTO implements java.io.Serializable{
private int pageNo; //요청한 페이지 번호 페이징버튼 번호
private int totalCount; //전체 게시물 수
private int limit; // 한 페이지에 보여줄 게시물 수
private int buttonAmount; // 한번에 보여줄 페이징 버튼의 갯수
private int maxPage; // 가장 마지막 페이지
private int startPage; //한 번에 보여줄 페이징 버튼의 시작하는 페이지 수
private int endPage; //한 번에 보여줄 페이징 버튼의 마지막 페이지 수
private int startRow; // DB 조회 시 최신 글 부터 조회해야 하는 행의 시작 수
private int endRow; // DB 조회 시 최신글부터 조회해야 하는 행의 마지막 수
public PageInfoDTO() {}
public PageInfoDTO(int pageNo, int totalCount, int limit, int buttonAmount, int maxPage, int startPage, int endPage,
int startRow, int endRow) {
super();
this.pageNo = pageNo;
this.totalCount = totalCount;
this.limit = limit;
this.buttonAmount = buttonAmount;
this.maxPage = maxPage;
this.startPage = startPage;
this.endPage = endPage;
this.startRow = startRow;
this.endRow = endRow;
}
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getButtonAmount() {
return buttonAmount;
}
public void setButtonAmount(int buttonAmount) {
this.buttonAmount = buttonAmount;
}
public int getMaxPage() {
return maxPage;
}
public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
public int getStartPage() {
return startPage;
}
public void setStartPage(int startPage) {
this.startPage = startPage;
}
public int getEndPage() {
return endPage;
}
public void setEndPage(int endPage) {
this.endPage = endPage;
}
public int getStartRow() {
return startRow;
}
public void setStartRow(int startRow) {
this.startRow = startRow;
}
public int getEndRow() {
return endRow;
}
public void setEndRow(int endRow) {
this.endRow = endRow;
}
@Override
public String toString() {
return "PageInfoDTO [pageNo=" + pageNo + ", totalCount=" + totalCount + ", limit=" + limit + ", buttonAmount="
+ buttonAmount + ", maxPage=" + maxPage + ", startPage=" + startPage + ", endPage=" + endPage
+ ", startRow=" + startRow + ", endRow=" + endRow + "]";
}
}
pageNo | controller에서 받아와 넘겨 줄 값 (브라우저에서 받아온 값,Pagenation class에 전달할 값) |
현재 보고 있는 페이지의 값이다. 1, 2, 3 .... |
totalCount | db에 저장되어있는 전체 게시물 수 (db조회, Pagenation class에 전달할 값) |
db에 저장되어있는 게시물의 갯수 db에서 조회 |
limit | 한 페이지에서 보여줄 게시물 수 (설정(고정) or 브라우저에서 받아온 값, Pagenation class에 전달할 값) |
브라우저 한 페이지에서 보여줄 게시물의 갯수 5, 10, 20 ... |
buttonAmount | 페이징 시 보여줄 버튼 수 (설정(고정) or 브라우저에서 받아온 값, Pagenation class에 전달할 값) |
아래 페이징 버튼의 갯수 5개, 10개, 15개에서 선택 |
maxPage | 가장 마지막 페이지 Math.ceil(totalCount / (double)limit) (pagenation class에서 계산 값) |
전체 게시물 갯수(totalCount)와 한 페이지에서 보여줄 게시물 수(limit)을 나눈 값 |
startPage | 페이징 버튼 시작 버튼 값 (Pagenation class에서 계산 값) |
현재 보고 있는 페이지의 값(pageNo)에서 아래 페이징 버튼의 시작 값 예시 버튼 갯수(buttonAmount)가 10개라면 startPage는 1, 11, 21, 31... 이 된다. |
endPage | 페이징 버튼 마지막 버튼 값 (Pagenation class에서 계산 값) |
현재 보고 있는 페이지의 값(pageNo)에서 아래 페이징 버튼의 마지막 값 예시 버튼 갯수(buttonAmount)가 10개라면 endPage는 10, 20, 30, ...이 된다. |
startRow | DB 조회 시 최신 글 부터 조회할 시작 값 (Pagenation class에서 계산 값) |
한 페이지에서 보여줄 게시글의 갯수(limit)가 10개라면 startRow는 1, 11, 21, 31이 된다. |
endRow | DB조회 시 최신 글 부터 조회할 마지막 값 (Pagenation class에서 계산 값) |
한 페이지에서 보여줄 게시글의 갯수(limit)가 10개라면 endRow는 20, 30, 40, 50이 된다. |
이해를 도울 수 있도록 아래그림을 보고 설명하도록 하겠습니다.
pageNo | 위 그림에서 1을 누르면 pageNo = 1, 2를 누르면 pageNo = 2 가 됩니다. |
buttonAmount | 위 그림에서 1 ~ 10까지 있습니다. buttonAmount = 10, 만약 나타내려는 버튼의 수가 5개라면 1 ~5 buttonAmount = 5 가 됩니다. |
maxPage | 예를 들어 buttonAmount = 10 이고, 총 나타낼 수 있는 페이지(maxPage)가 13이라면 1 ~ 10 다음에 11 ~ 20 까지 나타내는 것이 아닌 11 ~ 13까지 나타내게 됩니다. |
startPage | 위 그림에서 볼 수 있듯이 1 ~10에서 startPage의 값은 1이 됩니다. 다음으로 넘어가 11 ~20 이라면 startPage는 11이 됩니다. |
endPage | 위 그림에서 볼 수 있듯이 1 ~ 10에서 endPage는 10이 됩니다. 다음버튼을 눌러 넘어가 11 ~ 20이라면 endPage는 20이 됩니다. |
DTO의 사용처는 앞으로 알아 보도록 하고, 이어서 페이징을 계산할 class를 만들도록 하겠습니다.
- Pagenation.java (페이징 계산 class)
package com.js.board.paging;
import com.js.board.model.dto.PageInfoDTO;
public class Pagenation {
public static PageInfoDTO getPageInfo(int pageNo, int totalCount) {
int maxPage;
int startPage;
int endPage;
int startRow;
int endRow;
int limit = 10;
int buttonAmount = 10;
/*총 페이지 수 계산
* ex) 123개의 게시물 한 페이지당 10개씩 보여지는 경우
* 짜투리목록이 1페이지 추가로 필요
* 1페이지 추가 하기 위해 + 0.9
* 뒤에 더하기는 가중치 만큼 더하는 것 10개씩일 때는 하나의 게시물의 가중치는 10% 이니까 0.9
* 5개 일때는 게시물 한 개의 가중치는 20% 이니까 0.8
* 20개 일때는 게시물 한 개의 가중치는 5% 이니까 0.95
* */
// maxPage = (int) (((double) totalCount / limit) + 0.9);
maxPage = (int) Math.ceil((double) totalCount / limit); //올림처리
/*현재 페이지에 보여줄 시작페이지 수
* 10개씩 보여지게 할 경우
* 1, 11, 21, 31...
* 5개 씩일 경우
* 1, 6, 11, 16,...
* 뒤에 덧셈은 버튼 가중치에 대한 덧셈
* 앞자리 구해서 + 1
* 배수의 + 1
* */
// startPage = (((int) ((double) pageNo / buttonAmount + 0.9)) - 1) * buttonAmount + 1;
startPage = (int)(Math.ceil((double) pageNo / buttonAmount) - 1) * buttonAmount + 1;
/*
* 목록 아래 쪽에 보여질 마지막 페이지 수
* */
endPage = startPage + buttonAmount - 1;
/*maxPage가 더 작은 경우 마지막 페이지가 maxPage가 된다.*/
if(endPage > maxPage) {
endPage = maxPage;
}
/*마지막 페이지는 0이 될수 없기 때문에 게시물이 아무것도 존재하지 않으면 max 페이지와 endPage를 1로 바꿔준다.*/
if(maxPage == 0 && endPage == 0) {
maxPage = startPage;
endPage = startPage;
}
/*조회를 시작할 행의 번호 수와 마지막 행 번호를 계산한다.*/
startRow = (pageNo - 1) * limit + 1;
endRow = startRow + limit - 1;
return new PageInfoDTO(pageNo, totalCount, limit, buttonAmount, maxPage, startPage, endPage, startRow, endRow);
}
}
maxPage | 페이징 페이지 총 갯수 | ex) 게시물 갯수(totalCount) 135개 / 한 페이지에 보여줄 게시물 갯수(limit) 10개 => 14페이지 게시물 갯수 152개 / 한 페이지에 보여줄 게시물 갯수 5개 => 31페이지 |
startPage | 페이징 시작 번호 | 한 페이지에 보여줄 게시물 갯수(limit)가 10개라면 => 1, 11, 21, 31, 41... 한 페이지에 보여줄 게시물 갯수(limit)가 5개라면 => 1, 6, 11, 16, 21 ... |
endPage | 페이징 끝 번호 | 한 페이지에 보여줄 게시물 갯수(limit)가 10개라면 => 10, 20, 30, 40,... 한 페이지에 보여줄 게시물 갯수(limit)가 5개라면 => 5, 10, 15, 20.... |
startRow | db조회 시작 행 번호 | 한 페이지에 보여줄 게시물 갯수(limit)가 10개라면 => 1, 11, 21, 31... 한 페이지에 보여줄 게시물 갯수(limit)가 5개라면 => 1, 6, 11, 16, 21.. |
endRow | db조회 마지막 행 번호 | 한 페이지에 보여줄 게시물 갯수(limit)가 10개라면 => 10, 20, 30, 40,... 한 페이지에 보여줄 게시물 갯수(limit)가 5개라면 => 5, 10, 15, 20.... |
limit | 페이지에서 보여줄 게시물 갯수 | 한 페이지에 보여줄 게시물 갯수 ex) limit = 10; limit = 20; |
buttonAmount | 페이징 시 보여줄 버튼 수 | 페이징 시 보여줄 버튼 수 ex) buttonAmount = 5; buttonAmount = 10; |
먼저 maxPage 부분부터 살펴보도록 하겠습니다.
/*총 페이지 수 계산
* ex) 123개의 게시물 한 페이지당 10개씩 보여지는 경우
* 짜투리목록이 1페이지 추가로 필요
* 1페이지 추가 하기 위해 + 0.9
* 뒤에 더하기는 가중치 만큼 더하는 것 10개씩일 때는 하나의 게시물의 가중치는 10% 이니까 0.9
* 5개 일때는 게시물 한 개의 가중치는 20% 이니까 0.8
* 20개 일때는 게시물 한 개의 가중치는 5% 이니까 0.95
* */
// maxPage = (int) (((double) totalCount / limit) + 0.9);
maxPage = (int) Math.ceil((double) totalCount / limit); //올림처리
maxPage(페이징 페이지 총 갯수, button갯수) 계산 방법인데요, 예를 들어 게시물이 총 123개이고 한 페이지에 보여줄 게시물의 갯수가 10라면 총 13페이지가 있어야 합니다. (maxPage = 13) 이유는 10개 씩 보여주게 된다면 12페이지 까지는 10개씩 보여주고, 마지막 짜투리인 3개의 게시물도 보여줘야하기 때문에 총 13페이지가 필요하게됩니다.
먼저 주석처리한 계산법부터 알아보도록 하겠습니다.
totalCount / (double) limit | 1) 게시물 총 갯수와 한 페이지에서 보여줄 게시물의 갯수를 나눕니다. double로 형변환을 해주는 이유는 소수점 자리까지 계산하기 위함입니다. |
(totalCount / (double) limit) + 0.9 | 2) 나누고 난 후 0.9를 더합니다. 여기서 덧셈의 숫자는 게시물의 가중치에 따라 달라집니다. 한 페이지에 보여줄 게시물의 갯수(limit)에 따라 다른데 예를 들어 지금 10개(limit)를 보여준다면 한 게시물의 가중치는 10% 즉 0.1이 될 것입니다. 따라서 0.9를 더해준 것이고, 5개(limit)을 보여준다면 한 게시물의 가중치는 20% 즉 0.8을 더하게 될 것입니다. 마지막으로 20을(limit) 보면 한 게시물에 대한 가중치는 5%가 될 것이고 0.95를 더하게 될 것입니다. ex) limit = 5 => 0.8; limit = 10 => 0.9; limit = 20 => 0.95; |
(int) ((totalCount / (double) limit) + 0.9) | 마지막으로 소수점 자리는 날려버리도록 하겠습니다. 버림으로 없애도 됩니다. |
위와 같은 방법이기에 중간에 덧셈하는 부분(올림을 하기위한 계산) 은 짜투리를 위하여 올림을 하는 계산이기 때문에 double형으로 형변환 후 계산된 값에 올림을 해서 계산을 해도 같은 결과를 얻을 수 있다.
totalCount / (double) limit | 1) 게시물 총 갯수와 한 페이지에서 보여줄 게시물의 갯수를 나눕니다. double로 형변환을 해주는 이유는 소수점 자리까지 계산하기 위함입니다. |
Math.ceil(totalCount / (double) limit) | 2) 위 계산법과는 다른 점이 Math.ceil() 메소드를 사용하는 것인데 따로 가중치에 대한 덧셈이 아닌 짜투리 값이 남는다면 올림 처리를 통해 한 페이지를 더 생성하도록 계산하는 것입니다. |
(int) Math.ceil(totalCount / (double) limit) | 3) 마지막으로 int형으로 형변환 하여 maxPage 변수에 담습니다. |
이어서 startPage를 알아보도록 하겠습니다. startPage는 페이징의 시작 값을 계산하는 것입니다. 예를들어 10개의 페이징 버튼(buttonAmount)을 설정한다면 1, 11, 21, 31 이 될 것입니다.
/*현재 페이지에 보여줄 시작페이지 수
* 10개씩 보여지게 할 경우
* 1, 11, 21, 31...
* 5개 씩일 경우
* 1, 6, 11, 16,...
* 뒤에 덧셈은 버튼 가중치에 대한 덧셈
* 앞자리 구해서 + 1
* 배수의 + 1
* */
// startPage = (((int) ((double) pageNo / buttonAmount + 0.9)) - 1) * buttonAmount + 1;
startPage = (int)(Math.ceil((double) pageNo / buttonAmount) - 1) * buttonAmount + 1;
pageNo = 3, buttonAmount = 10 | |
pageNo / (double) buttonAmount | 1) 현재 페이지 조회 값(pageNo)과 buttonAmount(나타낼 페이징 버튼 갯수)를 나눕니다. 이 후에 나오겠지만 기본적으로 pageNo는 1로 설정하겠습니다. |
(pageNo / (double) buttonAmount) + 0.9 | 2) 나눈 값에 0.9를 더합니다. 여기서 더하는 값은 버튼 갯수의 가중치에 해당합니다. 페이징 10개씩(buttonAmount) 나타내겠다면 0.9에 해당합니다. 5개 씩(buttonAmount) 나타낸다면 0.95를 더하시면 됩니다. 위에서 했던 maxPage와 같습니다. ex) buttonAmount = 5 => 0.95 buttonAmount = 10 => 0.9 buttonAmount = 20 => 0.8 |
(int)((pageNo / (double) buttonAmount) + 0.9) | 3) int형으로 형변환을 합니다. |
(int)((pageNo / (double) buttonAmount) + 0.9) - 1 | 4) buttonAmount = 10, pageNo = 3 일 때 (2) 계산을 하게 되면 1.2가 됩니다. pageNo는 현재 1 ~10 첫 번째 페이징에 속하는 값입니다. 이어서 형변환 후 -1을 통해 0을 만듭니다. ex) 1 ~ 10 -> 0 11 ~ 20 -> 1 21 ~ 30 -> 2 이어서 buttonAmount를 곱하게 되는데 계산해 보면 1 ~ 10은 곱하여 0 * buttonAmount이되고, 11 ~20은 곱하여 1 * buttonAmount, 21 ~30은 2 * buttonAmount 가 됩니다. |
((int)((pageNo / (double) buttonAmount) + 0.9) - 1) * buttonAmount | 5) 계산된 값에 buttonAmount값을 곱합니다. |
((int)((pageNo / (double) buttonAmount) + 0.9) - 1) * buttonAmount + 1 | 6) +1 을 통해 첫 번째 페이징 값을 계산합니다. ex) 1 ~ 10 =>0 + 1 -> startPage = 1 11 ~ 20 => 10 + 1 -> startPage = 11 21 ~ 30 => 20 + 1 -> startpage = 21 |
이것도 maxPage와 같이 Math.ceil()을 통한 올림으로 덧셈을 대신할 수 있습니다.
둘 다 Math.ceil() 메소드를 사용하는 것이 좋을 것 같습니다. 만약 limit이 고정되어 있다면 둘 다 괜찮겠지만 만약, 유동적으로 limit의 값이 변경된다면 그에 따라 덧셈이 달라지기 때문에 코드의 수정이 필요하지만 Math.ceil()을 통해 올림 처리를 한다면 가중치에 대한 덧셈을 따로 해주지 않아도 되기 때문이라고 생각합니다.
이어서 나머지 부분을 보도록 하겠습니다.
/*
* 목록 아래 쪽에 보여질 마지막 페이지 수
* */
endPage = startPage + buttonAmount - 1;
/*maxPage가 더 작은 경우 마지막 페이지가 maxPage가 된다.*/
if(endPage > maxPage) {
endPage = maxPage;
}
/*마지막 페이지는 0이 될수 없기 때문에 게시물이 아무것도 존재하지 않으면 max 페이지와 endPage를 1로 바꿔준다.*/
if(maxPage == 0 && endPage == 0) {
maxPage = startPage;
endPage = startPage;
}
/*조회를 시작할 행의 번호 수와 마지막 행 번호를 계산한다.*/
startRow = (pageNo - 1) * limit + 1;
endRow = startRow + limit - 1;
return new PageInfoDTO(pageNo, totalCount, limit, buttonAmount, maxPage, startPage, endPage, startRow, endRow);
endPage = startPage + buttonAmount - 1; | 페이징의 마지막 값(endPage)는 위에서 구한 startPage의 값에 나타낼 버튼의 갯수(buttonAmount)를 더한 후 -1 을 하여 계산을 합니다. ex) startPage = 1, buttonAmount = 10 => 1 + 10 -1; (1 ~ 10) endPage = 10 startPage = 11, buttonAmount = 10 => 11 + 10 -1; (11 ~ 20) endPage = 20 |
if(endPage > maxPage) { endPage = maxPage; } |
페이징 마지막 값(endPage)이 최대 페이지 값(maxPage)를 넘어서 표현할 필요가 없기 때문에 만약 endPage이 maxPage값을 넘는다면 endPage에 maxPage값을 초기화 한다. ex) maxPage = 13, endPage = 15; 라면 총 나타낼 페이징의 갯수는 13인데 15까지 나타낼 필요가 없으니 13까지만 나타낼 수 있도록 초기화를 한다. |
if(maxPage == 0 && endPage == 0) { maxPage = startPage; endPage = startPage; } |
게시물이 존재하지 않는다면 적어도 게시판의 틀은 보여줘야 하기 때문에 최대 페이징 페이징(maxPage)이 0 이고(게시물이 없을 경우) 페이징마지막 페이지 (endPage)가 0이라면 startPage를 통해 두 값을 초기화 한다. ex) 만약에 maxPage의 값이 0이라면 endPage 또한 0으로 초기화가 될 것이다(바로 위 if문을 통해 maxPage가 0이라면 endPage가 0으로 초기화 되기 때문에). startPage는 적어도 1 값을 가지기 때문에 startPage 값으로 두 값을 초기화 한다. |
startRow = (pageNo - 1) * limit + 1; | DB에서 조회할 값으로 현재 페이징 페이지(pageNo) -1 에 페이지에서 보여줄 게시물 갯수(limit)을 곱한 후 + 1을 한다 ex) pageNo = 1, limit = 10; => (1 - 1) * 10 + 1; -> 1; startRow = 1 pageNo = 2, limit = 20; => (2 - 1) * 10 + 1; -> 11; startRow = 11 |
endRow = startRow + limit - 1; | DB에서 조회할 값으로 시작 값(startRow)에 페이지에서 보여줄 게시물 갯수(limit)을 더한 후 -1 을 한다. ex) startRow = 1, limit = 10; => 1 + 10 -1 -> endRow = 10; startRow = 11, limit = 10; => 11 + 10 - 1 -> endRow = 20; |
return new PageInfoDTO(~~~~); | 계산된 값들을 생성자에 담아 반환합니다. |
이제 Controller, Service, Repository를 수정 및 작성하도록 하겠습니다
먼저 BoardController에서 board list를 보여주는 board method를 수정하도록 하겠습니다.
- 전체 게시물 수를 조회해오는 흐름 만들기
- startRow, endRow를 통해 해당 게시물 조회해오기
- 페이징(pageInfoDTO) 객체 와 boardList 객체(boarDTO) model에 담기
먼저 전체 게시물 숫자를 조회해오는 것 부터 진행하도록 하겠습니다.
- BoardController.java
package com.js.board.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
import com.js.board.model.service.BoardService;
import com.js.board.paging.Pagenation;
@Controller
public class BoardController {
private static final Logger log = LoggerFactory.getLogger(BoardController.class);
private final BoardService boardService;
@Autowired
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
/*
*board 리스트 페이지 접속 시 controller
*@param model Model 객체
*@param currentPage 페이징 번호
*@return viewResolver에 전달할 view path String
*/
@RequestMapping(value ="/board", method = RequestMethod.GET)
public String board(Model model, @RequestParam(required = false, defaultValue = "1") int currentPage) {
/*기본 페이징 값 1로 설정*/
int pageNo = 1;
/*넘어온 currentPage 값이 0이거나 0보다 작다면 기본적으로 1로 설정*/
if(currentPage <= 0) {
pageNo = 1;
} else {
pageNo = currentPage;
}
/*db에 저장되어 있는 게시판 갯수 조회*/
int totalCount = boardService.totalCount();
/*페이징 계산 후 pageInfo에 값을 담는다.*/
PageInfoDTO pageInfo = Pagenation.getPageInfo(pageNo, totalCount);
/*starRow, endRow에 해당하는 list 조회*/
List<BoardDTO> list = boardService.selectBoardList(pageInfo);
/*model에 담기*/
model.addAttribute("list", list);
model.addAttribute("pageInfo", pageInfo);
return "board/board";
}
}
전에 구현했던 모든 게시물리스트를 조회하는 controller를 수정하였습니다.
int pageNo = 1 | 기본 페이징 페이지 값으로 1을 설정하기 위해 1로 초기화 |
if(currentPage <= 0) { pageNo = 1; } else { pageNo = currentPage; } |
controller로 넘어온 currentPage의 값이 0과 같거나 0보다 작다면 pageNo의 값을 1로 초기화 한다. 그 것이 아니라면 currentPage의 값을 pageNo에 대입 합니다. ex) url 로 currentPage의 값을 임의로 수정하여 요청한다면 페이징 페이지 1을 보여줄 수 있도록 설정 |
int totalCount | db에서 게시물의 총 갯수를 조회해와서 초기화 합니다. ex) db에서 조회할 때 삭제되지않고, board_level(본문)이 0 인 값으로 조회 |
PageInfoDTO pageInfo | 페이징 페이지 값(pageNo)과 게시물의 총 갯수(totalCount)를 매개변수로 전달하여 페이징 계산 후 pageInfo 변수에 초기화 |
List<BoardDTO> list | startRow 와 endRow 를 갖고 해당 게시물을 조회해 옵니다. |
model.addAttribute() | model에 담아 view에 전달합니다. |
- BoardService.java (interface)
package com.js.board.model.service;
import java.util.List;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
public interface BoardService {
/*select board list*/
public List<BoardDTO> selectBoardList(PageInfoDTO pageInfo);
/*select board*/
public BoardDTO selectById(int number);
/*update board*/
public void updateById(BoardDTO board);
/*count board*/
public int totalCount();
}
- BoardServiceImpl.java
package com.js.board.model.service;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
import com.js.board.model.repository.BoardRepository;
@Service("boardService")
public class BoardServiceImpl implements BoardService{
private final BoardRepository boardRepository;
private final SqlSessionTemplate sqlSession;
@Autowired
public BoardServiceImpl(BoardRepository boardRepository, SqlSessionTemplate sqlSession) {
this.boardRepository = boardRepository;
this.sqlSession = sqlSession;
}
/*board list를 조회할 때 사용하는 service method
* @param pageInfo 페이징 객체
* @return board List 정보
* */
@Override
public List<BoardDTO> selectBoardList(PageInfoDTO pageInfo) {
return boardRepository.selectBoardList(sqlSession, pageInfo);
}
}
- BoardRepository.java (interface)
package com.js.board.model.repository;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
public interface BoardRepository {
/*select Board List*/
public List<BoardDTO> selectBoardList(SqlSessionTemplate sqlSession, PageInfoDTO pageInfo);
/*select Board*/
public BoardDTO selectById(int number, SqlSessionTemplate sqlSession);
/*update Board*/
public int updateById(BoardDTO board, SqlSessionTemplate sqlSession);
/*count board*/
public int totalCount(SqlSessionTemplate sqlSession);
}
- BoardRepositoryImpl.java
package com.js.board.model.repository;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
@Repository("boardRepository")
public class BoardRepositoryImpl implements BoardRepository{
/*
* board List 조회 Repository method
* @param sqlSession service에서 전달받은 sqlSessionTemplate 쿼리문 실행할 객체
* @param pageInfo 페이징 객체
* @return db에서 조회한 board List정보
* */
@Override
public List<BoardDTO> selectBoardList(SqlSessionTemplate sqlSession, PageInfoDTO pageInfo) {
return sqlSession.selectList("board.selectList", pageInfo);
}
}
이제 mapper만 남았는데 쿼리문을 살펴보고 가도록 하겠습니다.
list를 조회하는 쿼리문입니다. 최근에 insert된 게시물이 가장 위로 올라와야 하니 내림차순(BOARD_ID)으로 조회 하도록 하겠습니다.
- DESC로 조회한 쿼리문을 인라인 뷰로 사용
- 윗 쿼리문(1)을 인라인 뷰로 사용한 쿼리문에 ROWNUM 추가하여 조회
- 윗 쿼리문(2)을 인라인 뷰로 사용하여 ROWNUM을 이용하여 해당 갯수만큼 게시물 조회
SELECT
C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC;
이제 한 페이지에 보여줄 게시물 갯수(limit)가 10 이라면 10개 씩 잘라서 조회할 수 있어야 합니다. ROWNUM을 사용하도록 하겠습니다. 먼저 위 쿼리문을 인라인뷰로 사용하여 ROWNUM을 추가하도록 하겠습니다.
SELECT
ROWNUM AS RNUM
, B.BOARD_ID
, B.BOARD_CATEGORY
, B.BOARD_WRITER
, B.BOARD_TITLE
, B.BOARD_TEXT
, B.BOARD_DATE
, B.BOARD_REPLY
, B.BOARD_LEVEL
, B.BOARD_STATUS
FROM(SELECT C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC
) B;
마지막으로 위 쿼리문을 인라인 뷰로 사용하여 RNUM(ROWNUM) 을 조회 할 만큼 잘라서 조회 하도록 하겠습니다.
- 1 ~ 10 게시물
SELECT
A.RNUM
, A.BOARD_ID
, A.BOARD_CATEGORY
, A.BOARD_WRITER
, A.BOARD_TITLE
, A.BOARD_TEXT
, A.BOARD_DATE
, A.BOARD_REPLY
, A.BOARD_LEVEL
, A.BOARD_STATUS
FROM(SELECT ROWNUM AS RNUM
, B.BOARD_ID
, B.BOARD_CATEGORY
, B.BOARD_WRITER
, B.BOARD_TITLE
, B.BOARD_TEXT
, B.BOARD_DATE
, B.BOARD_REPLY
, B.BOARD_LEVEL
, B.BOARD_STATUS
FROM(SELECT C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC
) B
)A
WHERE A.RNUM BETWEEN 1 AND 10;
- 11 ~20 게시물
SELECT
A.RNUM
, A.BOARD_ID
, A.BOARD_CATEGORY
, A.BOARD_WRITER
, A.BOARD_TITLE
, A.BOARD_TEXT
, A.BOARD_DATE
, A.BOARD_REPLY
, A.BOARD_LEVEL
, A.BOARD_STATUS
FROM(SELECT ROWNUM AS RNUM
, B.BOARD_ID
, B.BOARD_CATEGORY
, B.BOARD_WRITER
, B.BOARD_TITLE
, B.BOARD_TEXT
, B.BOARD_DATE
, B.BOARD_REPLY
, B.BOARD_LEVEL
, B.BOARD_STATUS
FROM(SELECT C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC
) B
)A
WHERE A.RNUM BETWEEN 11 AND 20;
위 처럼 WHERE절의 BETWEEN 의 값을 변경하여 조회하는 것 입니다. BETWEEN에 startRow 와 endRow 값이 들어가 쿼리문을 실행하는 것입니다.
- BETWEEN의 값을 startRow 와 endRow로 변경
SELECT
A.RNUM
, A.BOARD_ID
, A.BOARD_CATEGORY
, A.BOARD_WRITER
, A.BOARD_TITLE
, A.BOARD_TEXT
, A.BOARD_DATE
, A.BOARD_REPLY
, A.BOARD_LEVEL
, A.BOARD_STATUS
FROM(SELECT ROWNUM AS RNUM
, B.BOARD_ID
, B.BOARD_CATEGORY
, B.BOARD_WRITER
, B.BOARD_TITLE
, B.BOARD_TEXT
, B.BOARD_DATE
, B.BOARD_REPLY
, B.BOARD_LEVEL
, B.BOARD_STATUS
FROM(SELECT C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC
) B
)A
WHERE A.RNUM BETWEEN #{startRow} AND #{endRow};
마지막으로 작성한 쿼리문을 mapper에서 사용하도록 하겠습니다.
- Board-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
<select id="selectList" resultType="com.js.board.model.dto.BoardDTO" parameterType="com.js.board.model.dto.PageInfoDTO">
SELECT
A.RNUM
, A.BOARD_ID
, A.BOARD_CATEGORY
, A.BOARD_WRITER
, A.BOARD_TITLE
, A.BOARD_TEXT
, A.BOARD_DATE
, A.BOARD_REPLY
, A.BOARD_LEVEL
, A.BOARD_STATUS
FROM(SELECT ROWNUM AS RNUM
, B.BOARD_ID
, B.BOARD_CATEGORY
, B.BOARD_WRITER
, B.BOARD_TITLE
, B.BOARD_TEXT
, B.BOARD_DATE
, B.BOARD_REPLY
, B.BOARD_LEVEL
, B.BOARD_STATUS
FROM(SELECT C.BOARD_ID
, C.BOARD_CATEGORY
, C.BOARD_WRITER
, C.BOARD_TITLE
, C.BOARD_TEXT
, C.BOARD_DATE
, C.BOARD_REPLY
, C.BOARD_LEVEL
, C.BOARD_STATUS
FROM BOARD C
WHERE BOARD_LEVEL = 0
AND BOARD_STATUS = 'N'
ORDER BY C.BOARD_ID DESC
) B
)A
WHERE A.RNUM BETWEEN #{startRow} AND #{endRow}
</select>
</mapper>
이제 실행 후 잘 동작하는지 확인해 보도록 하겠습니다.
- console
INFO : jdbc.sqlonly - SELECT COUNT(*) FROM BOARD WHERE BOARD_LEVEL = 0 AND BOARD_STATUS = 'N'
INFO : jdbc.resultsettable -
|---------|
|count(*) |
|---------|
|157 |
|---------|
INFO : jdbc.sqlonly - SELECT A.RNUM , A.BOARD_ID , A.BOARD_CATEGORY , A.BOARD_WRITER , A.BOARD_TITLE , A.BOARD_TEXT
, A.BOARD_DATE , A.BOARD_REPLY , A.BOARD_LEVEL , A.BOARD_STATUS FROM(SELECT ROWNUM AS RNUM
, B.BOARD_ID , B.BOARD_CATEGORY , B.BOARD_WRITER , B.BOARD_TITLE , B.BOARD_TEXT , B.BOARD_DATE
, B.BOARD_REPLY , B.BOARD_LEVEL , B.BOARD_STATUS FROM(SELECT C.BOARD_ID , C.BOARD_CATEGORY
, C.BOARD_WRITER , C.BOARD_TITLE , C.BOARD_TEXT , C.BOARD_DATE , C.BOARD_REPLY , C.BOARD_LEVEL
, C.BOARD_STATUS FROM BOARD C WHERE BOARD_LEVEL = 0 AND BOARD_STATUS = 'N' ORDER BY C.BOARD_ID
DESC ) B )A WHERE A.RNUM BETWEEN 1 AND 10
INFO : jdbc.resultsettable -
|---------|---------|---------------|-------------|------------|-----------|-----------|------------|------------|-------------|
|rnum |board_id |board_category |board_writer |board_title |board_text |board_date |board_reply |board_level |board_status |
|---------|---------|---------------|-------------|------------|-----------|-----------|------------|------------|-------------|
|[unread] |157 |0 |sj |제목157 |내용157 |2021-06-17 |[null] |0 |N |
|[unread] |156 |0 |sj |제목156 |내용156 |2021-06-17 |[null] |0 |N |
|[unread] |155 |0 |sj |제목155 |내용155 |2021-06-17 |[null] |0 |N |
|[unread] |154 |0 |sj |제목154 |내용154 |2021-06-17 |[null] |0 |N |
|[unread] |153 |0 |sj |제목153 |내용153 |2021-06-17 |[null] |0 |N |
|[unread] |152 |0 |sj |제목152 |내용152 |2021-06-17 |[null] |0 |N |
|[unread] |151 |0 |sj |제목151 |내용151 |2021-06-17 |[null] |0 |N |
|[unread] |150 |0 |sj |제목150 |내용105 |2021-06-17 |[null] |0 |N |
|[unread] |149 |0 |sj |제목149 |내용149 |2021-06-17 |[null] |0 |N |
|[unread] |148 |0 |sj |제목148 |내용148 |2021-06-17 |[null] |0 |N |
|---------|---------|---------------|-------------|------------|-----------|-----------|------------|------------|-------------|
INFO : com.js.board.controller.BoardController - list : [BoardDTO [board_id=157, board_category=0, board_writer=sj, board_title=제목157, board_text=내용157, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=156, board_category=0, board_writer=sj, board_title=제목156, board_text=내용156, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=155, board_category=0, board_writer=sj, board_title=제목155, board_text=내용155, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=154, board_category=0, board_writer=sj, board_title=제목154, board_text=내용154, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=153, board_category=0, board_writer=sj, board_title=제목153, board_text=내용153, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=152, board_category=0, board_writer=sj, board_title=제목152, board_text=내용152, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=151, board_category=0, board_writer=sj, board_title=제목151, board_text=내용151, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=150, board_category=0, board_writer=sj, board_title=제목150, board_text=내용105, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=149, board_category=0, board_writer=sj, board_title=제목149, board_text=내용149, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N], BoardDTO [board_id=148, board_category=0, board_writer=sj, board_title=제목148, board_text=내용148, board_date=2021-06-17, board_reply=0, board_level=0, board_status=N]]
INFO : com.js.board.controller.BoardController - pageInfo : PageInfoDTO [pageNo=1, totalCount=157, limit=10, buttonAmount=10, maxPage=16, startPage=1, endPage=10, startRow=1, endRow=10]
잘 가져와지는 것을 볼 수 있습니다. 잊어버렸네요. 페이징 게시판을 클릭했을 때 currentPage의 값을 설정 안했습니다. 사이드 바에 currentPage의 값을 설정해주고 다시 요청해 보도록 하겠습니다.
currentPage=1 로 요청했을 때
currentPage=2 로 요청했을 때
잘 동작하는 것을 확인할 수 있습니다.
만약에 currentPage(key)의 value값으로 문자열로 요청하면 어떻게 될지 생각해 보았습니다.
NumberFormatException이 발생하였는데요, 이러한 요청 시 문제를 처리하기 위해 param을 String으로 바꾸면 되지 않을까? 란 생각을 하게 되었습니다. 만약, 문자열이 넘어오게 된다면 첫 번째 페이지를 보여주도록 하겠습니다. 바꿔보고 잘 동작하는지 확인해 보도록 하겠습니다.
- BoardController.java
package com.js.board.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.js.board.model.dto.BoardDTO;
import com.js.board.model.dto.PageInfoDTO;
import com.js.board.model.service.BoardService;
import com.js.board.paging.Pagenation;
@Controller
public class BoardController {
private static final Logger log = LoggerFactory.getLogger(BoardController.class);
private final BoardService boardService;
@Autowired
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
/*
*board 리스트 페이지 접속 시 controller
*@param model Model 객체
*@param currentPage 페이징 번호
*@return viewResolver에 전달할 view path String
*/
@RequestMapping(value ="/board", method = RequestMethod.GET)
public String board(Model model, @RequestParam(value="currentPage", defaultValue = "1", required = false) String currentPage) {
//====================변경부분==================================
/*기본 페이징 값 1로 설정*/
int pageNo = 1;
int page;
try {
page = Integer.parseInt(currentPage);
}catch(NumberFormatException e){
page = 1;
}
/*넘어온 currentPage 값이 0이거나 0보다 작다면 기본적으로 1로 설정*/
if(page <= 0) {
pageNo = 1;
}else {
pageNo = page;
}
//============================================================
/*db에 저장되어 있는 게시판 갯수 조회*/
int totalCount = boardService.totalCount();
/*페이징 계산 후 pageInfo에 값을 담는다.*/
PageInfoDTO pageInfo = Pagenation.getPageInfo(pageNo, totalCount);
/*starRow, endRow에 해당하는 list 조회*/
List<BoardDTO> list = boardService.selectBoardList(pageInfo);
/*model에 담기*/
model.addAttribute("list", list);
model.addAttribute("pageInfo", pageInfo);
log.info("list : {}", list);
log.info("pageInfo : {}", pageInfo);
return "board/board";
}
}
try catch문을 사용하여 코드를 수정 하였습니다. 예상처럼 동작하는지 요청해 보도록 하겠습니다.
다른 값으로 요청 시 첫 페이지(currentPage=1)가 보여지는 것을 확인 할 수 있었습니다.
다음에는 넘어온 값을 이용하여 jsp를 구성하도록 하겠습니다.