Spring MyBatis (2) 게시물 목록 만들기 & 페이징

강서진·2024년 1월 22일
0

Spring

목록 보기
13/18
post-custom-banner

2번째 필수강의 ch4 3~5강 요약

페이징을 구현할 때, 시작 페이지, 현재 페이지, 끝 페이지, 페이지 사이즈 등의 이름을 변수에 맞게 지정해 줄 필요가 있다. 또, 게시물을 조회할 때는 링크에 몇 페이지인지, 페이지 사이즈가 얼마인지도 함께 넘겨주어야 다시 목록으로 돌아올 수 있다.
페이징을 사용해 테이블에 들어있는 데이터를 가져오려면 limit이라는 sql문을 사용해야 한다.

LIMIT [offset,] row_count

offset은 맨 처음부터 얼마나 떨어져있는지, row_count는 읽어들일 row의 개수를 의미한다.


PageHandler

Page Handler 클래스를 만들어 페이징에 필요한 값을 제공하도록 한다. 이 객체는 총 게시물 개수, 페이지 크기, 현재 페이지를 파라미터로 받으며, 필요한 나머지 정보는 이 세 정보를 받아 공식에 대입하여 얻을 수 있다.

public class PageHandler {

    private int totalCount; // 총 게시물 개수
    private int pageSize; // 페이지 크기
    private int naviSize = 10; // 페이지 네비게이션 크기, 기본 10
    private int page; // 현재 페이지
    private int totalPage; // 전체 페이지 개수
    private int beginPage; // 네비게이션 첫번째 페이지
    private int endPage; // 네비게이션 마지막 페이지
    private boolean showPrev; // 이전 페이지로 이동하는 링크 보여줄지 여부
    private boolean showNext; // 다음 페이지로 이동하는 링크 보여줄지 여부

한 페이지에 10개의 게시물을 출력하고, 페이징 단위가 10개 단위라고 가정하면, 위와 같은 정보가 필요하다. totalCount, pageSize, page만 가지고도 나머지 정보를 만들어내려면

	public PageHandler(int totalCount, int page, int pageSize){
        this.totalCount = totalCount;
        this.page = page;
        this.pageSize = pageSize;

        totalPage = (int)Math.ceil(totalCount / (double)pageSize);
        // 10단위로 고정된 네비게이션 x 그냥 현재페이지를 기준으로 +-5
        beginPage = page / naviSize * naviSize +1;
        // 총 페이지 수가 더 작으면 totalPage
        endPage = Math.min(beginPage + naviSize-1, totalPage);
        showPrev = beginPage != 1;
        showNext = endPage != totalPage;
    }

위와 같은 생성자 메서드를 만들어줄 수 있다. 테스트를 통해 잘 작동하는 것도 확인하였다.

public class PageHandlerTest {
    @Test
    public void test(){
        PageHandler ph = new PageHandler(255, 25);
        ph.print();
        assertEquals(21, ph.getBeginPage());
        assertEquals(26,ph.getEndPage());
    }
}

페이징 핸들러가 완성되었으니 mapper 문서에 총 게시물 개수를 반환하는 sql을 추가해준다.

    <select id="count" resultType="int">
        select count(*) from springbasic.board
    </select>

함께 작성된 insert, delete, update를 DaoImpl에 구현한다. 추가된 count도 포함한다.
하나의 결과를 반환하는 select 문일 경우 selectOne과 namespace+id로 파라미터를 넘기며(추가로 필요한 파라미터가 존재하면 그것도), 리스트를 반환할 경우 selectList로 호출한다.
delete의 경우, 게시물 번호와 작성자도 함께 받아야 하기 때문에 mapper에 map으로 파라미터를 넘겨주어야 한다.

    public int delete(Integer bno, String writer){
        Map map = new HashMap();
        map.put("bno", bno);
        map.put("wrtier", writer);
        return session.delete(namespace + "delete", map);
    }

좀 길기 때문에 수업자료에서 BoardDaoImplTest를 받아온다.
(increaseViewCount DAO 작성할 때 bno를 함께 넘겨주는 것을 깜박해서 계속 테스트가 실패해서 시간을 허비했다...)


BoardService

다음으로는 service를 작성한다. 마찬가지로 수업자료에서 가져오고, BoardService 인터페이스를 추출한다. 다만 현재 가져오는 작업에서나 값을 수정하는 작업에서나 에러가 나면 취소가 되기 때문에 따로 try-catch로 트랜잭션을 잡아주지는 않았다.

getPage는 페이징 용도로 만들어진 것으로,

    @Override
    public List<BoardDto> getPage(Map map) throws Exception {
        return boardDao.selectPage(map);
    }

selectPage라는 Dao 메서드를 추가해줄 필요가 있다. 먼저 mapper 문서에 추가해준다.

    <select id="selectPage" parameterType="map" resultType="BoardDto">
        select bno, title, content, writer, view_count, comment_count, reg_Date, up_date
        from springbasic.board
        order by reg_date desc, bno desc
        limit #{offset}, #{pageSize}
    </select>

결과를 반환하는 시작점이 되는 게시물 번호(offset)와 반환할 게시물 개수를 map으로 전달하면 해당하는 게시물들이 전달된다. Dao 메서드는 다음과 같다.

    @Override
    public List<BoardDto> selectPage(Map map) throws Exception{
        return session.selectList(namespace+"selectPage", map);
    }

BoardController

게시판 컨트롤러에는 로그인 상태를 확인하는 메서드와 "/board/list"로 맵핑된 메서드가 있다. 이 때 GetMapping으로 page, pageSize를 요청과 함께 전달받고, 이 정수들을 map으로 묶어 boardService의 getPage로 전달해준다.

@Controller
@RequestMapping("/board")
public class BoardController {
    @Autowired
    BoardService boardService;

    @GetMapping("/list")
    public String list(Integer page, Integer pageSize, Model m, HttpServletRequest request) {
        if(!loginCheck(request))
            return "redirect:/login/login?toURL="+request.getRequestURL();  // 로그인을 안했으면 로그인 화면으로 이동

		if(page==null) page=1;
        if(pageSize==null) pageSize=10;

        try{
            Map map = new HashMap();
            map.put("offset",(page-1)*pageSize);
            map.put("pageSize",pageSize);

            List<BoardDto> list = boardService.getPage(map);
            m.addAttribute("list",list);

        } catch (Exception e){
            e.printStackTrace();
        }
        return "boardList"; // 로그인을 한 상태이면, 게시판 화면으로 이동
    }

    private boolean loginCheck(HttpServletRequest request) {
        // 1. 세션을 얻어서
        HttpSession session = request.getSession();
        // 2. 세션에 id가 있는지 확인, 있으면 true를 반환
        return session.getAttribute("id")!=null;
    }
}

jsp가 반복문으로 게시판 결과를 출력하게 만든 후 서버를 키면, 다음과 같은 에러를 마주하게 된다.

Request processing failed; nested exception is java.lang.IllegalStateException: Optional int parameter 'page' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.

쿼리로 page와 pageSize 변수를 함께 요청해야 한다.

http://localhost:8080/ch4/board/list?page=1$pageSize=10

요청을 다시 보내면 로그인하라는 페이지가 뜬다. loginCheck가 잘 작동한 것을 확인할 수 있다. 테스트용 더미데이터로 빈 게시글을 220개를 만들고 게시글을 조회하니

제대로 10개씩 뜨는 것을 확인할 수 있었다.


PageHandler를 사용해 아래에 pagination을 구현해본다. PageHandler 객체를 생성한 후 model에 속성으로 추가하여 jsp로 들려보내려 try 문 안을 다음과 같이 수정해주었다.

	int totalCount = boardService.getCount();
    PageHandler pageHandler = new PageHandler(totalCount,page,pageSize);

    Map map = new HashMap();
    map.put("offset",(page-1)*pageSize);
    map.put("pageSize",pageSize);

    List<BoardDto> list = boardService.getPage(map);
    m.addAttribute("list",list);
    m.addAttribute("ph", pageHandler);

jsp 파일도 수정해준다.

  <c:if test="${ph.showPrev}">
    <a href="<c:url value='/board/list?page=${ph.beginPage-1}&pageSize=${ph.pageSize}'/>">&lt;</a>
  </c:if>
  <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
    <a href="<c:url value='/board/list?page=${i}&pageSize=${ph.pageSize}'/>">${i}</a>
  </c:forEach>
  <c:if test="${ph.showNext}">
    <a href="<c:url value='/board/list?page=${ph.endPage+1}&pageSize=${ph.pageSize}'/>">&gt;</a>
  </c:if>
  </div>

모델에 함께 전달된 ph가 가진 showPrev가 true면 앞으로 10페이지 넘어가는 화살표가 뜨고, false면 뜨지 않는다. 마찬가지로 showNext가 true면 다음 10페이지로 넘어가는 화살표가 뜬다.
그 사이에는 forEach로 beginPage부터 endPage까지 페이지에 반복문으로 링크를 띄운다.

그런데 > 버튼은 잘 작동하는데 <버튼이 잘 작동하지 않는 문제가 발생했다. beginPage 계산식에 오류있는 것으로,

beginPage = (page-1) / naviSize * naviSize +1;

위와 같이 고쳐주어야 한다(page에서 1을 빼는 부분이 빠졌다). 이를 고쳐주면 잘 동작하는 것을 확인할 수 있다.

추가로, 로그인을 담당하는 UserDao는 여전히 JDBC를 사용하고 있다. 이 부분을 MyBatis로 바꿔야한다.

post-custom-banner

0개의 댓글