📗 self-study/📗 KH정보교육원 당산지원

[MyBatis] MyBatis CRUD - 게시판 기능 1(공통 코드, 페이징 바, 게시판 전체 조회)

천재강쥐 2022. 11. 11. 18:15

 

더보기

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;

}