Chapter 13 MyBatis와 스프링에서 페이징 처리 - oneso123456789/2022 GitHub Wiki

MyBatis는 SQL을 그대로 사용할 수 있기 때문에 인라인뷰를 이용하는 SQL을 작성하고,
필요한 파라미터를 지정하는 방식으로 페이징 처리를 하게됨

이때 신경써야하는 점은 페이징 처리를 위해선 SQL을 실행할 때 몇 가지 파라미터가 필요하다는점임

페이징 처리를 위해서 필요한 파라미터는 다음과 같음

  1. 페이지번호(pageNum)
  2. 한 페이지당 몇 개의 데이터(amount)를 보여줄 것인지

페이지 번호와 몇 개의 데이터가 필요한지를 별도의 파라미터로 전달하는 방식보단
아예 이 데이터들을 하나의 객체로 묶어서 전달하는 방식이 확장성이 더 좋음

com.crow.domain 패키지에 Criteria 이름의 클래스를 작성함
Criteria는 기준을 의미하지만 여기선 검색의 기준으로 사용함

Criteria.class

package com.crow.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;
    }
}

Criteria 클래스의 용도는 pageNum과 amount 값을 같이 전달하는 용도지만 생성자를 통해서
기본값을 1페이지, 10개로 지정해서 처리
(내가 페이징 처리하는 경우 따라서 변경하면됨)
lombok을 이용해서 getter/setter를 생성함


13.1 MyBatis 처리와 테스트

BoardMapper는 인터페이스와 어노테이션을 이용하기 때문에 페이징 처리와 같이 경우에 따라
SQL문 구문 처리가 필요한 상황에선 복잡하게 작성됨
(SQL문이 길어지고 복잡해지면 개인적으론 XML로 처리하는것이 더 알아보기 쉽고 관리하기 쉽다는대 책 발행 년도가 19년도임 22년인 지금은 어떨지 모름)

com.crow.mapper 패키지의 BoardMapper 인터페이스에 위에 Criteria 타입을 파라미터로 사용하는 getListWithPaging() 메서드를 작성

BoardMapper 인터페이스

package com.crow.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import com.crow.domain.BoardVO;
import com.crow.domain.Criteria;

public interface BoardMapper {

// mapper.xml사용으로 인해 주석처리    @Select("select * from tbl_board where bno > 0")
    public List<BoardVO> getList();

    public List<BoardVO> getListWithPaging(Criteria cri);

    public void insert(BoardVO board);

    public void insertSelectKey(BoardVO board);

    public BoardVO read(Long bno);

    public int delete(Long bno);

    public int update(BoardVO board);

}

그리고 src/main/resources의 BoardMapper.xml에 getListWithPaging에 해당하는 태그를 추가

BoardMapper.xml의 일부

<select id="getListWithPaging" resultType="com.crow.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 
                rownum <= #{pageNum} * #{amount}
/* mybatis 사용전 = rownum <= 20 */
          
            )
        where 
            rn > (#{pageNum} -1) * #{amount}
/* MyBatis 사용전 = rn > 10 */
        ]]>
    </select>

작성된 BoardMapper.xml에서는 XML의 CDATA처리가 들어감
CDATD섹션은 XML에서 사용할 수 없는 부등호를 사용하기 위함임
XML을 사용할 경우에는 <,>는 태그로 인식하는데, 이로 인해서 생기는 문제를 막기 위함임
(< 나 >와 같은 특수문자를 시용할 수도 있긴함)

또한 인라인 뷰에선 BoardVO를 구성하는데 필요한 모든 칼럼과 ROWNUM을 RN이라는 가명을
이용해서 만들어 주고 바깥쪽에 SQL에서는 RN 칼럼을 조건으로 처리함

13.1.1 페이징 테스트와 수정

MyBatis의 #{}을 적용하기 전에 XML 설정이 제대로 동작하는지 테스트 코드를 먼저
진행하는 것이 좋음
src/test/java 내에 BoardMapperTests 클래스에 testPaging() 메서드를 추가함

BoardMapperTests.class에 메서드 추가

    @Test
    public void testPaging() {
        
        Criteria cri = new Criteria();
        
        /* 10개씩 3페이지를 가져오게 setter를 사용해서 설정함 */
        cri.setPageNum(3);
        cri.setAmount(10);
        
        List<BoardVO> list = mapper.getListWithPaging(cri);
        
        list.forEach(board -> log.info(board.getBno()));
    }

SQL에 문제가 없다는 것을 확인했다면 이제 Criteria 객체 내부의 값을 이용해서
SQL이 동작하도록 해야함

위의 SQL문에 나온 where(조건문)rownum <= 20 and rn > 10 의 정수부분의 값은
pageNum과 amount를 이용해서 조절해야 하는 값임

인라인뷰 안쪽의 조건문은 pageNum * amount의 값 즉 몇개의 결과를 가져올 것인지 인라인뷰 바깥쪽의 조건문은 (pageNum - 1) * amount은 어느 부분부터
결과를 보여줄것인지 나타냄
(내가 사용한 Criteria객체는 pageNum = 1이고 amount=10이므로 10개의 결과를 페이지
하나에 0번부터 보여달라는 SQL문이됨)

MyBatis를 통해서 SQL의 조건문으로 수정해주면됨

13.2 BoardController와 BoardService 수정

페이징 처리는 브라우저에서 들어오는 정보를 기준으로 동작하기 때문에
BoardController와 BoardService 역시 전달되는 파라미터들을 받는 형태로 수정해야함

13.2.1 BoardService 수정

BoardService는 Criteria를 파라미터로 처리하도록 BoardService 인터페이스와
BoardServiceImpl 클래스를 수정함

BoardSerivce 인터페이스 수정

package com.crow.service;

import java.util.List;

import com.crow.domain.BoardVO;
import com.crow.domain.Criteria;

public interface BoardService {

    public void register(BoardVO board);

    public BoardVO get(Long bno);

    public boolean modify(BoardVO board);

    public boolean remove(Long bno);

//    public List<BoardVO> getList();

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

인터페이스가 수정되었으니 동일한 패키지에 인터페이스를 구현하는 클래스또한 수정해줘야함

BoardServiceImpl 클래스 수정

 /*
     * @Override public List<BoardVO> getList() { // TODO Auto-generated method stub
     * 
     * log.info("getList......");
     * 
     * return mapper.getList(); }
     */

    @Override
    public List<BoardVO> getList(Criteria cri) {
        // TODO Auto-generated method stub
        log.info("get List with criteria: " + cri);
        return mapper.getListWithPaging(cri);
    }

또한 원칙적으론 BoardService쪽에 대한 수정이 이루어 졌으니 이에 대한 테스트를 진행해야함
메서드를 수정하면 이미 작성된 테스트 코드 또한 오류가 발생함
그러니 다음과 같이 수정하겠음

src/test/java 밑의 BoardServiceTests 클래스의 일부

    @Test
    public void testGetList() {

        // service.getList().forEach(board -> log.info(board));
        service.getList(new Criteria(2, 10)).forEach(board -> log.info(board));
    }



13.2.2 BoardController 수정

기존 BoardController의 list()는 아무런 파라미터가 없이 처리되었기 때문에
pageNum과 amount를 처리하기 위해서 아래와 같이 수정함

BoardController 클래스의 일부

    /*
     * @GetMapping("/list") 
     * public void list(Model model) {
     * 
     * log.info("controller list"); 
     * model.addAttribute("list", service.getList());
     * 
     * }
     */

    @GetMapping("/list")
    public void list(Criteria cri, Model model) {

        log.info("list: " + cri);
        model.addAttribute("list", service.getList(cri));
    }

Criteria 클래스를 하나 만들어 두면 위와 같이 편하게 하나의 타입만으로
파라미터나 리턴 타입을 사용할 수 있기 때문에 여러모로 편리함

BoardController 역시 이전에 테스트를 진행했으므로,
pageNum, amount를 파라미터로 테스트를 진행

src/test/java 밑의 BoardControllerTests 클래스의 일부

    @Test
    public void testListWithPaging() throws Exception{
        log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")
                .param("pageNum", "2")
                .param("amount", "50"))
                .andReturn().getModelAndView().getModelMap());
    }
⚠️ **GitHub.com Fallback** ⚠️