KOSA Spring - 오라클 DB 페이징 처리

채정윤·2025년 4월 21일

Spring

목록 보기
15/25

ROWNUM 사용 시 주의사항

👉 ROWNUM은 정렬 전에 붙고, 바로 필터링 어려워서 서브쿼리 + 별칭이 필수!

  1. 정렬 전에 ROWNUM이 부여됨

    ROWNUM은 쿼리 실행 초기에 붙기 때문에

    ORDER BY 이후 원하는 순번을 가져오기 어렵다.

  2. 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;
  • 내부: 정렬 후 ROWNUM 부여 + 별칭 rn 부여
  • 외부: rn 기준으로 페이징 처리

페이징처리 게시판 예제

◻️ Criteria.java

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.java

	@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 |
|----|------|--------|-------|----------------------|----------------------|

🔩 resources / Mapper.xml

	<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 
  ]]>
  ...

🅱️ main / Mapper.java

	public List<BoardVO> getListWithPaging(Criteria cri);

🗺️ Service.java

	public List<BoardVO> getList();
	public List<BoardVO> getList(Criteria cri);

🖱️ ServiceImpl.java

	@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)값이 자동넘어감
	}

🧑‍💼 Controller.java

	@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개)]

📺 list.jsp

						<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>

◻️ PageDTO.java

@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;
  }
}

🧑‍💼 Controller.java

	@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에 자동 추가해주는 역할

0개의 댓글