페이징은 사용자에게 데이터를 제공할 때, 전체 데이터 중의 일부르 보여주는 방식이다
예를 들어서, 사용자의 쪽지 목록이 100개라고 할 때, 한 페이지에 쪽지 100개를 전부 보여주면 로딩 속도가 느려지는 단점이 있을 수 있다
페이징은 이러한 문제점을 해결할 수 있으며 검색 기능을 통해 원하는 데이터를 조회할 수 있다
페이징과 검색을 처리하기 위해서는 몇 가지 파라미터가 필요하다
data class PagingDto(
var page: Int = 1, //현재 페이지 번호
var recordSize: Int = 10, //페이지 당 출력할 데이터 개수
var pageSize: Int = 10, //화면에 보여줄 페이지 사이즈
) {
val offset: Int
get() = (page - 1) * recordSize
}
객체가 생성되는 시점에 현재 페이지 번호를 1로, 페이지 당 출력할 데이터 개수를 10으로 지정해준다
offset의 get메서드를 통해 쿼리문 안의 LIMIT 구문의 시작부분을 지정해 준다
매퍼를 작성해준다
@Mapper
@Repository
interface ProfileMapper {
//작성 글을 불러올 때 페이징 처리 된 상태로 보여주도록 하는 메서드
fun findAllBoardByIdWithPaging(userId: Int, pagingDto: PagingDto): List<BoardDTO>
//userId에 일치하는 유저의 게시글 갯수를 반환하는 메서드
fun getBoardCount(userId: Int): Int
}
각 매퍼에 매치되는 xml mapper 또한 작성한다
<?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="com.tovelop.maphant.mapper.ProfileMapper">
<select id="findAllBoardByIdWithPaging" resultType="BoardDTO">
SELECT categoryId,
userId,
postId,
type,
title,
body,
state,
isAnonnymous,
creatAt,
modifiedAt
FROM board
WHERE userId = #{user_id}
ORDER BY created_at
LIMIT #{params.recordSize} OFFSET #{params.offset}
</select>
<select id="getBoardCount">
SELECT COUNT(*)
FROM board
WHERE userId = #{user_id}
</select>
</mapper>
리미트 구문은 SELECT 쿼리와 함께 사용되며 테이블의 데이터를 조회 시 한계를 지정할 수 있다
findAllBoardByIdWithPaging 쿼리의 params.offset은 pagingDto의 get()메서드가 리턴하는 (page-1)*recordSize를 계산한 값이다
예를 들어 page를 1로 recordSize를 10으로 가정한다면 (1-1)*10 = 0이다
즉, 현재 페이지 번호가 1이라면 쿼리는 LIMIT 0, 10 으로 실행되어 0부터 10까지의 데이터를 보여준다
페이지네이션이란 웹 화면에서 페이지의 번호를 출력하는 기능이다
위에서 작성한 pagingDto 멤버 변수를 이용해 페이지 정보를 계산할 수 있다
data class Pagination(
var totalRecordCount: Int = 0, // 전체 데이터 수
var totalPageCount: Int = 0, // 전체 페이지 수
var startPage: Int = 0, // 첫 페이지 번호
var endPage: Int = 0, // 끝 페이지 번호
var limitStart: Int = 0, // LIMIT 시작 위치
var existPrevPage: Boolean = false, // 이전 페이지 존재 여부
var existNextPage: Boolean = false // 다음 페이지 존재 여부
) {
constructor(totalRecordCount: Int, params: PagingDto) : this() {
if (totalRecordCount > 0) {
this.totalRecordCount = totalRecordCount
calculation(params)
}
}
private fun calculation(params: PagingDto) {
// 전체 페이지 수 계산
totalPageCount = ((totalRecordCount - 1) / params.recordSize) + 1
// 현재 페이지 번호가 전체 페이지 수보다 큰 경우, 현재 페이지 번호에 전체 페이지 수 저장
if (params.page > totalPageCount) {
params.page = totalPageCount
}
// 첫 페이지 번호 계산
startPage = ((params.page - 1) / params.pageSize) * params.pageSize + 1
// 끝 페이지 번호 계산
endPage = startPage + params.pageSize - 1
// 끝 페이지가 전체 페이지 수보다 큰 경우, 끝 페이지에 전체 페이지 수 저장
if (endPage > totalPageCount) {
endPage = totalPageCount
}
// LIMIT 시작 위치 계산
limitStart = (params.page - 1) * params.recordSize
// 이전 페이지 존재 여부 확인
existPrevPage = startPage != 1
// 다음 페이지 존재 여부 확인
existNextPage = (endPage * params.recordSize) < totalRecordCount
}
}
화면에 페이지 번호를 보여주는 것은 html단에서 이루어진다. 이 때 html에서는 리스트의 데이터와 pagination 객체 모두를 필요로 한다
getBoardList의 findAllBoardByIdwithPaging()의 리턴 값에 pagination객체와 리스트의 데이터 모두 컨트롤러로 보내줄 수 없기 때문에 이를 위한 새로운 응답용 클래스를 작성한다
open class PagingResponse<T>(
val list: List<T> = mutableListOf(),
val pagination: Pagination?
)
fun getBoardsList(userId: Int, params: PagingDto):PagingResponse<BoardDTO>{
//getBoardCount xml 구현
val count = profileMapper.getBoardCount(userId);
if(count < 1) {
return PagingResponse(Collections.emptyList(),null);
}
//findAllBoardByIdwithPaging xml 구현
val pagination = Pagination(count,params);
val boards = profileMapper.findAllBoardByIdWithPaging(userId,params);
return PagingResponse(boards,pagination);
}
getBoardList가 뷰로 전달하는 데이터를 pagingDto로 변경해준다
@GetMapping("/board")
fun getBoardList(@ModelAttribute pagingDto: PagingDto): ResponseEntity<Response<PagingResponse<BoardDTO>>>{
val auth = SecurityContextHolder.getContext().authentication!! as TokenAuthToken
val userId:Int = auth.getUserData().id!!
return ResponseEntity.ok().body(Response.success(profileService.getBoardsList(userId,pagingDto)));
}
위에서 작성한 컨트롤러를 보면 파라미터로 받는 pagingDto 앞에 @ModelAttribute가 붙어있는 것을 알 수 있다
그래서 이에 대해서도 간단하게 다뤄보고자 한다
이 어트리뷰트는 메소드 레벨, 메소드의 파라미터 두 곳에서 사용가능하다
이는 사용자 요청시 전달하는 값을 오브젝트 형태로 매핑해주는 어노테이션이다
예를 들엇 위에서 작성한 컨트롤러에서 여러 인스턴스 변수를 담고있는 pagingDto를 파라미터로 사용하면 각각의 값들이 핸들러의 pagingDto 객체로 바인딩된다
단, Setter가 존재해야한다