
1. Controller(흐름제어)
BoardController
@Controller
public class BoardController {
@Qualifier("BoardService")
@Autowired
BoardService boardService;
// 게시글 목록, 검색, 페이징
@RequestMapping(value = "/list", method = RequestMethod.GET)
public ModelAndView list(Criteria cri) throws Exception {
ModelAndView mv = new ModelAndView("list");
cri.setCategory(current);
PageMaker pageMaker = new PageMaker();
pageMaker.setCri(cri);
pageMaker.setTotalCount(boardService.countPostListSearch(cri));
if(boardService.countPostListSearch(cri)>0) {
List<BoardVO> postList = boardService.listAllSearch(cri);
mv.addObject("postList", postList);
}
mv.addObject("pageMaker", pageMaker);
return mv;
}
}
2. Service(비즈니스 로직, DB 연동 이외의 작업)
BoardService
public interface BoardService {
//게시글 목록, 검색, 페이징
public List<BoardVO> listAllSearch(Criteria cri) throws Exception;
//페이징 전체 목록 개수
public int countPostListSearch(Criteria cri) throws Exception;
}
BoardServiceImpl
@Service("BoardService")
public class BoardServiceImpl implements BoardService {
@Autowired
BoardDao boardDao;
//게시글 목록, 검색, 페이징
@Override
public List<BoardVO> listAllSearch(Criteria cri) throws Exception {
return boardDao.listAllSearch(cri);
}
//페이징 전체 목록 개수
@Override
public int countPostListSearch(Criteria cri) throws Exception {
return boardDao.countPostListSearch(cri);
}
}
3. Model(비즈니스 로직, DB 연동 작업)
BoardVO(게시판 데이터 저장 클래스)
public class BoardVO {
private int boardSeq;
private String category;
private String title;
private String content;
private String file;
private Timestamp register_date;
private int hit;
private String comment_yn;
private String delete_yn;
private int userSeq;
private String checkTitle;
//DB 컬럼은 아니지만, 댓글수를 가져올 때 별칭을 comment로 붙여서 가져왔음
private int comment;
//DB 컬럼은 아니지만, 사용자 이름(등록자 또는 작성자)을 가져올 때 별칭을 name으로 붙여서 가져왔음
private String writer;
//DB 컬럼은 아니지만 write페이지에서 file 저장
private MultipartFile p_file;
//DB 컬럼은 아니지만 서버에 저장된 uuid 파일 이름의 별칭을 storedFileName로 붙여서 가져왔음. view페이지에서 이용.
private String storedFileName;
/***********getter, setter***********/
@Override
public String toString() {
return "BoardVO [boardSeq=" + boardSeq + ", category=" + category + ", title=" + title + ", content=" + content
+ ", file=" + file + ", register_date=" + register_date + ", hit=" + hit + ", comment_yn=" + comment_yn
+ ", delete_yn=" + delete_yn + ", userSeq=" + userSeq + ", checkTitle=" + checkTitle + ", comment="
+ comment + ", writer=" + writer + ", p_file=" + p_file + "]";
}
}
Criteria(페이징 데이터 저장 클래스)
package com.ncomz.sample.dto;
import org.springframework.web.util.UriComponentsBuilder;
public class Criteria {
private int page;
private int perPageNum = 10; //원하는 페이징 끝 번호
private String option;
private String keyword;
private String category; //list 출력 시 category 지정
public int getPageStart() {
return (this.page-1)*perPageNum;
}
public Criteria() {
this.page = 1;
this.option = null;
this.keyword = null;
}
public int getPage() {
return page;
}
public void setPage(int page) {
if(page <= 0) {
this.page = 1;
} else {
this.page = page;
}
}
public int getPerPageNum() {
return perPageNum;
}
public void setPerPageNum(int perPageNum) {
this.perPageNum = perPageNum;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public String getOption() {
return option;
}
public void setOption(String option) {
this.option = option;
}
//
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
//
public String makeQuery() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance().queryParam("page", page).queryParam("perPageNum", this.perPageNum);
if (option!=null) {
uriComponentsBuilder.queryParam("option", this.option).queryParam("keyword", this.keyword);
}
return uriComponentsBuilder.build().encode().toString();
}
@Override
public String toString() {
return "Criteria [page=" + page + ", perPageNum=" + perPageNum + ", option=" + option + ", keyword="
+ keyword + "]";
}
}
PageMaker(페이징 데이터 저장 클래스)
package com.ncomz.sample.dto;
import org.springframework.web.util.UriComponentsBuilder;
public class PageMaker {
private Criteria cri;
private int totalCount;
private int startPage = 1;
private int endPage;
private boolean prev;
private boolean next;
private int displayPageNum = 10;
public Criteria getCri() {
return cri;
}
public void setCri(Criteria cri) {
this.cri = cri;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
calcData();
}
private void calcData() {
endPage = (int) (Math.ceil(cri.getPage() / (double) displayPageNum) * displayPageNum);
startPage = (endPage - displayPageNum) + 1;
if (startPage <= 0)
startPage = 1;
int tempEndPage = (int) (Math.ceil(totalCount / (double) cri.getPerPageNum()));
if (endPage > tempEndPage) {
endPage = tempEndPage;
}
prev = startPage == 1 ? false : true;
next = endPage * cri.getPerPageNum() < totalCount ? true : false;
}
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;
}
public boolean isPrev() {
return prev;
}
public void setPrev(boolean prev) {
this.prev = prev;
}
public boolean isNext() {
return next;
}
public void setNext(boolean next) {
this.next = next;
}
public int getDisplayPageNum() {
return displayPageNum;
}
public void setDisplayPageNum(int displayPageNum) {
this.displayPageNum = displayPageNum;
}
public String makeQuery(int page) {
UriComponentsBuilder uriComponentsBuilder =
UriComponentsBuilder.newInstance().queryParam("page", page).queryParam("perPageNum", this.cri.getPerPageNum());
if (this.cri.getOption() != null) {
uriComponentsBuilder.queryParam("option", this.cri.getOption()).queryParam("keyword", this.cri.getKeyword());
}
return uriComponentsBuilder.build().encode().toString();
}
}
BoardDao
public interface BoardDao {
public List<BoardVO> listAllSearch(Criteria cri) throws Exception;
public int countPostListSearch(Criteria cri) throws Exception;
}
BoardDaoImpl
@Repository
public class BoardDaoImpl implements BoardDao {
SqlSession sqlSession;
//게시글 목록, 검색, 페이징
@Override
public List<BoardVO> listAllSearch(Criteria cri) throws Exception {
return sqlSession.selectList("com.ncomz.sample.dao.BoardDao.listAllSearch", cri);
}
//검색 페이징
@Override
public int countPostListSearch(Criteria cri) throws Exception {
return sqlSession.selectOne("com.ncomz.sample.dao.BoardDao.countPostListSearch", cri);
}
}
BoardDao.xml(SQL문)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 인터페이스 경로까지 -->
<mapper namespace="com.ncomz.sample.dao.BoardDao">
<sql id="search">
<if test="keyword != null">
<choose>
<when test="option == 'all'.toString()">
AND
(title like CONCAT('%', #{keyword}, '%')
OR
content like CONCAT('%', #{keyword}, '%'))
</when>
<when test="option == 'p_content'.toString()">
AND
(title like CONCAT('%', #{keyword}, '%')
OR
content like CONCAT('%', #{keyword}, '%'))
</when>
<otherwise>
AND
${option} like CONCAT('%', #{keyword}, '%')
</otherwise>
</choose>
</if>
</sql>
<!-- 검색 목록 -->
<select id="listAllSearch" parameterType="com.ncomz.sample.dto.Criteria" resultType="com.ncomz.sample.dto.BoardVO">
SELECT
boardSeq,
title,
register_date,
u.name as writer,
hit,
delete_yn,
(SELECT COUNT(*) FROM TB_JMJ_COMMENT WHERE boardSeq = b.boardSeq) AS comment
FROM TB_JMJ_USER as u
JOIN TB_JMJ_BOARD as b
ON u.userSeq = b.userSeq
WHERE
delete_yn = 'n'
AND category = #{category}
<include refid = "search"></include>
ORDER BY
boardSeq desc
LIMIT #{pageStart}, #{perPageNum}
</select>
<!-- 페이징 검색 게시글 개수 -->
<select id="countPostListSearch" parameterType="com.ncomz.sample.dto.Criteria" resultType="int">
SELECT
count(*)
FROM
TB_JMJ_BOARD
WHERE
delete_yn = 'n'
AND category = #{category}
<include refid = "search"></include>
</select>
</mapper>
4. View(화면)
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ page session="false" %>
<% request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=UTF-8"); %>
<html>
<head>
<title>게시판</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<%
String pageNum = request.getParameter("page");
%>
<script type="text/javascript">
var clicktitle = "unsorted";
var clickwriter = "unsorted";
var clickhit = "unsorted";
var clickdate = "unsorted";
function Sorted(id){
var SortedResult = new Array();
<c:forEach items="${postList}" var = "postListTitle">
console.log("${postListTitle.boardSeq}");
SortedResult.push({
title: "${postListTitle.title}" ,
comment: "${postListTitle.comment}" ,
hit: "${postListTitle.hit}",
writer: "${postListTitle.writer}",
register_date: "<fmt:formatDate value='${postListTitle.register_date}' pattern='yyyy-MM-dd a hh:mm:ss'/>" ,
boardSeq: "${postListTitle.boardSeq}"
});
</c:forEach>
var name = id;
if(name == "clicktitle"){
objSort(SortedResult,"title", id);
tagParsing(SortedResult);
}else if(name == "clickwriter"){
objSort(SortedResult,"writer", id);
tagParsing(SortedResult);
}else if(name == "clickhit"){
objSortNum(SortedResult,"hit", id);
tagParsing(SortedResult);
}else if(name == "clickdate"){
objSort(SortedResult,"register_date", id);
tagParsing(SortedResult);
}
}
//objSort 함수
function objSort(obj, sort, id){
if(eval(id) == "unsorted"){
obj.sort(function(a,b){
if(a[sort] > b[sort]) return 1;
else if(a[sort] < b[sort]) return -1;
});//sort
eval(id +'= "sorted"');
} else{
obj.sort(function(a,b){
if(a[sort] > b[sort]) return -1;
else if(a[sort] < b[sort]) return 1;
});//sort
eval(id +'= "unsorted"');
}
}
//objSortNum (조회수)
function objSortNum(obj, sort, id){
if(eval(id) == "unsorted"){
obj.sort(function(a,b){
return a[sort] - b[sort]
});//sort
eval(id +'= "sorted"');
} else{
obj.sort(function(a,b){
return b[sort] - a[sort]
});//sort
eval(id +'= "unsorted"');
}
}
//tagParsing 함수 function tagParsing(obj, init)
function tagParsing(obj){
var title=0;
var comment=0;
var writer=0;
var hit=0;
var register_date=0;
var boardSeq=0;
var pageNum= document.getElementById("pageNumber").value;
if(pageNum == null || pageNum == "" || isNaN(pageNum)){
pageNum = 1;
}
var seq = (pageNum-1)*10;
var len = obj.length;
var tag = "<colgroup><col width='50'><col width='200'><col width='80'><col width='50'><col width='80'></colgroup>";
for(i=0; i<obj.length ; i ++){
var seqNumber = seq + i + 1;
tag+= "<tr><td align='center'>"
+ "<input type='hidden' id='pageNumber' name='pageNumber' value=<%=pageNum %> />"
+ seqNumber + "</td>"
+"<td id='title'>" + "<a href='/view?boardSeq=" + obj[i].boardSeq + "'>"+ obj[i].title + "(" + obj[i].comment + ")" +"</a>" +"</td>"
+ "<td id='writer'>" + obj[i].writer + "</td>"
+ "<td id='hit'>" + obj[i].hit + "</td>"
+ "<td id='date'>" + obj[i].register_date + "</td></tr>";
title += obj[i].title;
comment += obj[i].comment;
writer += obj[i].writer;
hit += obj[i].hit;
register_date += obj[i].register_date;
boardSeq += obj[i].boardSeq;
}//for
document.getElementById("data").innerHTML = tag;
}
//제목 중복 체크
window.onload = function() {
var form = document.form1;
if('${writeResult}'=='1'){
alert('등록되었습니다.');
location.href="list?current=${current}";
}
if('${postDelResult}'=='1'){
alert('게시글이 삭제되었습니다.');
location.href="list?current=${current}";
}
//라디오 버튼 값
var radioVal = ${perPageNum};
if(radioVal == '5'){
$('input:radio[name=perPageNum]:input[value="10"]').attr("checked", false);
$('input:radio[name=perPageNum]:input[value="5"]').attr("checked", true);
}else if(radioVal == '20'){
$('input:radio[name=perPageNum]:input[value="10"]').attr("checked", false);
$('input:radio[name=perPageNum]:input[value="20"]').attr("checked", true);
} else{
$('input:radio[name=perPageNum]:input[value="10"]').attr("checked", true);
}
//말줄임표 (제목)
$(".ellipsisTitle").each(function(){
var length = 14; //표시할 글자수 정하기
$(this).each(function(){
if( $(this).text().length > length ){
//지정할 글자수 이후 표시할 텍스트
$(this).text($(this).text().substr(0,length)+'...');
}
});
});
//말줄임표 (작성자)
$(".ellipsisWriter").each(function(){
var length = 4; //표시할 글자수 정하기
$(this).each(function(){
if( $(this).text().length > length ){
//지정할 글자수 이후 표시할 텍스트
$(this).text($(this).text().substr(0,length)+'...');
}
});
});
//말줄임 된 것만 찾아서 툴팁 표시
/* $('.ellipsisTitle').each(function () {
if (this.offsetWidth < this.scrollWidth){
$(this).attr('title', $(this).text());
}
}); */
console.log('${writeResult}'+"*****************");
//탭 메뉴 색 변경
if('${current}' == 'dance'){
$('#currentDashboard').removeClass('current');
$('#currentDance').css({
"background-color": "#2baae1",
"color": "#222"
});
} else if('${current}' == 'movie'){
$('#currentDashboard').removeClass('current');
$('#currentMovie').css({
"background-color": "#2baae1",
"color": "#222"
});
}
}
</script>
<style type="text/css">
div.container{
text-align: center;
width: 100%;
}
table {
font-family: "Lato","sans-serif";
font-size: 12px;
width: 60%;
border-collapse: collapse;
margin-top: 10px;
table-layout: fixed;
margin-right: auto;
margin-left: auto;
}
th {
width: 80px;
height: 25px;
text-align: center;
background-color: #2baae1;
color: white;
}
td {
height: 25px;
}
a {
text-decoration: none;
color: #000000;
}
.pageNumberLi {
list-style: none;
float: left;
padding: 6px;
}
/* after 위치를 이 요소의 기준으로 상대적으로 잡아준다. */
[data-tooltip-text]:hover {
position: relative;
}
/* 가상 선택자를 사용하여 가상으로 자녀 요소를 생성한다. */
[data-tooltip-text]:after {
/* bottom 값과 opacity 값에 대해 애니메이션 효과를 준다. */
transition: bottom .3s ease-in-out, opacity .3s ease-in-out;
background-color: rgba(28, 166, 188, 0.8);
box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4);
border-radius: 5px;
color: #FFFFFF;
font-size: 12px;
margin-bottom: 10px;
padding: 7px 12px;
/* 부모 요소를 기준으로 설정하게 된다. */
position: absolute;
width: auto;
min-width: 50px;
max-width: 300px;
/* 요소의 경계에서 break point가 아니어도 줄바꿈을 한다. */
word-wrap: break-word;
z-index: 9999;
opacity: 0;
left: -9999px;
top: 90%;
/* 툴팁의 내용을 넣어준다. */
content: attr(data-tooltip-text);
}
[data-tooltip-text]:hover:after {
top: 130%;
left: 0;
opacity: 1;
}
</style>
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
</head>
<body>
<jsp:include page="/WEB-INF/views/header.jsp" flush ="false"/>
<div class="container">
<form method="get" action="/list" name="form1">
<!-- 검색바 -->
<table>
<tr>
<td>
<select name="option">
<option value="all">전체검색</option>
<option value="title" <c:out value="${pageMaker.cri.option == 'title'?'selected':''}" />>제목</option>
<option value="content" <c:out value="${pageMaker.cri.option == 'content'?'selected':''}" />>제목+내용</option>
<option value="writer" <c:out value="${pageMaker.cri.option == 'writer'?'selected':''}" />>작성자</option>
</select>
<input type="text" name="keyword" value="${pageMaker.cri.keyword}" />
<input type="hidden" name="current" value="${current}" />
<input type="submit" value="검색"/>
</td>
</tr>
<tr>
<td>
<div style="float:left; border: 0.3px solid #9F9D9D; width: 85px; height: 17px; vertical-align:middle; text-align:center;">게시글수</div>
<div style="float:left;">
<input type="radio" name="perPageNum" value="5">5
<input type="radio" name="perPageNum" value="10" checked="checked">10
<input type="radio" name="perPageNum" value="20">20
</div>
</td>
</tr>
</table>
</form>
<!-- 게시판 글 목록 -->
<table border="1" width="1200px">
<colgroup>
<col width="50">
<col width="200">
<col width="80">
<col width="50">
<col width="80">
</colgroup>
<tr>
<th>번호</th>
<th id="clicktitle" onclick="Sorted('clicktitle')">
제목
</th>
<th id="clickwriter" onclick="Sorted('clickwriter')">
등록자
</th>
<th id="clickhit" onclick="Sorted('clickhit')">
조회수
</th>
<th id="clickdate" onclick="Sorted('clickdate')">
등록일
</th>
</tr>
</table>
<table border="1" id="data" width="1200px">
<colgroup>
<col width="50">
<col width="200">
<col width="80">
<col width="50">
<col width="80">
</colgroup>
<c:choose>
<c:when test="${fn:length(postList) == 0}">
<tr>
<td colspan="5" align="center">등록된 게시글이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="postList" items="${postList}" varStatus="status">
<tr>
<td align="center">
<input type="hidden" id="pageNumber" name="pageNumber" value=<%= pageNum %> />
<!-- (현재 페이지 개수 - 1)*페이지당 보여줄 게시글 수 + 카운트 -->
${pageMaker.cri.getPageStart() + status.count}
</td>
<td id="title">
<a href="/view?boardSeq=${postList.boardSeq}¤t=${current}" data-tooltip-text="${postList.title}" class="ellipsisTitle">${postList.title}</a>
(${postList.comment})
</td>
<td id="writer">
<div class="ellipsisWriter" data-tooltip-text="${postList.writer}" class="ellipsisWriter">${postList.writer}</div>
</td>
<td id="hit">${postList.hit}</td>
<td id="date">
<fmt:formatDate value="${postList.register_date}" pattern="yyyy-MM-dd a hh:mm:ss"/>
</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</table>
<!-- 글 작성 페이지로 넘어가는 버튼 -->
<table width="1200px">
<tr align="right">
<td>
<button type="button" id="btnwrite" onclick="location.href='write?current=${current}'">글 작성</button>
</td>
</tr>
</table>
<table>
<tr>
<td>
<ul>
<c:if test="${pageMaker.prev }">
<li class="pageNumberLi">
<a href='<c:url value="list${pageMaker.makeQuery(pageMaker.startPage-1)}¤t=${current}"/>'><i>[이전]</i></a>
</li>
</c:if>
<c:forEach begin="${pageMaker.startPage }" end="${pageMaker.endPage }" var="pageNum">
<li class="pageNumberLi">
<a href='<c:url value="list${pageMaker.makeQuery(pageNum)}¤t=${current}"/>'> ${pageNum } </a>
</li>
</c:forEach>
<c:if test="${pageMaker.next && pageMaker.endPage >0 }">
<li class="pageNumberLi">
<a href='<c:url value="list${pageMaker.makeQuery(pageMaker.endPage+1) }¤t=${current}"/>'><i>[다음]</i></a>
</li>
</c:if>
</ul>
</td>
</tr>
</table>
</div>
</body>
</html>
'스프링(Spring) > 게시판 만들기' 카테고리의 다른 글
| [Spring 게시판] 상세보기(댓글) (0) | 2020.03.12 |
|---|---|
| [Spring 게시판] 등록(첨부파일, Validation) (0) | 2020.03.12 |
| [Spring 게시판] 대시보드 화면(탭메뉴, TOP5, 주간접속통계) (0) | 2020.03.12 |
| [Spring 게시판] 로그인(Session, Validation) (0) | 2020.03.12 |
| [Spring 게시판] 환경설정 (0) | 2020.03.12 |
댓글