반응형
목차
페이지네이션 처리 관련해서 미래의 내가 보기 위해 기록 😃
웹개발의 기초 중 기초라고 할 수 있는 페이지네이션(페이징) 처리에 대해서 간략하게 정리
그동안은 원리를 제대로 이해하지 못하고 썼다면 이제 핵심위주로 잘 정리해 보자.
시간 상 페이징 처리는 디자인은 위처럼 하나도 안 이쁘지만 데이터가 잘 나오는지 위주로 작성함
개발환경
back-end: springBoot 2.6.13(jdk 1.8)/mybatis 3.5.9/h2 database H2 2.1.214 (2022-06-13)(mode는 mySQL로 설정)
front-end: thymeleaf/javascript/jQuery
예시를 위한 테이블
CREATE TABLE t_board(
board_id bigint auto_increment,
title varchar (30),
content varchar (30),
name varchar (30),
reg_date timestamp,
update_date timestamp,
primary key(board_id)
);
테스트 데이터 넣는 것은 생략
DBMS는 mysql 기준으로 작성
쿼리에서 데이터를 제한 걸어서 가져와야 함
limit절 설명은 생략함(시작값, 가져올 데이터 건수)
쿼리는 limit만 걸어서 가져오면 된다.
다만 limit절 뒤의 parameter가 화면에서 사용자가 클릭해서 넘어온 값들로 변수 처리되어야 한다는 것
1페이지는 limit 0, 10
2페이지는 limit 10,10
3페이지는 limit 20,10 이렇게 처리가 되어야 한다.
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">
<!-- column과 vo의 field명이 상이해서 resultMap 선언 -->
<mapper namespace="paging.study.mapper.BoardMapper">
<resultMap id="boardMap" type="paging.study.domain.vo.BoardVO">
<id column="board_id" property="boardId"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="name" property="name"/>
<result column="reg_date" property="regDate"/>
<result column="update_date" property="updateDate"/>
</resultMap>
<!-- 현재 페이지 번호와 가져올 데이터 개수가 있는 Criteria를 parameter로 받아 limit절에 활용 -->
<select id="findBoardListPaging" parameterType="paging.study.domain.Criteria" resultMap="boardMap">
SELECT * FROM t_board
ORDER BY reg_date DESC
limit #{limitStart}, #{amount}
</select>
</mapper>
Criteria(쿼리의 paramer에 활용)
// 프론트에서 페이지 번호와 페이지 출력 개수를 넘기기 위한 클래스
package paging.study.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter @Setter
@ToString
public class Criteria {
private int pageNum; // 페이지 번호(현재 페이지가 몇 페이지인지)
private int amount; // 한 화면에 출력한 페이지 개수
private int limitStart; // 쿼리에서 (pageNum -1) * amount 사용하기 위한 변수
// 기본 값 세팅(현재 페이지는 1, 가져올 데이터는 10건인 경우)
public Criteria() {
this.pageNum = 1;
this.amount = 10;
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
// 쿼리에서 limit 1번 째 parameter로 쓸 값(mybatis에서 getter로 이 값을 사용)
public int getLimitStart() {
return this.limitStart = (pageNum - 1) * this.amount;
}
}
BoardVO
package paging.study.domain.vo;
import lombok.*;
import java.time.LocalDateTime;
@Getter @Setter
@ToString
@NoArgsConstructor
public class BoardVO {
private Long boardId;
private String title;
private String content;
private String name;
private LocalDateTime regDate;
private LocalDateTime updateDate;
public BoardVO(String title, String content, String name) {
this.title = title;
this.content = content;
this.name = name;
}
}
mapper interface
package paging.study.mapper;
import org.apache.ibatis.annotations.Mapper;
import paging.study.domain.Criteria;
import paging.study.domain.vo.BoardVO;
import java.util.List;
@Mapper
public interface BoardMapper {
List<BoardVO> findBoardListPaging(Criteria cri);
// 전체 카운트
@Select("SELECT COUNT(*) FROM t_board")
int findBoardCount();
}
service
package paging.study.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import paging.study.domain.Criteria;
import paging.study.domain.vo.BoardVO;
import paging.study.mapper.BoardMapper;
import java.util.List;
@Service
@Slf4j
@RequiredArgsConstructor
public class BoardService {
private final BoardMapper boardMapper;
public List<BoardVO> findBoardListPaging(Criteria cri) {
return boardMapper.findBoardListPaging(cri);
}
public int findBoardCount() {
return boardMapper.findBoardCount();
}
}
controller
package paging.study.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import paging.study.domain.Criteria;
import paging.study.domain.paging.PageMaker;
import paging.study.domain.vo.BoardVO;
import paging.study.service.BoardService;
import paging.study.service.ReplyService;
import java.util.List;
@Controller
@Slf4j
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
/**
* content list
* @param cri
* @param model
* @return
*/
@GetMapping("/board/list")
public String listPaging(@ModelAttribute("cri") Criteria cri, Model model) {
// 화면에서 cri를 받아서 목록 조회 서비스 호출시 parameter로 같이 넘김
List<BoardVO> list = boardService.findBoardListPaging(cri);
// 페이지네이션 화면 처리에 필요한 전체 목록 개수 구함
int boardCount = boardService.findBoardCount();
// model에 2 종류의 데이터(리스트, 페이지네이션 데이터)를 담아서 view에 전달
model.addAttribute("list", list);
// pageMaker에 2개의 parameter를 넘김
model.addAttribute("pageMaker", new PageMaker(boardCount, cri));
return "board/boardList";
}
}
페이지네이션 화면 처리를 위한 클래스 😎
// 프론트에서 넘긴 Criteria를 참조해서 실제 화면에서 페이지네이션 처리를 하기 위한 계산을 하는 클래스
package paging.study.domain.paging;
import lombok.Getter;
import lombok.ToString;
import paging.study.domain.Criteria;
@Getter // 외부에서 값 변경 불가하도록 setter는 선언하지 않음
@ToString
// 페이지네이션 화면에서 필요한 데이터 계산하는 클래스
public class PageMaker {
private int startPage; //페이징 화면 하단의 시작 번호(5페이지라고 하면 [1][2][3][4][5] 여기서 제일 첫 번 째 시작번호)
private int endPage; //페이징 화면 하단의 끝 번호(5페이지라고 하면 [1][2][3][4][5] 여기서 제일 끝 번호)
private boolean prev, next; // 이전, 다음 존재 여부
private int totalCount; // 전체 게시글 수
private Criteria cri; // 프론트에서 전달하는 pageNum(현재 페이지), amount(출력 페이지) 전달 역할
// 게산식에 필요한 게 전체 카운트, 그리고 현재 페이지와 페이지 개수
// 2개 parameter를 받아서 아래에서 계산 처리(공식이니 외울 필요는 없고 이런식으로 구하는 구나라고 이해하면 됨)
public PageMaker(int totalCount, Criteria cri) {
this.totalCount = totalCount;
this.cri = cri;
this.endPage = (int)(Math.ceil(cri.getPageNum()/10.0)) * 10;
this.startPage = this.endPage - 9;
// 실제 끝 번호
int realEnd = (int)(Math.ceil((totalCount * 1.0) / cri.getAmount()));
// 만일 80개의 데이터가 있을 때 끝번호는 10이 아니라 8이 되어야 하기에 실제 끝번호와 비교해서 실제 끝 번호로 치환
if(realEnd < this.endPage) {
this.endPage = realEnd;
}
// 이전, 다음
this.prev = this.startPage > 1;
this.next = this.endPage < realEn
}
}
view(thymeleaf)
<!-- 게시글 목록 영역 start -->
<div>
<table class="table table-striped">
<thead>
<tr>
<th>아이디</th>
<th>제목</th>
<th>내용</th>
<th>이름</th>
</tr>
</thead>
<tbody>
<!-- 게시글 목록 루프 돌며 표시 -->
<tr th:each="item : ${list}">
<td th:text="${item.boardId}"></td>
<td th:text="${item.title}"></td>
<td th:text="${item.content}"></td>
<td th:text="${item.name}"></td>
</tr>
</tbody>
</table>
</div>
<!-- // 게시글 목록 영역 end -->
<!-- 게시판 하단 페이지네이션 영역 start -->
<nav aria-label="Page navigation">
<ul class="pagination">
<!-- prev -->
<li class="page-item" th:if="${pageMaker.prev} == true">
<a class="page-link" th:href="@{/board/list(pageNum=${pageMaker.startPage}-1)}">Prev</a>
</li>
<!-- pageMaker의 startPage부터 endPage까지 루프, a태그의 href에 idx를 링크(get방식으로 pageNum을 붙여서) -->
<li class="page-item" id="paginate_btn" th:each="idx: ${#numbers.sequence(pageMaker.startPage, pageMaker.endPage)}" th:classappend="${pageMaker.cri.pageNum} == ${idx} ? active : null">
<a class="page-link" th:href="@{/board/list(pageNum=${idx})}" th:text="${idx}"></a>
</li>
<!-- next -->
<li class="page-item" th:if="${pageMaker.next} == true and ${pageMaker.endPage > 0}">
<a class="page-link" th:href="@{/board/list(pageNum=${pageMaker.endPage}+1)}">Next</a>
</li>
</ul>
</nav>
<!-- // 게시판 하단의 페이지네이션 영역 end -->
이렇게 하면 아래처럼 못생긴 페이지네이션 처리가 된 게시판 목록이 표시된다.
데이터가 잘 나오는지 확인 후 아래처럼 디자인을 입히면 된다.
테스트를 위해 급하게 만든거라 오탈자가 있거나 오류가 있을 수 있으니 알려주시면 감사드리겠습니다.
반응형
'IT > development' 카테고리의 다른 글
[jQuery]radio/checkbox 체크 여부 확인 (0) | 2022.11.19 |
---|---|
[IDE/SVN]eclipse svn branch 생성 (0) | 2022.11.19 |
[spring boot/mybatis] 쿼리 로그 정렬 및 기타(feat. log4jdbc) (0) | 2022.11.19 |
[mybatis] map을 list로 받아서 화면에 그리기 (0) | 2022.11.19 |
[IDE]dbeaver 한글 깨짐 조치 (0) | 2022.11.18 |