본문 바로가기
BACK-END/SPRING

[Spring]Spring-MyBatis를 이용해 공지사항 게시판 만들기

by 썬키 2022. 3. 2.

오늘은 Spring-MyBatis를 이용해 공지사항 게시판을 만들었다.

공지사항 게시판에서는 검색을 할 수 있고,

공지종류(현재/예약/지난/전체)를 선택해서 공지사항을 볼 수 있는 기능을 중점적으로 다뤘다.

 

기본적인 흐름들은 앞글에서 다뤘으니 넘어가도록 하고, SQL문과 Controller, JSP를 다뤄보자.

 

<NoticeMapper.java>

package com.webjjang.notice.mapper;

import java.util.List;

import com.webjjang.notice.vo.NoticeVO;
import com.webjjang.util.PageObject;

public interface NoticeMapper {

	// 1. 리스트
	public List<NoticeVO> list(PageObject pageObject) throws Exception;
	
	// 1-1. 전체 데이터 개수
	public long getTotalRow(PageObject pageObject) throws Exception;
	
	// 2. 보기
	public NoticeVO view(long no) throws Exception;
	
	// 3. 글쓰기
	public int write(NoticeVO vo) throws Exception;
	
	// 4. 글수정
	public int update(NoticeVO vo) throws Exception;
	
	// 5. 삭제
	public int delete(long no) throws Exception;
}


<NoticeController.java>

package com.webjjang.notice.controller;

import java.net.URLEncoder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.webjjang.notice.service.NoticeService;
import com.webjjang.notice.vo.NoticeVO;
import com.webjjang.util.PageObject;

import lombok.extern.log4j.Log4j;

@Controller
@RequestMapping("/notice")
// log 객체를 자동으로 만들어준다.
@Log4j
public class NoticeController {

	@Autowired
	private NoticeService service;

	// 1. list(버튼 눌러서 이동하는 페이지는 get방식)
	// PageObject의 period 기능 - 현재공지:pre, 지난공지:old, 예약공지:res, 전체공지:all
	@GetMapping("/list.do")
	public String list(@ModelAttribute PageObject pageObject, Model model) throws Exception{
		if(pageObject.getPage()<1) pageObject.setPage(1);
		System.out.println("NoticeController.list().pageObject - " + pageObject);
		
		// 로그 정보 출력
		log.info("log 출력 - pageObject : " + pageObject);
		// 로그 경고 출력
//		log.warn("log warn 출력 - pageObject : " + pageObject);
		// 로그 오류 출력
//		log.error("log error 출력 - pageObject : " + pageObject);
		model.addAttribute("list", service.list(pageObject));
		// /WEB-INF/views/ + board/list + .jsp → servlet-context.xml에 설정
		return "notice/list";
	}
	// 2. view
	@GetMapping("/view.do")
	public String view(long no, Model model) throws Exception {
		System.out.println("NoticeController.view()");
		
		NoticeVO vo = service.view(no);
		vo.setContent(vo.getContent().replace("\n", "<br>"));
		model.addAttribute("vo", vo);
		return "notice/view";
	}
	// 3. writeForm
	@GetMapping("/write.do")
	public String writeForm() throws Exception{
		System.out.println("NoticeController.writeForm()");
		return "notice/write";
	}
	// 3-1. write
	@PostMapping("/write.do")
	public String write(NoticeVO vo, int perPageNum) throws Exception{
		System.out.println("NoticeController.write()");
		service.write(vo);
		return "redirect:list.do?page=1&perPageNum=" + perPageNum;
	}
	// 4-1. updateForm
		@GetMapping("update.do")
		public String updateForm(long no, Model model) throws Exception {
			System.out.println("NoticeController.updateForm().no - " + no);
			
			model.addAttribute("vo", service.view(no));
			
			return "notice/update";
		}
		// 4-2. update
		@PostMapping("update.do")
		public String update(PageObject pageObject, NoticeVO vo) throws Exception{
			System.out.println("NoticeController.update().vo - " + vo);
			service.update(vo);
			return "redirect:view.do?no=" + vo.getNo()
					+ "&page="+ pageObject.getPage() +"&perPageNum=" + pageObject.getPerPageNum()
					// 자바 부분의 한글 코드와 운영 한글 코드가 다르므로 자바에서 꺼내서 넣으면 깨진다. 엔코딩을 해야 한다.
					+ "&key="+ pageObject.getKey() +"&word=" + URLEncoder.encode(pageObject.getWord(), "utf-8")
					;
		}
		// 5. delete
		@GetMapping("delete.do")
		public String delete(long no, int perPageNum) throws Exception{
			System.out.println("NoticeController.delete().no - " + no);
			service.delete(no);
			return "redirect:list.do?perPageNum=" + perPageNum;
		}
		
		
	}

 

<NoticeMapper.xml>

<?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.webjjang.notice.mapper.NoticeMapper">

	<!-- // 1. 리스트(id와 메소드 이름을 맞춰준다.) -->
	<select id="list" resultType="com.webjjang.notice.vo.NoticeVO">
		select no, title, startDate, endDate, updateDate
		from (
		select rownum rnum, no, title, startDate, endDate, updateDate
		from(
		select no, title, startDate, endDate, updateDate
		from notice
		where 1 = 1
		<include refid="search" />
		<include refid="searchPeriod" />
		order by updateDate desc
		)
		)
		where rnum between #{startRow} and #{endRow}
	</select>

	<!-- // 1-1. 전체 데이터 개수 -->
	<select id="getTotalRow" resultType="Long">
		select count(*) from notice
		where 1 = 1
		<include refid="search" />
		<include refid="searchPeriod" />
	</select>
	<!-- 검색 조건 처리를 위한 태그 - 부분 태그 : SQL : 동적쿼리 작성 -->
	<sql id="search">
		<if test="word != null and word != ''.toString">
			and (
			<if test="key == 't'.toString()">
				title like '%' || #{word} || '%'
			</if>
			<if test="key == 'c'.toString()">
				content like '%' || #{word} || '%'
			</if>
			<if test="key == 'tc'.toString()">
				title like '%' || #{word} || '%'
				or content like '%' || #{word} || '%'
			</if>
			)
		</if>
	</sql>

	<sql id="searchPeriod">
		and (
		<if test="period == 'pre'">
			<![CDATA[
			startDate <= sysdate and endDate >= trunc(sysdate)
			]]>
		</if>
		<if test="period == 'old'">
			<![CDATA[
			endDate < sysdate
			]]>
		</if>
		<if test="period == 'res'">
			<![CDATA[
			startDate > sysdate
			]]>
		</if>
		<if test="period == 'all'">
			<![CDATA[
			1 = 1
			]]>
			<!-- 67행은 true를 만들기 위한 조건: 1 = 1 -->
		</if>
		)
	</sql>

	<!-- // 2. 보기 -->
	<select id="view" resultType="com.webjjang.notice.vo.NoticeVO">
		select no, title, content,
		startDate, endDate, updateDate, writeDate
		from notice
		where no = #{no}
	</select>

	<!-- // 3. 글쓰기 -->
	<insert id="write">
		insert into notice(no, title, content, startDate,
		endDate)
		values(notice_seq.nextval, #{title}, #{content}, #{startDate},
		#{endDate})
	</insert>

	<!-- // 4. 수정 -->
	<update id="update">
		update notice
		set title = #{title}, content =
		#{content}, startDate = #{startDate}, endDate = #{endDate}, updateDate
		= sysdate
		where no = #{no}
	</update>

	<!-- // 5. 삭제 -->
	<delete id="delete">
		delete from notice
		where no = #{no}
	</delete>
</mapper>

 

<list.jsp>

<%@page import="com.webjjang.util.PageObject"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="pageNav" tagdir="/WEB-INF/tags"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>공지사항 리스트</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
	src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
	<style type="text/css">
.dataRow:hover {
	background: #eee;
	cursor: pointer;
}
</style>
<script type="text/javascript">
	$(function() {
		$(".dataRow")
				.click(
						function() {
							var no = $(this).find(".no").text();
							location = "view.do?no="
									+ no
									+ "&inc=1&page=${pageObject.page}&perPageNum=${pageObject.perPageNum}&key=${pageObject.key}&word=${pageObject.word}";
						});
		// perPageNum 데이터의 변경 이벤트 처리 → jQuery에 대한 이벤트
		$("#perPageNumSelect").change(function() {
			// 			alert("값 변경");
			$("#perPageNumForm").submit()
		});

	});
</script>
</head>
<body>
<div class="container">
<h2 style="text-align: center">공지사항 리스트</h2>
<div class="row" style="margin-bottom: 5px;">
<!-- 기간 검색에 대한 div -->
<div style="margin: 5px 15px; text-align: center;">
	<a href="list.do?page=${pageObject.page }&perPageNum=${pageObject.perPageNum }&key=${pageObject.key }&word=${pageObject.word }&period=pre" class='btn btn-${(pageObject.period=="pre")?"primary":"default"}'>현재</a>
	<a href="list.do?page=${pageObject.page }&perPageNum=${pageObject.perPageNum }&key=${pageObject.key }&word=${pageObject.word }&period=old" class='btn btn-${(pageObject.period=="old")?"primary":"default"}'>지난</a>
	<a href="list.do?page=${pageObject.page }&perPageNum=${pageObject.perPageNum }&key=${pageObject.key }&word=${pageObject.word }&period=res" class='btn btn-${(pageObject.period=="res")?"primary":"default"}'>예약</a>
	<a href="list.do?page=${pageObject.page }&perPageNum=${pageObject.perPageNum }&key=${pageObject.key }&word=${pageObject.word }&period=all" class='btn btn-${(pageObject.period=="all")?"primary":"default"}'>전체</a>
</div>
<!-- 검색에 대한 div -->
			<div style="text-align:right;">
				<form class="form-inline">
				<input type="hidden" name="perPageNum" value="${pageObject.perPageNum }">
					<div class="input-group">
						<select name="key" class="form-control">
							<option value="t" ${(pageObject.key == "t")?"selected":"" }>제목</option>
							<option value="c" ${(pageObject.key == "c")?"selected":"" }>내용</option>
							<option value="tc" ${(pageObject.key == "tc")?"selected":"" }>제목+내용</option>
						</select>
					</div>
					<div class="input-group">
						<input type="text" class="form-control" placeholder="Search"
							name="word" value="${pageObject.word }">
						<div class="input-group-btn">
							<button class="btn btn-default" type="submit">
								<i class="glyphicon glyphicon-search"></i>
							</button>
						</div>
					</div>
				</form>
			</div>
			<!-- 한 페이지 당 보여주는 데이터 갯수 -->
			<div style="align:right;">
				<form action="list.do" class="form-inline" id="perPageNumForm">
					<input type="hidden" name="page" value="1"> <input
						type="hidden" name="key" value="${pageObject.key }"> <input
						type="hidden" name="word" value="${pageObject.word }">
					<div class="form-group">
						<label><select name="perPageNum"
							class="form-control" id="perPageNumSelect">
								<option ${(pageObject.perPageNum == 5)?"selected":"" }>5</option>
								<option ${(pageObject.perPageNum == 10)?"selected":"" }>10</option>
								<option ${(pageObject.perPageNum == 15)?"selected":"" }>15</option>
								<option ${(pageObject.perPageNum == 20)?"selected":"" }>20</option>
						</select>
						개씩 보기</label>
					</div>
				</form>
			</div>
		</div>
		<table class="table">
			<tr>
				<th>번호</th>
				<th>제목</th>
				<th>시작일</th>
				<th>종료일</th>
				<th>수정일</th>
			</tr>
			<c:forEach items="${list }" var="vo">
				<tr class="dataRow">
					<td class="no">${vo.no }</td>
					<td>${vo.title }</td>
					<td><fmt:formatDate value="${vo.startDate }"
							pattern="yyyy-MM-dd" /></td>
					<td><fmt:formatDate value="${vo.endDate }"
							pattern="yyyy-MM-dd" /></td>
					<td><fmt:formatDate value="${vo.updateDate }"
							pattern="yyyy-MM-dd" /></td>
				</tr>
			</c:forEach>
			<tr>
				<td colspan="5" align="center"><pageNav:pageNav
						listURI="list.do" pageObject="${pageObject }"
						query="&key=${pageObject.key }&word=${pageObject.word }" /></td>
			</tr>
			<tr>
				<td colspan="5" align="center"><a href="write.do?perPageNum=${pageObject.perPageNum }"
					class="btn btn-primary">공지 등록하기</a></td>
			</tr>
		</table>
	</div>
</body>
</html>


우리가 일상 생활시 편하게 쓰고 있었던 기능들이

뒤에서는 이렇게 복잡한 코드로 이루어져있다는 것을 깨달았다.

 

공지 종류별로 정렬하는 것은 이클립스에서도 했던것이기 때문에 어렵지 않을거라 생각했는데

SQL문을 작성하는 BoardMapper.xml 부분에서 굉장히 어렵게 써야 하는구나를 알게 되었다.

 

include 태그가 필요하고, 부등호 사용이 필요할 때는 CDATA를 이용한다는것이 중요하다.

 

 

<Spring-Sitemesh>

 

1. pom.xml에 <defendency> 태그로 라이브러리 넣어주기

<dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>2.4.2</version>
</dependency>

2. src - main - webapp - WEB-INF 폴더에 decorators.xml / 
sitemesh.xml 추가하기


3. web.xml에 필터 추가하기
  <filter>
   <filter-name>sitemesh</filter-name>
   <filter-class>
   com.opensymphony.sitemesh.webapp.SiteMeshFilter
   </filter-class>
  </filter>
  <filter-mapping>
   <filter-name>sitemesh</filter-name>
   <url-pattern>*.do</url-pattern>
  </filter-mapping>

4. src - main - webapp - WEB-INF - views - decorator에 
default_decorator.jsp 파일 추가

 

Spring에 Sitemesh 라이브러리를 추가하는 것은, 이클립스 때와 달리

<defendency> 태그를 이용해 maven 방식으로 넣는것 이외에는 크게 달라진게 없었다.

 

 

notice - list.jsp + sitemesh

 

 

 

댓글