paginationInfoVO
package kr.or.ddit.vo;
import java.util.List;
import lombok.Data;
@Data
public class PaginationInfoVO<T> {
private int totalRecord; // 총 게시글 수
private int totalPage; // 총 페이지 수
private int currentPage; // 현재 페이지
private int screenSize = 10; // 페이지 당 게시글 수
private int blockSize = 5; // 페이지 블록 수
private int startRow; // 시작row
private int endRow; // 끝 row
private int startPage; // 시작 페이지
private int endPage; // 끝 페이지
private List<T> dataList ; // 결과를 넣을 데이터 리스트
private String searchType ; // 검색 타입(제목, 내용, 작성자, 작성일 등등)
private String searchWord ; // 검색 단어(키워드)
public PaginationInfoVO() {}
// PaginationInfoVO 객체를 만들 때, 한 페이지당 게시글 수와 페이지 블록 수를
// 원하는 값으로 초기화 할 수 있도록 Custom 생성자 생성
public PaginationInfoVO(int screenSize, int blockSize) {
this.screenSize = screenSize;
this.blockSize = blockSize;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
// 총 페이지 수 구하기
// ceil 올림
totalPage = (int)Math.ceil(totalRecord / (double)screenSize);
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage; // 현재 페이지
endRow = currentPage * screenSize; // 끝 row = 현재 페이지 * 한 페이지당 게시글 수
startRow = endRow - (screenSize - 1 ); // 시작 row = 끝 row - (한 페이지당 게시글 수 - 1)
// 마지막 페이지 = (현재 페이지 + (페이지 블록 사이즈 - 1)) / 페이지 블록 사이즈 * 페이지 블록 사이즈
endPage = (currentPage + (blockSize - 1)) / blockSize * blockSize;
startPage = endPage - (blockSize - 1); // 시작 페이지 = 마지막 페이지 - (페이지 블록 사이즈 - 1)
}
public String getPagingHTML() {
StringBuffer html = new StringBuffer();
// 부트스트랩을 이용하면 여러 css요소를 이용해서 페이징 UI를 구현하는 곳입니다!
html.append("<ul class=\\"pagination justify-content-center\\">");
if(startPage > 1) {
html.append("<li class='page-item'><a href='' class='page-link' data-page='"
+(startPage - blockSize)+"'><span class=\\"material-icons\\">keyboard_arrow_left</span></a></li>");
}
for(int i = startPage; i <= (endPage < totalPage ? endPage : totalPage); i++) {
if(i == currentPage) {
html.append("<li class='page-item active'><span class='page-link'>"
+ i + "</span></li>");
}else {
html.append("<li class='page-item'><a href='' class='page-link' data-page='"
+ i + "'>" + i + "</a></li>");
}
}
if(endPage < totalPage) {
html.append("<li class='page-item'><a href='' class='page-link' data-page='"
+ (endPage + 1) + "'><span class=\\"material-icons\\">keyboard_arrow_right</span></a></li>");
}
html.append("</ul>");
return html.toString();
}
}
Controller
package kr.or.ddit.controller.noticeboard.web;
import java.util.List;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import kr.or.ddit.controller.noticeboard.service.INoticeService;
import kr.or.ddit.vo.NoticeVO;
import kr.or.ddit.vo.PaginationInfoVO;
import lombok.extern.slf4j.Slf4j;
@Controller
@RequestMapping("/notice")
@Slf4j
public class NoticeRetrieveController {
@Inject
private INoticeService noticeService;
**// 페이징 및 검색 리스트**
@RequestMapping(value="/list.do")
public String noticeList(
@RequestParam(name="page", required = false, defaultValue = "1") int currentPage,
@RequestParam(required = false, defaultValue = "title") String searchType,
@RequestParam(required = false) String searchWord,
Model model
) {
log.info("noticeList() 실행...!");
PaginationInfoVO<NoticeVO> pagingVO = new PaginationInfoVO<NoticeVO>();
//검색 기능 추가 시 활용
if(StringUtils.isNotBlank(searchWord)) { //검색 됨
pagingVO.setSearchType(searchType);
pagingVO.setSearchWord(searchWord);
model.addAttribute("searchType", searchType);
model.addAttribute("searchWord", searchWord);
}
pagingVO.setCurrentPage(currentPage); // startRow, endRow, startPage, endPage 가 결정
int totalRecord = noticeService.selectNoticeCount(pagingVO); // 총 게시글 수
pagingVO.setTotalRecord(totalRecord);
List<NoticeVO> dataList = noticeService.selectNoticeList(pagingVO);
pagingVO.setDataList(dataList);
model.addAttribute("pagingVO", pagingVO);
// 해당경로는 tiles 에서 설정한 경로 이름
return "notice/list";
}
@RequestMapping(value="/detail.do")
public String noticeDetail(int boNo, Model model) {
NoticeVO noticeVO = noticeService.selectNotice(boNo);
model.addAttribute("notice", noticeVO);
return "notice/detail";
}
}
Service
package kr.or.ddit.controller.noticeboard.service;
import java.util.List;
import javax.inject.Inject;
import org.apache.commons.io.input.TaggedInputStream;
import org.springframework.stereotype.Service;
import kr.or.ddit.ServiceResult;
import kr.or.ddit.controller.noticeboard.web.TelegramSendController;
import kr.or.ddit.mapper.NoticeMapper;
import kr.or.ddit.vo.NoticeVO;
import kr.or.ddit.vo.PaginationInfoVO;
@Service
public class NoticeServiceImpl implements INoticeService {
@Inject
private NoticeMapper noticeMapper;
TelegramSendController tst = new TelegramSendController();
@Override
public ServiceResult insertNotice(NoticeVO noticeVO) {
ServiceResult result = null;
int status = noticeMapper.insertNotice(noticeVO);
if(status > 0) {
// 성공 했다는 메시지를 텔레그램 BOT API를 이용하여 알림!
try {
tst.sendGet("조성희", noticeVO.getBoTitle());
} catch (Exception e) {
e.printStackTrace();
}
result = ServiceResult.OK;
}else {
result = ServiceResult.FAILED;
}
return result;
}
@Override
public NoticeVO selectNotice(int boNo) {
noticeMapper.incrementHit(boNo); //조회수 증가
return noticeMapper.selectNotice(boNo);
}
@Override
public ServiceResult updateNotice(NoticeVO noticeVO) {
ServiceResult result = null;
int status = noticeMapper.updateNotice(noticeVO);
if(status > 0) { // 성공
result = ServiceResult.OK;
}else {
result = ServiceResult.FAILED;
}
return result;
}
@Override
public ServiceResult deleteNotice(int boNo) {
ServiceResult result = null;
int status = noticeMapper.deleteNotice(boNo);
if(status > 0) { // 성공
result = ServiceResult.OK;
}else {
result = ServiceResult.FAILED;
}
return result;
}
**// 게시글 개수 세는 서비스**
@Override
public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO) {
return noticeMapper.selectNoticeCount(pagingVO);
}
**// 페이징, 검색 서비스**
@Override
public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO) {
return noticeMapper.selectNoticeList(pagingVO);
}
}
Mapper
package kr.or.ddit.mapper;
import java.util.List;
import kr.or.ddit.vo.NoticeVO;
import kr.or.ddit.vo.PaginationInfoVO;
public interface NoticeMapper {
public int insertNotice(NoticeVO noticeVO);
public void incrementHit(int boNo);
public NoticeVO selectNotice(int boNo);
public int updateNotice(NoticeVO noticeVO);
public int deleteNotice(int boNo);
public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
}
NoticeMapper (SQL)
<?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="kr.or.ddit.mapper.NoticeMapper">
<!-- 검색을위한 동적 쿼리 -->
<sql id="noticeSearch">
<if test="searchType != null and searchType == 'title'">
and (bo_title like '%'||#{searchWord}||'%')
</if>
<if test="searchType != null and searchType == 'writer'">
and (bo_writer like '%'||#{searchWord}||'%')
</if>
</sql>
<!-- 공지사항 등록하는 쿼리 -->
<insert id="insertNotice" parameterType="noticeVO" useGeneratedKeys="true">
<selectKey keyProperty="boNo" resultType="int" order="BEFORE">
select seq_notice.nextval from dual
</selectKey>
insert into notice(
bo_no, bo_title, bo_content, bo_writer, bo_date
)values(
#{boNo}, #{boTitle}, #{boContent}, #{boWriter}, sysdate
)
</insert>
<!-- 조회수 증가 쿼리 -->
<update id="incrementHit" parameterType="int">
update notice
set
bo_hit = bo_hit + 1
where bo_no = #{boNo}
</update>
<!-- 상세보기 쿼리 -->
<select id="selectNotice" parameterType="int" resultType="noticeVO">
select
bo_no, bo_title, bo_content, bo_writer,
to_char(bo_date, 'yy-mm-dd hh24:mi:ss') bo_date, bo_hit
from notice
where bo_no = #{boNo}
</select>
<!-- 수정하기 쿼리 -->
<update id="updateNotice" parameterType="noticeVO">
update notice
set
bo_title = #{boTitle},
bo_content = #{boContent},
bo_date = sysdate
where bo_no = #{boNo}
</update>
<delete id="deleteNotice" parameterType="int">
delete from notice
where bo_no = #{boNo}
</delete>
**<!-- 게시글 개수 세는 쿼리 -->**
<select id="selectNoticeCount" parameterType="pagingVO" resultType="int">
select count(bo_no)
from notice
where 1=1
<include refid="noticeSearch"/>
</select>
**<!-- 검색 및 페이징 위한 쿼리-->**
<select id="selectNoticeList" parameterType="pagingVO" resultType="noticeVO">
select
b.*
from(
select
a.*, row_number() over (order by a.bo_no desc) rnum
from(
select
bo_no, bo_title, bo_content, bo_writer,
to_char(bo_date, 'yy-mm-dd hh24:mi:ss') bo_date, bo_hit
from notice
where 1=1
<include refid="noticeSearch"/>
order by bo_no desc
)a
) b
<![CDATA[
where b.rnum >= #{startRow} and b.rnum <= #{endRow}
]]>
</select>
</mapper>
JSP 페이지
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="<http://tiles.apache.org/tags-tiles>" prefix="tiles" %>
<%@ taglib uri="<http://java.sun.com/jsp/jstl/core>" prefix="c" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AdminLTE 3 | Simple Tables</title>
<link rel="stylesheet" href="<https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback>">
<link rel="stylesheet" href="${pageContext.request.contextPath }/resources/plugins/fontawesome-free/css/all.min.css">
<link rel="stylesheet" href="${pageContext.request.contextPath }/resources/dist/css/adminlte.min.css">
<script src="${pageContext.request.contextPath }/resources/plugins/jquery/jquery.min.js"></script>
<script src="${pageContext.request.contextPath }/resources/ckeditor/ckeditor.js"></script>
</head>
<c:if test="${not empty message }">
<script type="text/javascript">
alert("${message}");
<c:remove var="message" scope="request"/>
<c:remove var="message" scope="session"/>
</script>
</c:if>
<body class="hold-transition sidebar-mini">
<div class="wrapper">
<!-- header 영역 -->
<tiles:insertAttribute name="header"/>
<div class="content-wrapper">
<!-- content 영역 -->
<tiles:insertAttribute name="content"/>
</div>
<!-- footer 영역 -->
<tiles:insertAttribute name="footer"/>
<aside class="control-sidebar control-sidebar-dark">
</aside>
</div>
<script src="${pageContext.request.contextPath }/resources/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="${pageContext.request.contextPath }/resources/dist/js/adminlte.min.js"></script>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="<http://java.sun.com/jsp/jstl/core>" prefix="c" %>
<!-- Content Header (Page header) -->
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>공지사항 게시판</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="#">DDIT HOME</a></li>
<li class="breadcrumb-item active">공지사항 게시판</li>
</ol>
</div>
</div>
</div>
<!-- /.container-fluid -->
</section>
<!-- Main content -->
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="card-tools">
<form method="post" id="searchForm" class="input-group input-group-sm" style="width: 440px;">
<input type="hidden" name="page" id="page">
<select class="form-control" name="searchType">
<option value="title" <c:if test="${searchType eq 'title' }">selected</c:if>>제목</option>
<option value="writer" <c:if test="${searchType eq 'writer' }">selected</c:if>>작성자</option>
</select>
<input type="text" name="searchWord" value="${searchWord }" class="form-control float-right" placeholder="Search">
<div class="input-group-append">
<button type="submit" class="btn btn-default">
<i class="fas fa-search"></i>검색
</button>
</div>
</form>
</div>
<h3 class="card-title">공지사항</h3>
</div>
<!-- /.card-header -->
<div class="card-body">
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 6%">#</th>
<th style="width: px">제목</th>
<th style="width: 12%">작성자</th>
<th style="width: 12%">작성일</th>
<th style="width: 10%">조회수</th>
</tr>
</thead>
<tbody>
<c:set value="${pagingVO.dataList }" var="noticeList"/>
<c:choose>
<c:when test="${empty noticeList }">
<tr>
<td colspan="5">조회하신 게시글이 존재하지 않습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${noticeList }" var="notice">
<tr>
<td>${notice.boNo }</td>
<td>
<a href="/notice/detail.do?boNo=${notice.boNo }">
${notice.boTitle }
</a>
</td>
<td>${notice.boWriter }</td>
<td>${notice.boDate }</td>
<td>${notice.boHit }</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
</div>
<div class="card-body">
<button type="button" class="btn btn-primary" id="newBtn">등록</button>
</div>
<!-- /.card-body -->
<div class="card-footer clearfix" id="pagingArea">
${pagingVO.pagingHTML }
</div>
</div>
</div>
</div>
</div>
</section>
<script>
$(function(){
var searchForm = $("#searchForm");
var pagingArea = $("#pagingArea");
var newBtn = $("#newBtn");
pagingArea.on("click", "a", function(event){
event.preventDefault();
var pageNo = $(this).data("page"); // key를이용하여 페이지 번호가 넘어옴
searchForm.find("#page").val(pageNo);
searchForm.submit();
});
newBtn.on("click", function(){
location.href = "/notice/form.do";
});
});
</script>