Spring

12.14.(수) Spring Framework(20): 게시판 기능(5)

콜라든포비 2022. 12. 17. 22:51

게시판 목록 페이지

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;
}