대용량 대비
v6 는 SELECT * FROM myboard ORDER BY num DESC 로 모든 글을 한 번에 가져옴.
실험 — 100 개 INSERT 후 list 진입. 직접 부하 체감.
SQL 의 LIMIT startRow, pageSize 로 일부분만 가져옴. 스키마 변경 없음 — 쿼리만 바뀌는 단계.
<select id="selectListPaging" parameterType="map"
resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content, created_at
FROM myboard
ORDER BY num DESC
LIMIT #{startRow}, #{pageSize}
</select>
<select id="count" resultType="int">
SELECT COUNT(*) FROM myboard
</select>
v6 의 selectList 는 그대로 두고 selectListPaging 을 신설. 「예전 화면도 살아있고 새 화면이 추가된」 형태.
package com.smhrd.domain;
// 화면 페이징 계산 전용 — DB 와 1:1 매핑되는 도메인이 아님
public class Paging {
private int pageNo; // 현재 페이지 (1부터)
private int pageSize; // 한 페이지 행 수 (예: 10)
private int totalCount; // 전체 글 수
public Paging(int pageNo, int pageSize, int totalCount) {
this.pageNo = pageNo < 1 ? 1 : pageNo;
this.pageSize = pageSize;
this.totalCount = totalCount;
}
public int getStartRow() { return (pageNo - 1) * pageSize; }
public int getTotalPages() {
return (int) Math.ceil((double) totalCount / pageSize);
}
public int getPageNo() { return pageNo; }
public int getPageSize() { return pageSize; }
public int getTotalCount() { return totalCount; }
}
@Service
public class BoardService {
@Autowired private BoardMapper mapper;
private static final int PAGE_SIZE = 10;
public List<Board> selectListPaging(Paging paging) {
Map<String, Integer> param = new HashMap<>();
param.put("startRow", paging.getStartRow());
param.put("pageSize", paging.getPageSize());
return mapper.selectListPaging(param);
}
public Paging buildPaging(int pageNo) {
int total = mapper.count();
return new Paging(pageNo, PAGE_SIZE, total);
}
}
@GetMapping("/board/list")
public String list(@RequestParam(defaultValue = "1") int page,
Model model) {
Paging paging = service.buildPaging(page);
List<Board> boards = service.selectListPaging(paging);
model.addAttribute("boards", boards);
model.addAttribute("paging", paging);
return "board/list";
}
<table>
<c:forEach var="b" items="${boards}">
<tr><td>${b.num}</td><td>${b.title}</td><td>${b.writer}</td></tr>
</c:forEach>
</table>
<div class="pagination">
<c:if test="${paging.pageNo > 1}">
<a href="?page=${paging.pageNo - 1}">이전</a>
</c:if>
<c:if test="${paging.pageNo < paging.totalPages}">
<a href="?page=${paging.pageNo + 1}">다음</a>
</c:if>
</div>
<c:forEach begin="1" end="${paging.totalPages}" var="p">
<c:choose>
<c:when test="${p == paging.pageNo}">
<strong>${p}</strong>
</c:when>
<c:otherwise>
<a href="?page=${p}">${p}</a>
</c:otherwise>
</c:choose>
</c:forEach>
현재 페이지면 강조, 그 외에는 링크. 한 줄 안에 모든 페이지 숫자가 나열됩니다.
-- 더미 데이터 100 개 (MySQL)
INSERT INTO myboard(title, writer, content)
SELECT CONCAT('테스트 글 ', seq), 'tester', '본문'
FROM (
SELECT @row := @row + 1 AS seq
FROM (SELECT 0 UNION SELECT 1 UNION SELECT 2) t1,
(SELECT 0 UNION SELECT 1 UNION SELECT 2) t2,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) t3,
(SELECT @row := 0) r
) numbers
LIMIT 100;
👉 100 개 INSERT 후 v6 list 와 v7 list 비교 — UI 차이 직접 확인.
| 실수 | 증상 |
|---|---|
| OFFSET 계산 오류 | 페이지 점프 시 잘못된 데이터 |
| ORDER BY 누락 | 매 요청마다 다른 순서 (DB 별 다름) |
| page=0 처리 안 함 | OFFSET 음수 → 에러 |
| 총 페이지 수 잘못 | 마지막 페이지 빈 데이터 |
전체 글 한 번에. 글 많으면 화면 망가짐.
한 페이지 10 개씩. 페이지 번호로 이동. 글 1 만 개도 부드럽게.
LIMIT #{startRow}, #{pageSize} (MySQL)COUNT(*) 쿼리Paging 보조 클래스 — 도메인이 아닌 계산용myboard 변경 없음c:forEach begin..end 로 페이지 번호 UI