👉 ROWNUM은 정렬 전에 붙고, 바로 필터링 어려워서 서브쿼리 + 별칭이 필수!
정렬 전에 ROWNUM이 부여됨
ROWNUM은 쿼리 실행 초기에 붙기 때문에
→ ORDER BY 이후 원하는 순번을 가져오기 어렵다.
ROWNUM > n은 바로 안 됨
ROWNUM은 1부터 부여되므로
→ WHERE ROWNUM > 10 같은 조건은 항상 결과 없음
→ 반드시 서브쿼리로 감싸야 함
SELECT * FROM (
SELECT ROWNUM AS rn, A.*
FROM (
SELECT * FROM 게시판 ORDER BY 작성일 DESC
) A
WHERE ROWNUM <= 20
)
WHERE rn >= 11;
rn 부여rn 기준으로 페이징 처리package org.zerock.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
@Getter
public class Criteria {
private int pageNum;
private int amount;
private String type;
private String keyword;
public Criteria() {
this(1, 10);
}
public Criteria(int pageNum, int amount) {
this.pageNum = pageNum;
this.amount = amount;
}
public String[] getTypeArr() {
return type == null? new String[] {}: type.split("");
}
}
<select id="getListWithPaging" resultType="org.zerock.domain.BoardVO">
<![CDATA[
select bno, title, content, writer, regdate, updatedate
from (
select /*+INDEX_DESC(tbl_board pk_board) */ -- 힌트 (최근 글 순으로 rownum을 부여함)
rownum rn,
bno, title, content, writer, regdate, updatedate
from tbl_board
where rownum <= #{pageNum} * #{amount} -- 가져올 최대 행 수 제한 (ex. 2페이지일 경우, 상위 20개까지)
)
where rn > (#{pageNum} -1) * #{amount} -- 하위 범위 제한 (ex. 2페이지라면 11~20번 rownum)
]]>
</select>
@Test
public void testPaging() {
Criteria cri = new Criteria();
cri.setPageNum(1); // 가장 최근에 등록된 글 한페이지
cri.setAmount(8); // 페이지 안에 글 8개 담기
List<BoardVO> list = mapper.getListWithPaging(cri);
list.forEach(board -> log.info(board.getBno()));
}
INFO : jdbc.resultsettable -
|----|------|--------|-------|----------------------|----------------------|
|bno |title |content |writer |regdate |updatedate |
|----|------|--------|-------|----------------------|----------------------|
|41 |ㄹㄹ |ㄹㄹ |ㄹㄹ |2025-04-21 10:37:12.0 |2025-04-21 10:37:12.0 |
|40 |ㅎㅎ |ㅎㅎ |ㅎㅎ |2025-04-21 10:37:08.0 |2025-04-21 10:37:08.0 |
|39 |pp |pp |pp |2025-04-21 10:36:22.0 |2025-04-21 10:36:22.0 |
|38 |oo |oo |oo |2025-04-21 10:36:17.0 |2025-04-21 10:36:17.0 |
|37 |jj |jj |jj |2025-04-21 10:36:14.0 |2025-04-21 10:36:14.0 |
|36 |aa |aaa |aa |2025-04-21 10:36:10.0 |2025-04-21 10:36:10.0 |
|35 |dd |dd |dd |2025-04-21 10:36:07.0 |2025-04-21 10:36:07.0 |
|34 |ff |ff |ff |2025-04-21 10:36:04.0 |2025-04-21 10:36:04.0 |
|----|------|--------|-------|----------------------|----------------------|
<select id="getListWithPaging"
resultType="org.zerock.domain.BoardVO">
<![CDATA[
select
bno, title, content, writer, regdate, updatedate
from
(
select /*+INDEX_DESC(tbl_board pk_board) */
rownum rn, bno, title, content, writer, regdate, updatedate
from
tbl_board
where
]]>
...
public List<BoardVO> getListWithPaging(Criteria cri);
public List<BoardVO> getList();
public List<BoardVO> getList(Criteria cri);
@Override
public List<BoardVO> getList() {
log.info("getList.............");
return mapper.getList();
}
@Override
public List<BoardVO> getList(Criteria cri) { // getList 이름은 같지만 다르다! (파라미터가 다름!)
return mapper.getListWithPaging(cri); // 이때는 디폴트생성자 this(1,10)값이 자동넘어감
}
@GetMapping("/list")
public void getList(Criteria cri, Model model) {
model.addAttribute("list", service.getList(cri));
}
[http://localhost:8081/board/list?pageNum=3&amount=10 (가장 처음입력 글)]
[http://localhost:8081/board/list?pageNum=1&amount=5 (가장 최근입력 글 5개)]
<c:if test="${pageMaker.prev}">
<li class="paginate_button previous"><a
href="${pageMaker.startPage -1}">Previous</a></li>
</c:if>
<c:forEach var="num" begin="${pageMaker.startPage}"
end="${pageMaker.endPage}">
<li class="paginate_button ${pageMaker.cri.pageNum == num ? "active":""} ">
<a href="${num}">${num}</a>
</li>
</c:forEach>
<c:if test="${pageMaker.next}">
<li class="paginate_button next"><a
href="${pageMaker.endPage +1 }">Next</a></li>
</c:if>
@Getter
@ToString
public class PageDTO {
private int startPage;
private int endPage;
private boolean prev, next;
private int total;
private Criteria cri;
public PageDTO(Criteria cri, int total) {
this.cri = cri;
this.total = total;
this.endPage = (int) (Math.ceil(cri.getPageNum() / 10.0)) * 10;
this.startPage = this.endPage - 9;
int realEnd = (int) (Math.ceil((total * 1.0) / cri.getAmount()));
if (realEnd <= this.endPage) {
this.endPage = realEnd;
}
this.prev = this.startPage > 1;
this.next = this.endPage < realEnd;
}
}
@GetMapping("/list")
public void getList(Criteria cri, Model model) {
model.addAttribute("list", service.getList(cri));
model.addAttribute("pageMaker", new PageDTO(cri, 25));
}
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno,
@ModelAttribute("cri") Criteria cri,
Model model) {
model.addAttribute("board", service.get(bno));
}
// 클라이언트가 넘긴 pageNum, amount 같은 값들이 Criteria 객체에 자동으로 들어가고
// 이 cri 객체가 model에도 자동 등록돼서,
// 뷰(jsp 등)에서 ${cri.pageNum} 이렇게 바로 쓸 수 있음
@GetMapping("/modify")
public void modify(@RequestParam("bno") Long bno,
@ModelAttribute("cri") Criteria cri,
Model model) {
model.addAttribute("board", service.get(bno));
}
@PostMapping("/modify")
public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri) {
service.modify(board);
return "redirect:/board/list?pageNum=" + cri.getPageNum()
+ "&amount=" + cri.getAmount();
}
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri) {
service.remove(bno);
return "redirect:/board/list?pageNum=" + cri.getPageNum()
+ "&amount=" + cri.getAmount();
}
}
@ModelAttribute란?데이터를 자동으로 객체에 바인딩,
해당 데이터를 뷰(jsp 등)에서도 쓸 수 있도록 model에 자동 추가해주는 역할