MyBatis의 게시판 기능을 구현해 보자
🔥 공통 코드 🔥
💻 Board 클래스 생성
package com.kh.mybatis.board.model.vo;
import java.sql.Date;
public class Board {
// 필드부
private int boardNo; // BOARD_NO NUMBER PRIMARY KEY,
private String boardTitle; // BOARD_TITLE VARCHAR2(100) NOT NULL,
private String boardContent; // BOARD_CONTENT VARCHAR2(4000) NOT NULL,
private String boardWriter; // BOARD_WRITER NUMBER,
private int count; // COUNT NUMBER DEFAULT 0,
private Date createDate; // CREATE_DATE DATE DEFAULT SYSDATE,
private String status; // STATUS VARCHAR2(1) DEFAULT 'Y' CHECK (STATUS IN('Y', 'N'))
// 생성자부
public Board() {
super();
}
public Board(int boardNo, String boardTitle, String boardContent, String boardWriter, int count, Date createDate,
String status) {
super();
this.boardNo = boardNo;
this.boardTitle = boardTitle;
this.boardContent = boardContent;
this.boardWriter = boardWriter;
this.count = count;
this.createDate = createDate;
this.status = status;
}
// 메소드부
public int getBoardNo() {
return boardNo;
}
public void setBoardNo(int boardNo) {
this.boardNo = boardNo;
}
public String getBoardTitle() {
return boardTitle;
}
public void setBoardTitle(String boardTitle) {
this.boardTitle = boardTitle;
}
public String getBoardContent() {
return boardContent;
}
public void setBoardContent(String boardContent) {
this.boardContent = boardContent;
}
public String getBoardWriter() {
return boardWriter;
}
public void setBoardWriter(String boardWriter) {
this.boardWriter = boardWriter;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Board [boardNo=" + boardNo + ", boardTitle=" + boardTitle + ", boardContent=" + boardContent
+ ", boardWriter=" + boardWriter + ", count=" + count + ", createDate=" + createDate + ", status="
+ status + "]";
}
}
💻 BoardDao 생성
일단 생성만!
💻 BoardService 인터페이스 생성
package com.kh.mybatis.board.model.service;
import java.util.ArrayList;
import com.kh.mybatis.board.model.vo.Board;
import com.kh.mybatis.common.model.vo.PageInfo;
public interface BoardService {
// 게시글 리스트 조회
int selectListCount();
ArrayList<Board> selectList(PageInfo pi);
// 게시글 상세 조회
int increaseCount(int boardNo);
Board selectBoard(int boardNo);
// ...
}
💻 BoardServiceImpl 클래스 생성
package com.kh.mybatis.board.model.service;
import java.util.ArrayList;
import com.kh.mybatis.board.model.vo.Board;
import com.kh.mybatis.common.model.vo.PageInfo;
public class BoardServiceImpl implements BoardService {
@Override
public int selectListCount() {
// TODO Auto-generated method stub
return 0;
}
@Override
public ArrayList<Board> selectList(PageInfo pi) {
// TODO Auto-generated method stub
return null;
}
@Override
public int increaseCount(int boardNo) {
// TODO Auto-generated method stub
return 0;
}
@Override
public Board selectBoard(int boardNo) {
// TODO Auto-generated method stub
return null;
}
}
💻 menubar.jsp
<div class="nav-area" align="center">
<div class="menu">HOME</div>
<div class="menu">공지사항</div>
<div class="menu" onclick="location.href='list.bo?currentPage=1';">게시판</div>
<div class="menu">ETC</div>
</div>
🔥 페이징 바 🔥
💻 PageInfo 클래스 생성 - 페이징 바
package com.kh.mybatis.common.model.vo;
public class PageInfo {
// 필드부 => 페이징 처리에 필요한 변수 7개 세팅
private int listCount; // 총 게시글 개수
private int currentPage; // 현재 페이지 (즉, 사용자가 요청한 페이지 수)
private int pageLimit; // 하단에 보여질 페이징바의 페이지 목록 최대 개수
private int boardLimit; // 한 페이지에 보여질 게시글의 최대 개수
private int maxPage; // 가장 마지막 페이지의 수 (listCount, boardLimit 이용해서 구함)
private int startPage; // 해당 페이지에 보여질 페이징바의 시작 수 (pageLimit, currentPage 이용해서 구함)
private int endPage; // 해당 페이지에 보여질 페이징바의 끝 수 (startPage, pageLimit, maxPage 이용해서 구함)
// 생성자부
public PageInfo() {
super();
}
public PageInfo(int listCount, int currentPage, int pageLimit, int boardLimit, int maxPage, int startPage,
int endPage) {
super();
this.listCount = listCount;
this.currentPage = currentPage;
this.pageLimit = pageLimit;
this.boardLimit = boardLimit;
this.maxPage = maxPage;
this.startPage = startPage;
this.endPage = endPage;
}
// 메소드부
public int getListCount() {
return listCount;
}
public void setListCount(int listCount) {
this.listCount = listCount;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getPageLimit() {
return pageLimit;
}
public void setPageLimit(int pageLimit) {
this.pageLimit = pageLimit;
}
public int getBoardLimit() {
return boardLimit;
}
public void setBoardLimit(int boardLimit) {
this.boardLimit = boardLimit;
}
public int getMaxPage() {
return maxPage;
}
public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
public int getStartPage() {
return startPage;
}
public void setStartPage(int startPage) {
this.startPage = startPage;
}
public int getEndPage() {
return endPage;
}
public void setEndPage(int endPage) {
this.endPage = endPage;
}
@Override
public String toString() {
return "PageInfo [listCount=" + listCount + ", currentPage=" + currentPage + ", pageLimit=" + pageLimit
+ ", boardLimit=" + boardLimit + ", maxPage=" + maxPage + ", startPage=" + startPage + ", endPage="
+ endPage + "]";
}
}
⌨️ BoardListController Servlet 생성
(방법 2로 진행할 것)
url mapping: /list.bo
방법 1. BoardListController에 모두 기재
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 페이징 처리를 위한 변수 먼저 세팅 후 PageInfo로 가공 처리
int listCount = new BoardServiceImpl().selectListCount(); // 현재 총 게시글 개수
int currentPage = Integer.parseInt(request.getParameter("currentPage")); // 현재 요청한 페이지(즉, 사용자가 요청한 페이지 수)
int pageLimit = 10; // 페이지 하단에 보여질 페이징바의 페이지 최대 개수
int boardLimit = 10; // 한 페이지에 보여질 게시글의 최대 개수(몇 개 단위씩 리스트가 보여질 건지)
int maxPage = (int)Math.ceil((double)listCount / boardLimit); // 가장 마지막 페이지가 몇 번 페이지인지(총 페이지 수)
int startPage = (currentPage - 1) / pageLimit * pageLimit + 1; // 페이지 하단에 보여질 페이징바의 시작 수
int endPage = startPage + pageLimit - 1; // 페이지 하단에 보여질 페이징바의 끝 수
if(endPage > maxPage) {
endPage = maxPage;
}
PageInfo pi = new PageInfo(listCount, currentPage, pageLimit, boardLimit
, maxPage, startPage, endPage);
}
위와 같이 사용해도 무방하나 PageInfo는 똑같이 자주 쓰는 구문이기 때문에
계산이 필요한 maxPage, startPage, endPage를
따로 계산 후 PageInfo 객체를 리턴하는 방식으로 Template을 만들어 놓으려고 함!
방법 2. 공통코드를 Pagination 클래스에 빼 놓고 페이징 바가 필요한 페이지마다 가지고 와서 작업하기
💻 Pagination 클래스 생성
package com.kh.mybatis.common.template;
import com.kh.mybatis.common.model.vo.PageInfo;
public class Pagination {
public static PageInfo getPageInfo(int listCount, int currentPage, int pageLimit, int boardLimit) {
int maxPage = (int)Math.ceil((double)listCount / boardLimit); // 가장 마지막 페이지가 몇 번 페이지인지(총 페이지 수)
int startPage = (currentPage - 1) / pageLimit * pageLimit + 1; // 페이지 하단에 보여질 페이징바의 시작 수
int endPage = startPage + pageLimit - 1; // 페이지 하단에 보여질 페이징바의 끝 수
if(endPage > maxPage) {
endPage = maxPage;
}
PageInfo pi = new PageInfo(listCount, currentPage, pageLimit, boardLimit
, maxPage, startPage, endPage);
return pi;
}
}
💻 BoardListController
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 페이징 처리를 위한 변수 먼저 세팅 후 PageInfo로 가공 처리
int listCount = new BoardServiceImpl().selectListCount(); // 현재 총 게시글 개수
int currentPage = Integer.parseInt(request.getParameter("currentPage")); // 현재 요청한 페이지(즉, 사용자가 요청한 페이지 수)
int pageLimit = 10; // 페이지 하단에 보여질 페이징바의 페이지 최대 개수
int boardLimit = 5; // 한 페이지에 보여질 게시글의 최대 개수(몇 개 단위씩 리스트가 보여질 건지)
PageInfo pi = Pagination.getPageInfo(listCount, currentPage, pageLimit, boardLimit);
// listCount 값을 return 받았으므로 완성된 PageInfo 객체가 담겨 있을 것
// System.out.println(pi);
// PageInfo [listCount=14, currentPage=1, pageLimit=10, boardLimit=5, maxPage=3, startPage=1, endPage=3]
}
💻 BoardServiceImpl
public class BoardServiceImpl implements BoardService {
private BoardDao boardDao = new BoardDao();
@Override
public int selectListCount() {
SqlSession sqlSession = /* Template. */ getSqlSession();
int listCount = boardDao.selectListCount(sqlSession);
// 이 시점에 board-mapper.xml -> BoardDao를 거친 int형 값이 도착함
sqlSession.close();
return listCount;
}
}
💻 board-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="">
</mapper>
mapper 유효성 검사를 해 주는 구문이므로 해당 구문은 .xml 상단에 항상 존재해야 함!
(공식 문서인 마이바티스 개발자문서에서 가지고 오는 게 정석적인 방법이지만 구문이 같으므로 member-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="boardMapper">
<select id="selectListCount" resultType="_int">
SELECT COUNT(*)
FROM BOARD
WHERE STATUS = 'Y'
</select>
</mapper>
👉🏻 개발자 문서에서 그렇게 하라고 함
https://mybatis.org/mybatis-3/ko/configuration.html
MyBatis – 마이바티스 3 | 매퍼 설정
매퍼 설정 마이바티스 XML 설정파일은 다양한 설정과 프로퍼티를 가진다. 문서의 구조는 다음과 같다.: configuration properties 이 설정은 외부에 옮길 수 있다. 자바 프로퍼티 파일 인스턴스에 설정할
mybatis.org
<개발자 문서 중에서>
공통 자바 타입을 위한 여러 내장 타입 별칭이 존재한다. 이들은 대소문자를 구별하지 않으며, 오버로딩된 이름 때문에 원시형 타입은 특별 취급된다는 것을 주의해라.
별칭 | 매핑된 타입 |
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
date[] | Date[] |
decimal[] | BigDecimal[] |
bigdecimal[] | BigDecimal[] |
biginteger[] | BigInteger[] |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
💻 mybatis-config.xml에 mapper 등록하기
👉🏻 등록하지 않으면 사용할 수 없음! 경로 틀려도 FileNotFounrException 발생함
<mappers>
<mapper resource="/mappers/member-mapper.xml" /> // member 때 이미 등록함
<mapper resource="/mappers/board-mapper.xml" />
</mappers>
💻 BoardDao
public int selectListCount(SqlSession sqlSession) {
return sqlSession.selectOne("boardMapper.selectListCount");
// 어느 mapper 타입의 어느 id를 가진 쿼리문을 찾아서 실행할 것인지?
}
✔️ 여기까지 진행 상황 확인
board-mapper.xml의 selectListCount에서 결과값으로 나온 값: int형
👉🏻 해당 int 값이 Dao에 와서 바로 return 됨
👉🏻 ServiceImpl에 바로 도착!
(필기상 ServiceImpl는 반환받은 후의 코드까지, boardListCotroller는 반환 후의 상태까지 미리 적어 놓음)
현재 메뉴바의 게시판을 누르면 흰 배경에 url 주소만 뜨는 상태
🔥 게시판 전체 조회 기능 🔥
💻 boardListView.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#list-area {
border : 1px solid white;
text-align : center;
}
</style>
</head>
<body>
<jsp:include page="../common/menubar.jsp" />
<div class="outer" align="center">
<br>
<h1 align="center">게시판</h1>
<div id="search-area">
</div>
<br>
<table id="list-area">
<thead>
<tr>
<th>글번호</th>
<th width="400">제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<c:forEach var="b" items="${ list }">
<tr>
<td>${ b.boardNo }</td>
<td>${ b.boardTitle }</td>
<td>${ b.boardWriter }</td>
<td>${ b.count }</td>
<td>${ b.createDate }</td>
</tr>
</c:forEach>
</tbody>
</table>
<br>
<div id="paging-area">
<c:if test="${ pi.currentPage ne 1 }">
<a href="list.bo?currentPage=${ pi.currentPage - 1 }">[이전]</a>
</c:if>
<c:forEach var="p" begin="${ pi.startPage }" end="${ pi.endPage }" step="1">
<a href="list.bo?currentPage=${ p }">[${ p }]</a>
</c:forEach>
<c:if test="${ pi.currentPage ne pi.maxPage }">
<a href="list.bo?currentPage=${ pi.currentPage + 1 }">[다음]</a>
</c:if>
</div>
</div>
</body>
</html>
💻 BoardListController
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 페이징 처리를 위한 변수 먼저 세팅 후 PageInfo로 가공 처리
int listCount = new BoardServiceImpl().selectListCount(); // 현재 총 게시글 개수
int currentPage = Integer.parseInt(request.getParameter("currentPage")); // 현재 요청한 페이지(즉, 사용자가 요청한 페이지 수)
int pageLimit = 10; // 페이지 하단에 보여질 페이징바의 페이지 최대 개수
int boardLimit = 5; // 한 페이지에 보여질 게시글의 최대 개수(몇 개 단위씩 리스트가 보여질 건지)
PageInfo pi = Pagination.getPageInfo(listCount, currentPage, pageLimit, boardLimit);
// listCount 값을 return 받았으므로 완성된 PageInfo 객체가 담겨 있을 것
// System.out.println(pi);
// PageInfo [listCount=14, currentPage=1, pageLimit=10, boardLimit=5, maxPage=3, startPage=1, endPage=3]
// 여기부터 전체 리스트 조회를 위한 코드
ArrayList<Board> list = new BoardServiceImpl().selectList(pi);
// 응답 페이지로 보낼 변수
request.setAttribute("pi", pi); // 페이징 바를 만들기 위한 변수
request.setAttribute("list", list); // 조회된 내용물을 화면에 뿌려 주기 위한 변수
// 포워딩
request.getRequestDispatcher("WEB-INF/views/board/boardListView.jsp").forward(request, response);
}
💻 BoardServiceImpl
@Override
public ArrayList<Board> selectList(PageInfo pi) {
SqlSession sqlSession = getSqlSession();
ArrayList<Board> list = boardDao.selectList(sqlSession, pi);
sqlSession.close();
return list;
}
💻 board-mapper.xml
페이지 구간에 맞는 게시글 목록 추려내기
👉🏻 기존에 쓰던 ROWNUM 방식을 써도 무방하나 효율성을 위해 RowBounds 쓰는 것을 권장!
👉🏻 ROWBOUNDS: 기존의 BETWEEN ? AND B에 대한 구간을 알아서 설정해 줌 (DAO에서 설정할 것)
✔️ 즉, mapper에서는 전체를 조회할 수 있는 범위만 잘 지정해 주면 됨!
<resultMap id="boardResultSet" type="board">
<result column="BOARD_NO" property="boardNo" />
<result column="BOARD_TITLE" property="boardTitle" />
<result column="USER_ID" property="boardWriter" />
<result column="COUNT" property="count" />
<result column="CREATE_DATE" property="createDate" />
</resultMap>
<select id="selectList" resultMap="boardResultSet">
SELECT BOARD_NO
, BOARD_TITLE
, USER_ID
, COUNT
, CREATE_DATE
FROM BOARD B
JOIN MEMBER ON (BOARD_WRITER = USER_NO)
WHERE B.STATUS = 'Y'
ORDER BY BOARD_NO DESC
</select>
✔️ 물음표 없는 완성된 상태이기 때문에 parameterType은 필요 없음
💻 mybatis-config.xml에 typeAliase 등록하기
<typeAliases>
<typeAlias type="com.kh.mybatis.member.model.vo.Member" alias="member" /> // member 때 이미 등록함
<typeAlias type="com.kh.mybatis.board.model.vo.Board" alias="board" />
</typeAliases>
💻 BoardDao
📍 마이바티스에서는 페이징 처리를 위해 RowBounds라는 클래스를 제공함
👉🏻 마이바티스에 존재하는 페이징 처리를 간략하게 만들어 주는 객체
RowBounds라는 객체를 생성할 경우 필요한 값이 2개가 있음
1️⃣ offSet: 0번째에서부터 몇 개의 행을 건너뛰고 조회할 건지에 대한 값
2️⃣ limit: 조회할 행의 개수에 대한 값 (== boardLimit 값)
ex) boardlimit가 5일 경우
offset(건너뛸 숫자) limit(조회할 개수)
currentPage: 1 => 1 ~ 5 0 5
currentPage: 2 => 2 ~ 10 5 5
currentPage: 3 => 11 ~ 15 10 5
...
👉🏻 limit == boardLimit
👉🏻 offset == (currentPage - 1) * limit
selectList 메소드 호출 시 RowBounds 객체를 넘겨야 할 경우
selectList 메소드의 오버로딩된 형태 중 매개변수가 3개인 메소드를 호출해야만 함
👉🏻 두 번째 매개변수 == 쿼리문을 완성시키기 위한 데이터 값
❗️❓ selectList 메소드 호출 시 주의할 점
👉🏻딱히 두 번째 매개변수 자리에 넘길 값이 없다면 null 값을 넘기면 됨!
public ArrayList<Board> selectList(SqlSession sqlSession, PageInfo pi) {
int limit = pi.getBoardLimit();
int offset = (pi.getCurrentPage() - 1) * limit;
RowBounds rowBounds = new RowBounds(offset, limit);
ArrayList<Board> list = (ArrayList)sqlSession.selectList("boardMapper.selectList", null, rowBounds);
// 쿼리문 컬럼 중에서 BOARD 타입의 값이 아닌 MEMBER나 다른 곳의 컬럼이 1개라도 들어 있다면 오류남!
// => 제네릭까지 형 변환 하지 말고 ArrayList까지만 형 변환!
return list;
}