게시글 목록 출력, 작성, 수정, 삭제 등 일반 기능들은 다 구현했다.
그러나 현 상태에서 글 목록을 출력하게 되면, 한 페이지에 모든 글들이 출력된다. 따라서 페이지 당 출력을 조절하고, 이를 네비게이션으로 이동할 수 있는 ‘pagination’을 구현해보자.
페이지 네비게이터를 만드는 방식은 개발자가 자신의 페이지 구성에 따라 달라진다. 그래서 누군가는 front에서 다 구성되게 하는 경우도 있고, 위치나 모양도 제각각이지만 가장 기본은 back에서 만드는 것이다.
이 기능이 어려운 것은, 설정값에 따라 알고리즘이 달라진다. 하지만 기능 자체는 크게 2가지로 구분된다.
페이지 간 이동은 단순한 HTML을 통한 이동을 의미한다. 다르게 말하면 페이지를 클릭 할 때마다 컨트롤러로 요청을 보내, forward
로 UI가 변화가 일어나게 해야하는 것이다.
따라서 알고리즘을 통해 DAO에서 우리가 출력 JSP에 뿌려줄 양식을 만들어줘야 한다.
int recordTotalCount = this.totalData();
int recordCountPerPage = 10;
int naviCountPerPage = 10;
int pageTotalCount = 0;
private int totalData() throws Exception{
String sql = "select count(*) from board";
try(Connection conn = this.getConnection();
PreparedStatement pstat = conn.prepareStatement(sql);){
ResultSet rs = pstat.executeQuery();
rs.next();
return rs.getInt(1);
}
}
// pageTotalCount 공식
- 총 데이터 % 출력 데이터 > 0
총 데이터 / 출력 데이터 + 1
if(recordTotalCount % recordCountPerPage > 0) {
pageTotalCount = recordTotalCount / recordCountPerPage +1;
}
- 총 데이터 % 출력 데이터 = 0
총 데이터 / 출력 데이터
else {
pageTotalCount = recordCountPerPage / recordCountPerPage;
}
int currentPage = cpage;
if (currentPage < 1) {
currentPage = 1;
} else if (currentPage > pageTotalCount) {
currentPage = pageTotalCount;
}
- 네비게이션의 시작-끝 페이지 설정
// 네비 시작 : 현 페이지가 페이지 당 네비의 개수의 배수면, 현 페이지보다 커진다.
int startNavi = (currentPage-1)/naviCountPerPage*naviCountPerPage+1;
// 네비 끝 : 시작점은 빼준다.
int endNavi = startNavi + naviCountPerPage -1;
if (startNavi < 0) { // 0보다 크면 1로 세팅
startNavi = 1;
} else if(endNavi > pageTotalCount) { // 총 페이지 == 마지막 페이지
endNavi = pageTotalCount;
}
- 다음 네비게이션으로 넘기는 기능
// 스위치처럼 키고 끄는 원리
boolean preNavi = true;
boolean nextNavi = true;
if (startNavi == 1) {
preNavi = false; // 시작 페이지의 이전은 없다.
}
if (endNavi >= pageTotalCount) {
nextNavi = false; // 마지막 페이지의 이후는 없다.
}
모든 세팅이 끝났다면, 최종적으로 JSP에서 출력될 수 있도록 바꿔줘야 한다.
곧 DAO에서 출력 양식을 만들어서 반환해주는 것이다. 이를 위해서 StringBuilder로 문자열을 만들어서 반환한다.
StringBuilder sbr = new StringBuilder();
// 이전
if (preNavi) {
// html을 포함한 문자열을 넘겨주고, 이를 통해 컨트롤러에서 cpage의 값을 받아
// 다음 클릭 시 다음 페이지를 요청하도록 한다.
sbr.append("<a href='list.board?cpage="+(startNavi-1) +"'>" + "< " + "</a>");
}
// 반복문으로 네비게이션을 만든다.
for (int i=startNavi; i<endNavi; i++) {
// 현재 페이지는 따로 표시되게 한다.
if(i == currentPage) {
sbr.append("<a href='list.board?cpage="+i+"'>" + "[ " + i + " ] " + "</a>");
} else {
sbr.append("<a href='list.board?cpage="+i+"'> " + i + " </a>");
}
}
// 이후
if (nextNavi) {
sbr.append("<a href='list.board?cpage="+(endNavi+1) +"'>" + " >" + "</a>");
}
DAO → Controller → JSP
String pageNavi = bDAO.getPageNavi(cpage);
request.setAttribute("navi", pageNavi);
request.getRequestDispatcher("/board/boardlist.jsp").forward(request, response);
<td colspan=5 align=center id="page">
${navi}
</td>
네비게이션 세팅이 끝났으면, 이제 네비게이션에 맞게 글이 출력되도록 설정해야 한다. 곧, 기존 설정에 출력의 능동성을 부여하는 과정이다.
실제 건드리는 부분은 글 목록에서 출력되는 글만 조절하면 된다.
한 페이지에 10개의 글을 출력한다 할 때, 현재 1페이지라면 DB에서 1~10번 글이, 2페이지 일 때 11~20, 3페이지 21~30 이런 식으로 출력된다.
그리고 클라이언트로부터는 현 페이지가 우리가 연결해둔 링크를 통해 get방식으로 받아올 수 있다. 따라서 다음과 같이 공식을 잡을 수 있다.
int start = cpage*naviCountPerPage - (cpage*naviCountPerPage-1);
int end = cpage*cpage*naviCountPerPage;
기준을 잡았다면, 쿼리로 받아보기만 하면 된다. 이때, 몇 가지 고려해야한다.
① 순서 잡기 : row_number() over(order by seq desc)
② select에서 *
을 집어 넣으면 다른 컬럼을 못 넣는다. → board.*
로 빼줘야됨.
③ DQL의 실행 순서상 where이 select보다 몬저 온다. → 서브쿼리 사용
select * from (select row_number() over(order by seq desc) line, board.* from board) where line between start and end;
try(
Connection con = this.getConnection();
PreparedStatement pstat = con.prepareStatement(sql);
){
pstat.setInt(1, start);
pstat.setInt(2, end);
try(
ResultSet rs = pstat.executeQuery();
){
List<BoardDTO> list = new ArrayList<BoardDTO>();
while (rs.next()) {
int seq = rs.getInt("seq");
String title = rs.getString("title");
String article = rs.getString("content");
String writer = rs.getString("writer");
Date writerDate = rs.getDate("write_date");
int viewCount = rs.getInt("view_count");
BoardDTO dto = new BoardDTO(seq, writer, title, article, writerDate, viewCount);
list.add(dto);
}
return list;
}
}
이렇게 세팅을 해두면, 페이지 정보가 없기 때문에 컨트롤러에서 글 출력 페이지를 설정해줄 수 없다. 따라서 기존에 글 출력으로 들어오는 모든 요청에 페이지 값을 세팅해줘야 한다.