12.14.(수) Spring Framework(20): 게시판 기능(5)
게시판 목록 페이지
SQL문 매핑
페이지 번호를 눌렀을 때, 순서에 맞게 해당 페이지에 들어있는 글을 표시하는 기능을 구현하기 위해 페이지에 대한 정보를 모아줄 PageVO 를 만들자.
public class PageVO {
// 페이징
private int nowPage = 1; // 현재 페이지
private int dataPerPage = 5; // 한 페이지에 표시할 데이터 수
private int totalData; // 총 데이터 수
private int totalPage; // 총 페이지 수
}
getter/setter와 toString()까지 해준 다음, 컨트롤러로 가서 게시판 목록에 대한 매핑을 수정해주자.
컨트롤러 매핑
게시판 목록 컨트롤러의 매개변수로 PageVO를 넣어줬다.
package com.poby.myapp.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.poby.myapp.service.BoardService;
import com.poby.myapp.vo.BoardVO;
import com.poby.myapp.vo.PageVO;
@Controller
public class BoardController {
@Autowired
BoardService service;
// 게시판 목록(페이징, 검색)
@RequestMapping("/board/boardList")
public ModelAndView boardList(PageVO pvo) {
ModelAndView mav = new ModelAndView();
return mav;
}
// 글 쓰기
@GetMapping("/board/boardPost")
public String boardPost() {
return "board/boardPost";
}
// 새 글 등록
@PostMapping("/board/boardPostOk")
public ModelAndView boardPostOk(BoardVO vo, HttpServletRequest req) { // 제목, 내용
ModelAndView mav = new ModelAndView();
vo.setIpAddr(req.getRemoteAddr()); // 작성자 ip주소
vo.setUsername((String)req.getSession().getAttribute("username")); // 현재 로그인한 아이디
mav.addObject("result", service.boardPostOk(vo));
mav.setViewName("board/boardPostOk");
return mav;
}
}
Service
package com.poby.myapp.service;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import com.poby.myapp.dao.BoardDAO;
import com.poby.myapp.vo.BoardVO;
import com.poby.myapp.vo.PageVO;
@Service
public class BoardServiceImpl implements BoardService {
@Inject
BoardDAO dao;
@Override
public int boardPostOk(BoardVO vo) {
return dao.boardPostOk(vo);
}
@Override
public List<BoardVO> boardList(PageVO pvo) {
return dao.boardList(pvo);
}
}
Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.poby.myapp.dao.BoardDAO">
<insert id="boardPostOk" parameterType="com.poby.myapp.vo.BoardVO">
INSERT INTO board_tbl(postno, subject, content, username, ipAddr)
VALUES(board_seq.nextval, #{subject}, #{content}, #{username}, #{ipAddr})
</insert>
<select id="boardList" resultType="com.poby.myapp.vo.BoardVO">
<![CDATA[
SELECT * FROM
(SELECT * FROM
(SELECT postno, subject, username, hitcount, to_char(regdate, 'mm-dd hh:mi') regdate
FROM board_tbl ORDER BY postno DESC)
WHERE rownum<=${nowPage}*${dataPerPage} ORDER BY postno ASC)
WHERE rownum<=${dataPerPage} ORDER BY postno DESC;
]]>
</select>
</mapper>
❗ <![CDATA[]]> 사용
xml에서는 <>기호를 태그를 열고 닫는 기호로 사용하고 있기 때문에, sql문에서 <>기호를 비교연산자로 사용하는 상황이 오면 별도의 코드를 통해 예외사항으로 인식하게 해줘야한다. rownum기준으로 글 개수를 자를때 비교연산자를 사용했기 때문에 여기서 CDATA로 쿼리문 전체를 감싸주었다. CDATA안에 <>기호가 포함되어있기만 하면 되기 때문에, 내가 한 것 처럼 쿼리문 전체를 감싸주어도 되고, 부등호만 감싸주어도 된다.
컨트롤러 모델&뷰
이제 컨트롤러로 돌아가서 Service메소드를 실행시켜서 ModelAndView를 완성시키자.
// 게시판 목록(페이징, 검색)
@RequestMapping("/board/boardList")
public ModelAndView boardList(PageVO pvo) {
ModelAndView mav = new ModelAndView();
// 페이징, 검색어에 해당하는 글 선택
mav.addObject("list", service.boardList(pvo));
mav.setViewName("board/boardList");
return mav;
}
뷰페이지
이제 boardList.jsp를 열어서 넘겨받은 list를 활용해서 DB에 있는 글을 표시하자. JSTL의 forEach 반복문으로 표시하면 된다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<link rel="stylesheet" href="/myapp/js_css/board.css" type="text/css">
<!-- attribute로 넘어온 변수 : list -->
<title>게시판 목록</title>
<div class="container">
<h1>게시판 목록</h1>
<div>
<a href="/myapp/board/boardPost">글 쓰기</a>
</div>
<ul class="boardList">
<li>No.</li>
<li>제목</li>
<li>작성자</li>
<li>등록일</li>
<li>조회수</li>
<c:forEach var="vo" items="${ list }">
<li>${ vo.postno }</li>
<li class="word-cut"><a href="/myapp/board/boardView?postno=${ vo.postno }">${ vo.subject }</a></li>
<li>${ vo.username }</li>
<li>${ vo.regdate }</li>
<li>${ vo.hitcount }</li>
</c:forEach>
</ul>
<!-- 페이지 처리 -->
<div class="pagesDiv">
<ul>
<li>prev</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>next</li>
</ul>
</div>
<!-- 검색 기능 -->
<div class="searchDiv">
<form action="/myapp/board/boardList">
<select name="searchKey">
<option>제목</option>
<option>내용</option>
<option>작성자</option>
</select>
<input type="text" name="searchValue" id="searchValue">
<input type="submit" value="검색">
</form>
</div>
</div>
이제 글의 총 개수를 이용하여 몇 페이지까지 표시되어야 하는지 알아보고, 기능을 구현하자.
PageVO에서 선언한 변수들을 이용하면 구할 수 있다. 페이지 번호는 1~5, 6~10, 11~15 이렇게 5개 묶음으로 표시할 것이다.
총 페이지 수 = 총 데이터 수/한 페이지당 글 수
우선 총 데이터 수를 구하는 메소드를 생성하자.
package com.poby.myapp.service;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import com.poby.myapp.dao.BoardDAO;
import com.poby.myapp.vo.BoardVO;
import com.poby.myapp.vo.PageVO;
@Service
public class BoardServiceImpl implements BoardService {
@Inject
BoardDAO dao;
@Override
public int boardPostOk(BoardVO vo) {
return dao.boardPostOk(vo);
}
@Override
public List<BoardVO> boardList(PageVO pvo) {
return dao.boardList(pvo);
}
@Override
public int totalData() {
return dao.totalData();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.poby.myapp.dao.BoardDAO">
<insert id="boardPostOk" parameterType="com.poby.myapp.vo.BoardVO">
INSERT INTO board_tbl(postno, subject, content, username, ipAddr)
VALUES(board_seq.nextval, #{subject}, #{content}, #{username}, #{ipAddr})
</insert>
<select id="boardList" resultType="com.poby.myapp.vo.BoardVO">
<![CDATA[
SELECT * FROM
(SELECT * FROM
(SELECT postno, subject, username, hitcount, to_char(regdate, 'mm-dd hh:mi') regdate
FROM board_tbl ORDER BY postno DESC)
WHERE rownum<=${nowPage}*${dataPerPage} ORDER BY postno ASC)
WHERE rownum<=${dataPerPage} ORDER BY postno DESC
]]>
</select>
<select id="totalData" resultType="int">
SELECT count(postno) FROM board_tbl
</select>
</mapper>
컨트롤러에서 총 데이터 수를 pvo에 set해준다.
// 게시판 목록(페이징, 검색)
@RequestMapping("/board/boardList")
public ModelAndView boardList(PageVO pvo) {
ModelAndView mav = new ModelAndView();
// 총 데이터 수 구하기
pvo.setTotalData(service.totalData());
// 페이징, 검색어에 해당하는 글 선택
mav.addObject("list", service.boardList(pvo));
mav.setViewName("board/boardList");
return mav;
}
여기까지 pvo에 등록된 변수는 nowPage = 1, dataPerPage = 5, totalData = 38이다.
총 데이터 수를 한 페이지 당 데이터 수로 나누면 총 페이지 수가 나온다.
totalPage = totalData/dataPerPage
이 계산을 아예 PageVO의 setTotalData메소드에 넣자. 자료형을 신경써서 계산해줘야한다.
public void setTotalData(int totalData) {
this.totalData = totalData;
// 총 페이지 수 구하기
totalPage = (int)Math.ceil((double)totalData/dataPerPage);
}
그리고 페이지 번호를 매길때 사용할 변수를 PageVO에 새로 선언한다.
package com.poby.myapp.vo;
public class PageVO {
// 페이징
private int nowPage = 1; // 현재 페이지
private int dataPerPage = 5; // 한 페이지에 표시할 데이터 수
private int totalData; // 총 데이터 수
private int totalPage; // 총 페이지 수
// 페이지 번호 매길때...
private int pageCount = 5; // 한번에 표시할 페이지 수
private int startPage = 1; // 시작 페이지 - 범위에 따라 바뀜 ex)1,6,11,16
}
시작 페이지를 구해보자. 현재 페이지가 4면 시작 페이지는 1, 현재 페이지가 7, 8이면 시작 페이지는 6이다.
그렇다면 시작 페이지는,
startPage = ((nowPage-1)/pageCount)*pageCount+1
이 계산을 setNowPage에 등록하자.
public void setNowPage(int nowPage) {
this.nowPage = nowPage;
// 시작페이지 계산하기
startPage = ((nowPage-1)/pageCount)*pageCount+1;
}
이제 PageVO에 있는 변수들에 대한 계산은 끝났다.
컨트롤러로 와서 현재 페이지(nowPage)에 따라 시작 페이지(startPage)가 어떻게 계산되는지 확인해보자.
// 게시판 목록(페이징, 검색)
@RequestMapping("/board/boardList")
public ModelAndView boardList(PageVO pvo) {
ModelAndView mav = new ModelAndView();
// 총 데이터 수 구하기
pvo.setTotalData(service.totalData());
System.out.println(pvo);
// 페이징, 검색어에 해당하는 글 선택
mav.addObject("list", service.boardList(pvo));
mav.setViewName("board/boardList");
return mav;
}
boardList페이지를 새로고침하면 콘솔에 PageVO에 있는 값들이 찍힐 것이다.
상단메뉴의 게시판 버튼을 클릭해서 들어가면 기본값을 현재 페이지(nowPage)를 1로 설정해놨기 때문에, 시작 페이지(startPage)도 1로 찍히는 걸 볼 수 있다. 만약 주소창에 nowPage값을 다른 값으로 넘겨주게되면 startPage값도 달라진다.
nowPage가 변함에 따라 표시되는 글도 rownum을 계산해서 표시가 된다. 위는 7페이지의 글 목록이다.
마지막으로 PageVO를 attribute로 등록하고 컨트롤러를 마무리하자.
// 게시판 목록(페이징, 검색)
@RequestMapping("/board/boardList")
public ModelAndView boardList(PageVO pvo) {
ModelAndView mav = new ModelAndView();
// 총 데이터 수 구하기
pvo.setTotalData(service.totalData());
System.out.println(pvo);
// 페이징, 검색어에 해당하는 글 선택
mav.addObject("list", service.boardList(pvo));
mav.addObject("pvo", pvo);
mav.setViewName("board/boardList");
return mav;
}