본문 바로가기
Spring/게시판프로젝트

[Spring] 게시판 프로젝트13 - 페이징 처리1

by 태옹 2021. 7. 20.

프로젝트용으로 데이터가 그렇게 많지 않기 때문에 페이징 처리를 고려하지 않을 수도 있지만 실 서비스에서는 대량의 데이터 조회를 위해 페이징 처리가 필수적이다. 이번 실습에서는 게시판에 페이징 처리를 해볼 것이다.

 

페이징 처리를 위한 sql쿼리를 작성할 때 hint에 대해서 이해할 필요가 있으니, 아래의 게시물에서 먼저 실행계획과 hint를 이해하고 진행하는 것을 권장한다. 

 

2021.07.20 - [Database] - [Oracle] 실행 계획(Execution plan)과 hint사용의 필요성

 

[Oracle] 실행 계획(Execution plan)과 hint사용의 필요성

스프링 개발을 하면서 이제 페이징 처리 단계에 다다랐는데, 페이징 처리는 SQL쿼리 작성에 대해 신중히 생각해볼 필요성이 있다. 일반적으로 우리는 게시물 양이 많은 경우 페이지를 나누게 된

taetoungs-branch.tistory.com

 

그럼 이제 여기에 페이징 처리를 위한 작업을 진행해보자.


 

ROWNUM을 사용하여 쿼리 작성

 

페이지 처리를 위해 ROWNUM이라는 키워드를 사용해서 순번을 붙여본다.

ROWNUM은 SQL 실행 결과에 넘버링을 하는 것과 같다. 그래서 위의 쿼리문을 그대로 가져와서 ROWNUM을 적용해보면 다음과 같은 쿼리를 작성할 수 있다.

select /*+ INDEX_DESC (tbl_board tbl_board_pk)*/ rownum, bno, title, content 
from tbl_board;

그럼 최신순으로 정렬된 데이터에 ROWNUM이라는 컬럼이 새로 추가되어 번호를 매긴 것을 확인할 수 있다.

 

이를 10개의 게시물을 보여주는 쿼리로 작성하면 where rownum>=10;를 뒤에 붙여주면 될 것이다.

 

그러면 2페이지를 보는 쿼리를 작성할 때 10보다 크고 20보다 작거나 같은 데이터를 가져오는 작업은 아래처럼 작성하면 될 것이라고 예상하게 된다.

select /*+ INDEX_DESC (tbl_board tbl_board_pk)*/ rownum, bno, title, content 
from tbl_board
where ROWNUM > 10  and ROWNUM <= 20;

그러나 실행해보면 아무런 데이터도 가져오지 않는다.

 

여기서 ROWNUM이 뭔지에 대해 다시 생각할 필요가 있는데, ROWNUM은 이미 SQL문이 처리가 되어서 TBL_BOARD에 나오는 값을 넘버링 한 것이기 때문에, 10번부터 19번을 찾는 것이 아니라 조회하는 데이터의 ROWNUM은 무조건 1이 되게 된다. 그러나 where절에 의해 ROWNUM이 1인 데이터는 무효화되고, 다음에 나오는 조건에 맞는 데이터도 ROWNUM이 1, where절에 의해 무효화, 다음 차례 데이터의 ROWNUM이 1, where절에 의해 무효화...임을 반복해서 결국 ROWNUM은 항상 1로 만들어지고 없어지는 과정 끝에 반환 결과는 아무것도 나오지 않게 된다.

 

그럼 우리는 SQL에 ROWNUM조건이 1이 포함되도록 수정할 필요가 있다.

select bno, title, content, writer, regdate, updatedate
from (
    select /*+ INDEX_DESC(tbl_board tbl_board_pk) */
    rownum rn, bno, title, content, writer, regdate, updatedate
    from tbl_board
    where rownum <= 20
)
where rn > 10

이런식으로 select문 안쪽에 select문을 넣어주는 인라인뷰를 적용하게 되면 2페이지의 데이터 처리가 가능하다.

 

그럼 sql쿼리를 작성했으니 스프링에 적용하러 가보자.

 


 

페이징 처리

페이징 처리를 위해 페이지 번호와 한 페이지당 몇 개의 데이터를 보여줄 것인지가 결정되어야 한다. 이 프로젝트에서는 게시물밖에 없으니 페이징을 게시물에만 적용하겠지만 다른 프로젝트에서는 여러 VO에서 사용할 수 있기 때문에 Criteria객체로 묶어서 전달하는 방식을 이용한다.

 

Domain 수정

VO가 있는 domain패키지에 Criteria 클래스를 생성한다.

Criteria.java

package com.taeong.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Criteria {
	private int pageNum;
	private int amount;
	
	public Criteria(){
		this(1,10);
	}
	
	public Criteria(int pageNum, int amount) {
		this.pageNum = pageNum;
		this.amount = amount;
	}
}

 

mapper패키지의 BoardMapper인터페이스에 Criteria클래스를 파라미터로 사용하는 메소드를 추가한다.

//페이징 처리 후 리스트 조회
public List<BoardVO> getListWithPaging(Criteria cri);

BoardMapper.xml에 페이징 처리를 구현할 sql쿼리를 넣어준다.

<select id="getListWithPaging" resultType="com.taeong.domain.BoardVO">
	<![CDATA[
        select *
        from (
            select /*+ INDEX_DESC(tbl_board tbl_board_pk) */
            rownum rn, bno, title, content, writer, regdate, updatedate
            from tbl_board
            where rownum <= 20
        )
        where rn > 10
	]]>
</select>

아까 작성한 sql쿼리는 단순히 2페이지만 조회할 수 있기 때문에 조건에 따라 각각 다른 페이지를 보여줄 수 있도록 수정해주어야 한다. 

위의 쿼리에서 rownum <= 20은 페이지번호 * 페이지양 과 같고, rn > 10은 (페이지번호 - 1)*페이지양 과 같은 원리로작동한다. #{변수명}으로 수정해주도록 한다.

<select id="getListWithPaging"
		resultType="com.taeong.domain.BoardVO">
	<![CDATA[
	select *
	from (
		select /*+ INDEX_DESC(tbl_board tbl_board_pk) */
		rownum rn, bno, title, content, writer, regdate, updatedate
		from tbl_board
		where rownum <= #{pageNum} * #{amount}
	)
	where rn > (#{pageNum} - 1) * #{amount}
	]]>
</select>

 

BoardMapperTests.java에 테스트 코드를 작성해서 결과가 잘 뜨는지 확인한다.

@Test
public void testPaging() {
    Criteria cri = new Criteria();
    cri.setPageNum(3);
    cri.setAmount(10);

    List<BoardVO> list = mapper.getListWithPaging(cri);
    list.forEach(board -> log.info(board));
}

 

Service 수정

기존에 Service클래스에 있는 getList메소드를 다음과 같이 수정해준다.

BoardService.java

public List<BoardVO> getList(Criteria cri);	//페이징처리 후 리스트 조회

 

BoardServiceImpl.java

@Override
public List<BoardVO> getList(Criteria cri) {
    log.info("[SERVICE]getList..."+cri);
    return mapper.getListWithPaging(cri);
}

 

Service계층 테스트는 다음 코드로 진행해볼 수 있다.

@Test // 리스트 조회
public void testGetList() {
	service.getList(new Criteria(2,10)).forEach(board -> log.info(board));
}

 

Controller 수정

Controller에도 Criteria객체를 반영한 코드로 수정해준다.

BoardController.java

@GetMapping("/list") // 조회하는 경우에는 get방식을 사용
public void list(Criteria cri, Model model) {
    log.info("[CONTROLLER]get list..."+cri);
    model.addAttribute("list", service.getList(cri));
}

 

Controller 테스트는 다음 코드로 진행해볼 수 있다. param()메소드를 사용하여 매개변수를 임의로 넣어준다.

BoardControllerTests.java

@Test
public void testList() throws Exception {
    log.info("[CONTROLLER]testList : " + mockMvc.perform(MockMvcRequestBuilders.get("/board/list")
        .param("pageNum","2")
        .param("amount","50"))
    .andReturn()
    .getModelAndView()
    .getModelMap());
}

 

제이유닛 테스트를 진행했을 때 에러가 나지 않는다면 성공적이다🙋‍♀️

 


 

 

 

댓글