관리 메뉴

거니의 velog

231130_SPRING 2 (7-1) 본문

대덕인재개발원/대덕인재개발원_웹기반 애플리케이션

231130_SPRING 2 (7-1)

Unlimited00 2023. 11. 30. 08:35

-- notice table 생성
create table notice(
    bo_no number(8) not null,
    bo_title VARCHAR2(300) not null,
    bo_content VARCHAR2(4000) not null,
    bo_writer VARCHAR2(150) not null,
    bo_date date not null,
    bo_hit number(8) DEFAULT 0 null,
    CONSTRAINT pk_notice PRIMARY KEY(bo_no)
);

-- notice sequence 생성
create sequence seq_notice increment by 1 start with 1 nocache;

-- noticefile table 생성
create table noticefile(
    file_no number(8) not null,
    bo_no number(8) not null,
    file_name VARCHAR2(300) not null,
    file_size number(20) not null,
    file_fancysize VARCHAR2(100) not null,
    file_mime VARCHAR2(100) not null,
    file_savepath VARCHAR2(150) not null,
    file_downcount number(8) not null,
    CONSTRAINT pk_noticefile PRIMARY KEY(file_no),
    CONSTRAINT fk_noticefile_bo_no foreign key(bo_no) references notice(bo_no)
);

-- noticefile sequence 생성
create sequence seq_noticefile increment by 1 start with 1 nocache;

commit;


package kr.or.ddit.controller.tiles;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping
public class TilesSettingController {

	/*
	 * [ 부트스트랩을 이용한 CRUD를 진행해 보자! ]
	 * 
	 * 		1. Tiles란?
	 * 
	 * 			어떤 JSP를 템플릿으로 사용하고 템플릿의 각 영역을 어떤 내용을 채울지에 대한 정보를 설정한다.
	 * 			하나의 화면을 만들다보면 공통적이고 반복적으로 생성해야 하는 header, footer와 같은 영역들이 존재한다.
	 * 			우리는 그러한 공통부분들을 분리하여 반복적으로 컴포넌트들을 사용하는게 아닌 공통적인 부분은 한번만 가져다 쓰고
	 * 			변화하는 부분에 대해서만 동적으로 변환해 페이지를 관리할 수 있어야 할 것이다.
	 * 			이렇게 header/footer/menu 등 공통적인 소스를 분리하여 한 화면에서 동적으로 레이아웃을 한 곳에 배치하여
	 * 			설정하고 관리할 수 있도록 도와주는 페이지 모듈화를 돕는 프레임워크이다.
	 * 
	 * 			- 아래 jsp들을 이용하여 페이지 모듈화 진행
	 * 			template.jsp
	 * 			> header.jsp
	 * 			> content source(jsp)
	 * 			> footer.jsp
	 * 
	 * 			** 그 외에 다양한 영역의 페이지는 구현하고자 하는 시나리오를 바탕으로 페이지가 구성될 때
	 * 				추가적으로 레이아웃 영역을 분리하여 작성하면 된다.
	 * 
	 * 		2. Tiles Layout 구현 설명
	 * 
	 * 			1) Tiles 의존 관계 등록
	 * 
	 * 				- tiles-core
	 * 				- tiles-api
	 * 				- tiles-servlet
	 * 				- tiles-jsp
	 * 
	 * 				** 의존 관계 등록 후 Maven > Update Projects로 프로젝트에 라이브러리 반영
	 * 
	 * 			2) servlet-context.xml 수정
	 * 				- ViewResolver order 순서 변경
	 * 				- tilesViewResolver Bean 등록 진행
	 * 
	 * 			3) tiles 설정 위한 xml 생성
	 * 				- /WEB-INF/spring/tiles-config.xml
	 * 
	 * 			4) tiles-config.xml 에 설정한 layout 설정대로 페이지 생성(jsp 구성)
	 * 
	 * 		3. 공지사항 게시판(부트스트랩 디자인) CRUD 실습
	 * 
	 * 			3-1) 데이터베이스 준비
	 * 
	 * 				notice table, noticefile table 생성
	 * 				notice sequence, noticefile sequence 생성
	 * 
	 * 			3-2) 게시판 작성
	 * 
	 * 				- 게시판 목록 컨트롤러 만들기(crud/notice/NoticeRetrieveController)
	 * 				- 게시판 목록 화면 컨트롤러 메소드 만들기(noticeList:get)
	 * 				- 게시판 목록 화면 만들기(noticeboard/list.jsp)
	 * 				- 여기까지 확인
	 * 
	 * 				- 게시판 목록 화면 서비스 인터페이스 메소드 만들기
	 * 				- 게시판 목록 화면 서비스 클래스 메소드 만들기
	 * 				- 게시판 목록 화면 Mapper 인터페이스 메소드 만들기
	 * 				- 게시판 목록 화면 Mapper xml 쿼리 만들기
	 * 				- 게시판 목록 화면 페이지에서 페이징 적용된 목록 확인
	 * 				- 여기까지 확인
	 * 
	 * 				- 게시판 등록 컨트롤러 만들기 (crud/notice/NoticeInsertController)
	 * 				- 게시판 등록 화면 컨트롤러 메소드 만들기 (noticeInsertForm:get)
	 * 				- 게시판 등록 화면 만들기 (noticeboard/form.jsp)
	 * 				- 게시판 등록 기능 컨트롤러 메소드 만들기 (noticeInsert:post)
	 * 				- 게시판 등록 기능 서비스 인터페이스 메소드 만들기
	 * 				- 게시판 등록 기능 서비스 클래스 메소드 만들기
	 * 				- 게시판 등록 기능 Mapper 인터페이스 메소드 만들기
	 * 				- 게시판 등록 기능 Mapper xml 쿼리 만들기
	 * 				- 여기까지 확인
	 * 
	 * 				- 게시판 상세 화면 컨트롤러 메소드 만들기 (noticeDetail:get)
	 * 				- 게시판 상세 화면 서비스 인터페이스 메소드 만들기
	 * 				- 게시판 상세 화면 서비스 클래스 메소드 만들기
	 * 				- 게시판 상세 화면 Mapper 인터페이스 메소드 만들기
	 * 				- 게시판 상세 화면 Mapper xml 쿼리 만들기
	 * 				- 게시판 상세 화면 만들기 (noticeboard/detail.jsp)
	 * 				- 여기까지 확인
	 * 
	 * 				- 게시판 수정 화면 컨트롤러 메소드 만들기 (noticeUpdateForm:get)
	 * 				- 게시판 수정 화면 만들기 (noticeboard/form.jsp)
	 * 				- 게시판 수정 기능 컨트롤러 메소드 만들기 (noticeUpdate:post)
	 * 				- 게시판 수정 기능 서비스 인터페이스 메소드 만들기
	 * 				- 게시판 수정 기능 서비스 클래스 메소드 만들기
	 * 				- 게시판 수정 기능 Mapper 인터페이스 메소드 만들기
	 * 				- 게시판 수정 기능 Mapper xml 쿼리 만들기
	 * 				- 여기까지 확인
	 */
	
}

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() {}
	public PaginationInfoVO(int screenSize, int blockSize) {
		super();
		this.screenSize = screenSize;
		this.blockSize = blockSize;
	}
	
	public void setTotalRecord(int totalRecord) {
		this.totalRecord = totalRecord;
		// ceil은 올림에 해당한다. 즉, 나머지가 올림으로 페이지를 1 증가시킴
		// 총 게시글 수가 121개 일 떄, screenSize로 나누게 되면 몫은 12이지만 나머지가 1 존재한다.
		// 이 때, 올림을 하게 되면 나머지에 대한 부분이 몫으로 올림이 되므로 총 페이지는 13이 된다.
		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)) / 페이지 블록 사이즈 * 페이지 블록 사이즈
		// blockSize * blockSize는 1, 2, 3, 4 ... 페이지마다 실수 계산이 아닌 정수 계산을 이용해 endPage를 구한다.
		endPage = (currentPage + (blockSize - 1)) / blockSize * blockSize;
		startPage = endPage - (blockSize - 1);
	}
	
	public String getPagingHTML() {
		StringBuffer html = new StringBuffer();
		
		html.append("<ul class='pagination pagination-sm m-0 float-right'>");
		
		if(startPage > 1) {
			html.append("<li class='page-item'><a href='' class='page-link' data-page='"+(startPage - blockSize)+"'>Prev</a></li>");
		}
		
		// 현재 페이지부터 blockSize에 해당하는 페이지 정보를 출력할 반복문
		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)+"'>Next</a></li>");
		}
		
		html.append("</ul>");
		
		return html.toString();
	}
	
}

package kr.or.ddit.vo.crud;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class NoticeVO {

	private int boNo;
	private String boTitle;
	private String boContent;
	private String boWriter;
	private String boDate;
	private int boHit;
	
	private Integer[] delNoticeNo;
	private MultipartFile[] boFile;
	private List<NoticeFileVO> noticeFileList;
	
}

package kr.or.ddit.vo.crud;

import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class NoticeFileVO {

	private MultipartFile item;
	private Integer boNo;
	private Integer fileNo;
	private String fileName;
	private long fileSize;
	private String fileFancysize;
	private String fileMime;
	private String fileSavepath;
	private int fileDowncount;
	
}

package kr.or.ddit.service;

import java.util.List;

import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeService {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);

}

package kr.or.ddit.service.impl;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import kr.or.ddit.mapper.INoticeMapper;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

@Service
public class NoticeServiceImpl implements INoticeService {

	@Inject
	private INoticeMapper noticeMapper;
	
	@Override
	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeCount(pagingVO);
	}

	@Override
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeList(pagingVO);
	}

}

package kr.or.ddit.mapper;

import java.util.List;

import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeMapper {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);

}

<?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.INoticeMapper">
	
	<select id="selectNoticeCount" parameterType="pagingVO" resultType="int">
		select count(bo_no) from notice
	</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, bo_date, bo_hit 
				from notice 
				where 1=1 
				order by bo_no desc
			) a
		) b
		<![CDATA[
			where b.rnum >= #{startRow} and b.rnum <= #{endRow}
		]]>
	</select>
	
</mapper>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<!--  
		[마이바티스] 스프링에서 "_"를 사용한 컬럼명 사용 시(BOOK 테이블의 BOOK_ID와 같은 컬럼)
		카멜케이스로 읽어주는 역할(bookId와 같이)
		
		ex) 테이블 컬럼명이 member_id인 경우 jsp 화면 단에서 이 값을 사용시 memberId로 사용할 수 있다.
	-->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	
	<typeAliases>
		<typeAlias type="kr.or.ddit.vo.Board" alias="board"/>
		<typeAlias type="kr.or.ddit.vo.crud.CrudMember" alias="crudMember"/>
		<typeAlias type="kr.or.ddit.vo.crud.CrudMemberAuth" alias="crudMemberAuth"/>
		<typeAlias type="kr.or.ddit.vo.crud.PaginationInfoVO" alias="pagingVO"/>
		<typeAlias type="kr.or.ddit.vo.crud.NoticeVO" alias="noticeVO"/>
		<typeAlias type="kr.or.ddit.vo.crud.NoticeFileVO" alias="noticefileVO"/>
	</typeAliases>
	
</configuration>

package kr.or.ddit.controller.crud.notice;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
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
			) {
		PaginationInfoVO<NoticeVO> pagingVO = new PaginationInfoVO<NoticeVO>();
		
		// 현재 페이지 전달 후, start/endRow와 start/endPage 설정
		pagingVO.setCurrentPage(currentPage);
		
		int totalRecord = noticeService.selectNoticeCount(pagingVO); // 총 게시글 수 가져오기
		pagingVO.setTotalRecord(totalRecord);
		List<NoticeVO> dataList = noticeService.selectNoticeList(pagingVO);
		pagingVO.setDataList(dataList);
		
		model.addAttribute("pagingVO", pagingVO);
		
		return "notice/list";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<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>
</section>

<section class="content">
	<div class="container-fluid">
		<div class="row">
			<div class="col-md-12">
				<div class="card card-dark card-outline">
					<div class="card-header">
						<div class="card-tools">
							<div class="input-group input-group-sm" style="width: 440px;">
								<select class="form-control">
									<option>제목</option>
									<option>작성자</option>
								</select> <input type="text" name="table_search"
									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>
							</div>
						</div>
						<h3 class="card-title">공지사항</h3>
					</div>
					<!-- /.card-header -->
					<div class="card-body">
						<table class="table table-bordered">
							<thead class="table-dark">
								<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>${notice.boTitle }</td>
												<td>
													<font class="badge badge-danger" style="font-size: 14px;">${notice.boWriter }</font>
												</td>
												<td>${notice.boDate }</td>
												<td>${notice.boHit }</td>
											</tr>
										</c:forEach>
									</c:otherwise>									
								</c:choose>
							</tbody>
						</table>
					</div>
					<div class="card-footer" align="right">
						<button type="submit" class="btn btn-dark">등록</button>
					</div>
					<!-- /.card-body -->
					<div class="card-footer clearfix">
						<ul class="pagination pagination-md m-0 float-right">
							<li class="page-item"><a class="page-link" href="#">&laquo;</a></li>
							<li class="page-item"><a class="page-link" href="#">1</a></li>
							<li class="page-item"><a class="page-link" href="#">2</a></li>
							<li class="page-item"><a class="page-link" href="#">3</a></li>
							<li class="page-item"><a class="page-link" href="#">&raquo;</a></li>
						</ul>
					</div>
				</div>
			</div>
		</div>
</section>

- http://localhost/notice/list.do


[mainTemplate.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!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>
</head>

[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" %>
<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>
</section>

<section class="content">
	<div class="container-fluid">
		<div class="row">
			<div class="col-md-12">
				<div class="card card-dark card-outline">
					<div class="card-header">
						<div class="card-tools">
							<form class="input-group input-group-sm" style="width: 440px;" id="searchForm" name="searchForm">
								<input type="hidden" name="page" id="page" />
								<select class="form-control">
									<option>제목</option>
									<option>작성자</option>
								</select> 
								<input type="text" name="table_search" 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>
					<div class="card-body">
						<table class="table table-bordered">
							<thead class="table-dark">
								<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>
													<font class="badge badge-danger" style="font-size: 14px;">${notice.boWriter }</font>
												</td>
												<td>${notice.boDate }</td>
												<td>${notice.boHit }</td>
											</tr>
										</c:forEach>
									</c:otherwise>									
								</c:choose>
							</tbody>
						</table>
					</div>
					<div class="card-footer" align="right">
						<button type="button" class="btn btn-dark" id="newBtn">등록</button>
					</div>
					<div class="card-footer clearfix" id="pagingArea">
						${pagingVO.pagingHTML }
					</div>
				</div>
			</div>
		</div>
</section>
<script>
	$(function(){
		// 페이징을 처리할 때 사용할 Element
		// pagingArea div 안에 ul과 li로 구성된 페이징 정보가 존재
		// 그 안에는 a태그로 구성된 페이지 정보가 들어있다.
		// a태그 안에 들어있는 page 번호를 가져와서 페이징 처리를 진행
		var pagingArea = $("#pagingArea");
		var searchForm = $("#searchForm");
		var newBtn = $("#newBtn"); // 등록 버튼
		
		pagingArea.on("click", "a", function(event){
			event.preventDefault(); // a태그의 이벤트를 block
			var pageNo = $(this).data("page");
			searchForm.find("#page").val(pageNo);
			searchForm.submit();
		});
		
		// 등록 버튼 클릭 시, 게시판 등록 페이지로 이동
		newBtn.on("click", function(){
			location.href = "/notice/form.do";
		});
	});
</script>
insert into notice values (seq_notice.nextval, '제목1', '내용', 'a001', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목2', '내용', 'a001', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목3', '내용', 'a001', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목4', '내용', 'a001', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목5', '내용', 'a002', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목6', '내용', 'a002', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목7', '내용', 'a002', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목8', '내용', 'a002', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목9', '내용', 'a003', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목10', '내용', 'a003', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목11', '내용', 'a004', sysdate, 0);
insert into notice values (seq_notice.nextval, '제목12', '내용', 'a005', sysdate, 0);

commit;

select * from notice;

DECLARE
   v_user_id VARCHAR2(4) := 'a001';
BEGIN
   FOR i IN 1..50 LOOP
      INSERT INTO notice
      VALUES (
         seq_notice.nextval,
         '제목' || TO_CHAR(i),
         '내용' || TO_CHAR(i),
         v_user_id,
         SYSDATE,
         0
      );
   END LOOP;
   COMMIT;
END;
/

- http://localhost/notice/list.do


package kr.or.ddit.controller.crud.notice;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
public class NoticeInsertController {

	@RequestMapping(value = "/form.do", method = RequestMethod.GET)
	public String noticeInsertForm() {
		return "notice/form";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<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>
</section>

<section class="content">
    <div class="row">
        <div class="col-md-12">
            <div class="card card-dark">
                <div class="card-header">
                    <h3 class="card-title">공지사항 등록</h3>
                    <div class="card-tools">

                    </div>
                </div>
                <div class="card-body">
                    <div class="form-group">
                        <label for="inputName">제목을 입력해주세요</label>
                        <input type="text" id="inputName" class="form-control" placeholder="제목을 입력해주세요">
                    </div>
                    <div class="form-group">
                        <label for="inputDescription">내용을 입력해주세요</label>
                        <textarea id="inputDescription" class="form-control" rows="14"></textarea>
                    </div>
                    <div class="form-group">
                        <div class="custom-file">

                            <input type="file" class="custom-file-input" id="customFile" multiple="multiple">
                            <label class="custom-file-label" for="customFile">파일을 선택해주세요</label>
                        </div>
                    </div>
                </div>
                <div class="card-footer bg-white">
                    <ul class="mailbox-attachments d-flex align-items-stretch clearfix">
                        <li>
                            <span class="mailbox-attachment-icon"><i class="far fa-file-pdf"></i></span>

                            <div class="mailbox-attachment-info">
                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> Sep2014-report.pdf</a>
                                <span class="mailbox-attachment-size clearfix mt-1">
                                    <span>1,245 KB</span>
                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
                                </span>
                            </div>
                        </li>
                        <li>
                            <span class="mailbox-attachment-icon"><i class="far fa-file-word"></i></span>

                            <div class="mailbox-attachment-info">
                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> App Description.docx</a>
                                <span class="mailbox-attachment-size clearfix mt-1">
                                    <span>1,245 KB</span>
                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
                                </span>
                            </div>
                        </li>
                        <li>
                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo1.png" alt="Attachment"></span>

                            <div class="mailbox-attachment-info">
                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo1.png</a>
                                <span class="mailbox-attachment-size clearfix mt-1">
                                    <span>2.67 MB</span>
                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
                                </span>
                            </div>
                        </li>
                        <li>
                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo2.png" alt="Attachment"></span>

                            <div class="mailbox-attachment-info">
                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo2.png</a>
                                <span class="mailbox-attachment-size clearfix mt-1">
                                    <span>1.9 MB</span>
                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
                                </span>
                            </div>
                        </li>
                    </ul>
                </div>
                <div class="card-footer bg-white">
                    <div class="row">
                        <div class="col-12">
                            <input type="button" value="목록" class="btn btn-secondary float-right">
                            <input type="button" value="등록" class="btn btn-dark float-right">
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>

- http://localhost/notice/form.do


[mainTemplate.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!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 type="text/javascript" src="${pageContext.request.contextPath }/resources/ckeditor/ckeditor.js"></script>
</head>

[form.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<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>
</section>

<section class="content">
    <div class="row">
        <div class="col-md-12">
            <div class="card card-dark">
                <div class="card-header">
                    <h3 class="card-title">공지사항 등록</h3>
                    <div class="card-tools">

                    </div>
                </div>
                <form action="/notice/insert.do" method="post" id="noticeForm" name="noticeForm">
	                <div class="card-body">
	                    <div class="form-group">
	                        <label for="boTitle">제목을 입력해주세요</label>
	                        <input type="text" id="boTitle" name="boTitle" class="form-control" placeholder="제목을 입력해주세요">
	                    </div>
	                    <div class="form-group">
	                        <label for="boContent">내용을 입력해주세요</label>
	                        <textarea id="boContent" name="boContent" class="form-control" rows="14"></textarea>
	                    </div>
	                    <!-- <div class="form-group">
	                        <div class="custom-file">
	                            <input type="file" class="custom-file-input" id="customFile" multiple="multiple">
	                            <label class="custom-file-label" for="customFile">파일을 선택해주세요</label>
	                        </div>
	                    </div> -->
	                </div>
	                <div class="card-footer bg-white">
	                    <ul class="mailbox-attachments d-flex align-items-stretch clearfix">
	                        <li>
	                            <span class="mailbox-attachment-icon"><i class="far fa-file-pdf"></i></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> Sep2014-report.pdf</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1,245 KB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon"><i class="far fa-file-word"></i></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> App Description.docx</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1,245 KB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo1.png" alt="Attachment"></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo1.png</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>2.67 MB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo2.png" alt="Attachment"></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo2.png</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1.9 MB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                    </ul>
	                </div>
	                <div class="card-footer bg-white">
	                    <div class="row">
	                        <div class="col-12">
	                            <input type="button" id="insertBtn" value="등록" class="btn btn-secondary float-right">
	                            <input type="button" id="listBtn" value="목록" class="btn btn-dark float-right">
	                            <input type="button" id="cancelBtn" value="취소" class="btn btn-dark float-right">
	                        </div>
	                    </div>
	                </div>
                </form>
            </div>
        </div>
    </div>
</section>
<script>
	$(function(){
		CKEDITOR.replace("boContent");
		CKEDITOR.config.height = "500px"; // CKEDITOR 높이 설정
		
		var noticeForm = $("#noticeForm");	// 등록 폼 Element
		var insertBtn = $("#insertBtn");	// 등록 버튼 Element
		var listBtn = $("#listBtn");		// 목록 버튼 Element
		var cancelBtn = $("#cancelBtn");	// 취소 버튼 Element
		
		// 등록 버튼 클릭 시, 등록 진행
		insertBtn.on("click", function(){
			var title = $("#boTitle").val();
			
			// 일반적인 textarea일 때 가져오는 방법
			// var content = $("#boContent").val();
			
			// CKEDITOR를 이용한 내용 데이터 가져오는 방법
			var content = CKEDITOR.instances.boContent.getData();
			
			if(!title) {
				alert("제목을 입력해 주세요!");
				$("#boTitle").focus();
				return false;
			}
			
			if(!content) {
				alert("제목을 입력해 주세요!");
				$("#boContent").focus();
				return false;
			}
			
			noticeForm.submit();
		});
		
		// 목록 버튼 클릭 시, 게시판 목록 화면으로 이동
		listBtn.on("click", function(){
        	
		});
		
		// 취소 버튼 클릭 시, 상세보기 화면으로 이동
		cancelBtn.on("click", function(){
			
		});
	});
</script>

- http://localhost/notice/form.do


[pom.xml]

		<!-- StringUtils를 이용한 데이터 검증 라이브러리 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.6</version>
		</dependency>
		<!-- StringUtils를 이용한 데이터 검증 라이브러리 -->

package kr.or.ddit;

public enum ServiceResult {

	OK, FAILED, EXIST, NOTEXIST
	
}
package kr.or.ddit.controller.crud.notice;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
public class NoticeInsertController {
	
	@Inject
	private INoticeService noticeService;

	@RequestMapping(value = "/form.do", method = RequestMethod.GET)
	public String noticeInsertForm() {
		return "notice/form";
	}
	
	@RequestMapping(value = "/insert.do", method = RequestMethod.POST)
	public String noticeInsert(NoticeVO noticeVO, Model model) {
		String goPage = "";
		
		// 넘겨받은 데이터 검증 후, 에러가 발생한 데이터에 대한 에러정보를 담을 공간
		Map<String, String> errors = new HashMap<String, String>();
		
		// 라이브러리 추가해야 함.
		// 제목 데이터가 누락되었을 때 에러 정보 저장
		if(StringUtils.isBlank(noticeVO.getBoTitle())) {
			errors.put("boTitle", "제목을 입력해주세요.");
		}
		// 내용 데이터가 누락되었을 때 에러 정보 저장
		if(StringUtils.isBlank(noticeVO.getBoContent())) {
			errors.put("boContent", "내용을 입력해주세요.");
		}
		// 기본 데이터의 누락정보에 따른 에러정보 갯수로 분기 처리
		if(errors.size() > 0) { // 에러 갯수가 0보다 클 때(에러가 존재)
			model.addAttribute("errors", errors);
			model.addAttribute("noticeVO", noticeVO);
			goPage = "notice/form";
		}else { // 에러가 없을 때
			// 로그인 처리를 하지 않고 게시글을 작성하므로 작성자를 하드코딩한다.
			noticeVO.setBoWriter("a001");
			ServiceResult result = noticeService.insertNotice(noticeVO);
		}
		
		return goPage;
	}
	
}
package kr.or.ddit.service;

import java.util.List;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeService {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
	public ServiceResult insertNotice(NoticeVO noticeVO);

}
package kr.or.ddit.service.impl;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.mapper.INoticeMapper;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

@Service
public class NoticeServiceImpl implements INoticeService {

	@Inject
	private INoticeMapper noticeMapper;
	
	@Override
	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeCount(pagingVO);
	}

	@Override
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeList(pagingVO);
	}

	@Override
	public ServiceResult insertNotice(NoticeVO noticeVO) {
		ServiceResult result = null;
		
		int status = noticeMapper.insertNotice(noticeVO);
		if(status > 0) {
			result = ServiceResult.OK;
		}else {
			result = ServiceResult.FAILED;
		}
		
		return result;
	}

}
package kr.or.ddit.mapper;

import java.util.List;

import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeMapper {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
	public int insertNotice(NoticeVO noticeVO);

}
<?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.INoticeMapper">
	
	<select id="selectNoticeCount" parameterType="pagingVO" resultType="int">
		select count(bo_no) from notice
	</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, bo_date, bo_hit 
				from notice 
				where 1=1 
				order by bo_no desc
			) a
		) b
		<![CDATA[
			where b.rnum >= #{startRow} and b.rnum <= #{endRow}
		]]>
	</select>
	
	<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>
	
</mapper>
package kr.or.ddit.controller.crud.notice;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
public class NoticeInsertController {
	
	@Inject
	private INoticeService noticeService;

	@RequestMapping(value = "/form.do", method = RequestMethod.GET)
	public String noticeInsertForm() {
		return "notice/form";
	}
	
	@RequestMapping(value = "/insert.do", method = RequestMethod.POST)
	public String noticeInsert(
			NoticeVO noticeVO, 
			Model model, 
			RedirectAttributes ra
			) {
		String goPage = "";
		
		// 넘겨받은 데이터 검증 후, 에러가 발생한 데이터에 대한 에러정보를 담을 공간
		Map<String, String> errors = new HashMap<String, String>();
		
		// 라이브러리 추가해야 함.
		// 제목 데이터가 누락되었을 때 에러 정보 저장
		if(StringUtils.isBlank(noticeVO.getBoTitle())) {
			errors.put("boTitle", "제목을 입력해주세요.");
		}
		// 내용 데이터가 누락되었을 때 에러 정보 저장
		if(StringUtils.isBlank(noticeVO.getBoContent())) {
			errors.put("boContent", "내용을 입력해주세요.");
		}
		// 기본 데이터의 누락정보에 따른 에러정보 갯수로 분기 처리
		if(errors.size() > 0) { // 에러 갯수가 0보다 클 때(에러가 존재)
			model.addAttribute("errors", errors);
			model.addAttribute("noticeVO", noticeVO);
			goPage = "notice/form";
		}else { // 에러가 없을 때
			// 로그인 처리를 하지 않고 게시글을 작성하므로 작성자를 하드코딩한다.
			noticeVO.setBoWriter("a001");
			ServiceResult result = noticeService.insertNotice(noticeVO);
			if(result.equals(ServiceResult.OK)) {
				goPage = "redirect:/notice/detail.do?boNo=" + noticeVO.getBoNo();
				ra.addFlashAttribute("message", "게시글 등록이 완료되었습니다.");
			}else { // 등록 실패
				model.addAttribute("noticeVO", noticeVO);
				model.addAttribute("message", "서버 에러, 다시 시도해 주세요!");
				goPage = "notice/form";
			}
		}
		
		return goPage;
	}
	
}
package kr.or.ddit.controller.crud.notice;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
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
			) {
		PaginationInfoVO<NoticeVO> pagingVO = new PaginationInfoVO<NoticeVO>();
		
		// 현재 페이지 전달 후, start/endRow와 start/endPage 설정
		pagingVO.setCurrentPage(currentPage);
		
		int totalRecord = noticeService.selectNoticeCount(pagingVO); // 총 게시글 수 가져오기
		pagingVO.setTotalRecord(totalRecord);
		List<NoticeVO> dataList = noticeService.selectNoticeList(pagingVO);
		pagingVO.setDataList(dataList);
		
		model.addAttribute("pagingVO", pagingVO);
		
		return "notice/list";
	}
	
	@RequestMapping(value = "/detail.do", method = RequestMethod.GET)
	public String noticeDetail(int boNo, Model model) {
		return "notice/detail";
	}
	
}

[mainTemplate.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 type="text/javascript" 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>

[detail.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<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>
</section>

<section class="content">
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-12">
                <div class="card card-dark">
                    <div class="card-header">
                        <h3 class="card-title">제목입니다!!</h3>
                        <div class="card-tools">
                            	관리자 2022-12-12 12:11 1456
                        </div>
                    </div>
                    <form id="quickForm" novalidate="novalidate">
                        <div class="card-body">내용입니다.</div>
                        <div class="card-footer bg-white">
                            <ul class="mailbox-attachments d-flex align-items-stretch clearfix">
                                <li>
                                    <span class="mailbox-attachment-icon">
                                        <i class="far fa-file-pdf"></i>
                                    </span>
                                    <div class="mailbox-attachment-info">
                                        <a href="#" class="mailbox-attachment-name">
                                            <i class="fas fa-paperclip"></i>
                                           	 파일명
                                        </a>
                                        <span class="mailbox-attachment-size clearfix mt-1">
                                            <span>파일 Fancy사이즈 KB</span>
                                            <a href="다운로드 url">
                                                <span class="btn btn-default btn-sm float-right">
                                                    <i class="fas fa-download"></i>
                                                </span>
                                            </a>
                                        </span>
                                    </div>
                                </li>
                            </ul>
                        </div>
                        <div class="card-footer">
                            <button type="submit" class="btn btn-secondary">목록</button>
                            <button type="submit" class="btn btn-dark">수정</button>
                            <button type="submit" class="btn btn-danger">삭제</button>
                        </div>
                    </form>
                </div>
            </div>
            <div class="col-md-6"></div>
        </div>
    </div>
</section>

- http://localhost/notice/form.do


package kr.or.ddit.controller.crud.notice;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
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
			) {
		PaginationInfoVO<NoticeVO> pagingVO = new PaginationInfoVO<NoticeVO>();
		
		// 현재 페이지 전달 후, start/endRow와 start/endPage 설정
		pagingVO.setCurrentPage(currentPage);
		
		int totalRecord = noticeService.selectNoticeCount(pagingVO); // 총 게시글 수 가져오기
		pagingVO.setTotalRecord(totalRecord);
		List<NoticeVO> dataList = noticeService.selectNoticeList(pagingVO);
		pagingVO.setDataList(dataList);
		
		model.addAttribute("pagingVO", pagingVO);
		
		return "notice/list";
	}
	
	@RequestMapping(value = "/detail.do", method = RequestMethod.GET)
	public String noticeDetail(int boNo, Model model) {
		NoticeVO noticeVO = noticeService.selectNotice(boNo);
		model.addAttribute("notice", noticeVO);
		return "notice/detail";
	}
	
}
package kr.or.ddit.service;

import java.util.List;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeService {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
	public ServiceResult insertNotice(NoticeVO noticeVO);
	public NoticeVO selectNotice(int boNo);

}
package kr.or.ddit.service.impl;

import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import kr.or.ddit.ServiceResult;
import kr.or.ddit.mapper.INoticeMapper;
import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

@Service
public class NoticeServiceImpl implements INoticeService {

	@Inject
	private INoticeMapper noticeMapper;
	
	@Override
	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeCount(pagingVO);
	}

	@Override
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO) {
		return noticeMapper.selectNoticeList(pagingVO);
	}

	@Override
	public ServiceResult insertNotice(NoticeVO noticeVO) {
		ServiceResult result = null;
		
		int status = noticeMapper.insertNotice(noticeVO);
		if(status > 0) {
			result = ServiceResult.OK;
		}else {
			result = ServiceResult.FAILED;
		}
		
		return result;
	}

	@Override
	public NoticeVO selectNotice(int boNo) {
		noticeMapper.incrementHit(boNo); // 게시글 조회수 증가
		return noticeMapper.selectNotice(boNo); // 게시글 번호에 해당하는 게시글 정보 가져오기
	}

}
package kr.or.ddit.mapper;

import java.util.List;

import kr.or.ddit.vo.crud.NoticeVO;
import kr.or.ddit.vo.crud.PaginationInfoVO;

public interface INoticeMapper {

	public int selectNoticeCount(PaginationInfoVO<NoticeVO> pagingVO);
	public List<NoticeVO> selectNoticeList(PaginationInfoVO<NoticeVO> pagingVO);
	public int insertNotice(NoticeVO noticeVO);
	public void incrementHit(int boNo);
	public NoticeVO selectNotice(int boNo);

}
<?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.INoticeMapper">
	
	<select id="selectNoticeCount" parameterType="pagingVO" resultType="int">
		select count(bo_no) from notice
	</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, bo_date, bo_hit 
				from notice 
				where 1=1 
				order by bo_no desc
			) a
		) b
		<![CDATA[
			where b.rnum >= #{startRow} and b.rnum <= #{endRow}
		]]>
	</select>
	
	<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, bo_date, bo_hit
		from notice 
		where bo_no = #{boNo}
	</select>
	
</mapper>

[detail.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<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>
</section>

<section class="content">
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-12">
                <div class="card card-dark">
                    <div class="card-header">
                        <h3 class="card-title">${notice.boTitle }</h3>
                        <div class="card-tools">
                         	${notice.boWriter } ${notice.boDate } ${notice.boHit }
                        </div>
                    </div>
                    <form id="quickForm" novalidate="novalidate">
                        <div class="card-body">${notice.boContent }</div>
                        <div class="card-footer bg-white">
                            <ul class="mailbox-attachments d-flex align-items-stretch clearfix">
                                <li>
                                    <span class="mailbox-attachment-icon">
                                        <i class="far fa-file-pdf"></i>
                                    </span>
                                    <div class="mailbox-attachment-info">
                                        <a href="#" class="mailbox-attachment-name">
                                            <i class="fas fa-paperclip"></i>
                                           	 파일명
                                        </a>
                                        <span class="mailbox-attachment-size clearfix mt-1">
                                            <span>파일 Fancy사이즈 KB</span>
                                            <a href="다운로드 url">
                                                <span class="btn btn-default btn-sm float-right">
                                                    <i class="fas fa-download"></i>
                                                </span>
                                            </a>
                                        </span>
                                    </div>
                                </li>
                            </ul>
                        </div>
                        <div class="card-footer">
                            <button type="button" id="listBtn" class="btn btn-secondary">목록</button>
                            <button type="button" id="updateBtn" class="btn btn-dark">수정</button>
                            <button type="button" id="deleteBtn" class="btn btn-danger">삭제</button>
                        </div>
                    </form>
                    <form action="/notice/delete.do" method="post" id="delForm">
                    	<input type="hidden" name="boNo" value="${notice.boNo }" />
                    </form>
                </div>
            </div>
            <div class="col-md-6"></div>
        </div>
    </div>
</section>
<script>
	$(function(){
		var delForm = $("#delForm"); 		// 수정 및 삭제를 처리할 Form Element
		var listBtn = $("#listBtn");		// 목록 버튼 Element
		var updateBtn = $("#updateBtn");	// 수정 버튼 Element
		var deleteBtn = $("#deleteBtn");	// 삭제 버튼 Element
		
		// 목록 버튼 클릭 시, 목록 페이지로 이동
		listBtn.on("click", function(){
			location.href = "/notice/list.do";
		});
		
		// 수정 버튼 클릭 시, 수정 페이지 이동
		updateBtn.on("click", function(){
			delForm.attr("action", "/notice/update.do");
			delForm.attr("method", "get");
			delForm.submit();
		});
		
		// 삭제 버튼 클릭 시, confirm 메시지 출력 후 삭제
		deleteBtn.on("click", function(){
			if(confirm("정말로 삭제하시겠습니까?")) {
				delForm.submit();
			}
		});
	});
</script>

페이지 새로 고침 시 조회수가 증가한다.


package kr.or.ddit.controller.crud.notice;

import javax.inject.Inject;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import kr.or.ddit.service.INoticeService;
import kr.or.ddit.vo.crud.NoticeVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
@RequestMapping("/notice")
public class NoticeModifyController {

	@Inject
	private INoticeService noticeService;
	
	@RequestMapping(value = "/update.do", method = RequestMethod.GET)
	public String noticeUpdateForm(int boNo, Model model) {
		NoticeVO noticeVO = noticeService.selectNotice(boNo);
		model.addAttribute("notice", noticeVO);
		model.addAttribute("status", "u"); // '수정입니다' flag
		return "notice/form";
	}
	
}

[form.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<section class="content-header">

	<c:set value="등록" var="name" />
	<c:if test="${status eq 'u' }">
		<c:set value="수정" var="name" />
	</c:if>
	
    <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>
</section>

<section class="content">
    <div class="row">
        <div class="col-md-12">
            <div class="card card-dark">
                <div class="card-header">
                    <h3 class="card-title">공지사항 ${name }</h3>
                    <div class="card-tools"></div>
                </div>
                <form action="/notice/insert.do" method="post" id="noticeForm" name="noticeForm">
                
                	<c:if test="${status eq 'u' }">
                		<input type="hidden" name="boNo" value="${notice.boNo }" />
                	</c:if>
                	
	                <div class="card-body">
	                    <div class="form-group">
	                        <label for="boTitle">제목을 입력해주세요</label>
	                        <input type="text" id="boTitle" name="boTitle" class="form-control" placeholder="제목을 입력해주세요" value="${notice.boTitle }">
	                    </div>
	                    <div class="form-group">
	                        <label for="boContent">내용을 입력해주세요</label>
	                        <textarea id="boContent" name="boContent" class="form-control" rows="14">${notice.boContent }</textarea>
	                    </div>
	                    <!-- <div class="form-group">
	                        <div class="custom-file">
	                            <input type="file" class="custom-file-input" id="customFile" multiple="multiple">
	                            <label class="custom-file-label" for="customFile">파일을 선택해주세요</label>
	                        </div>
	                    </div> -->
	                </div>
	                <div class="card-footer bg-white">
	                    <ul class="mailbox-attachments d-flex align-items-stretch clearfix">
	                        <li>
	                            <span class="mailbox-attachment-icon"><i class="far fa-file-pdf"></i></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> Sep2014-report.pdf</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1,245 KB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon"><i class="far fa-file-word"></i></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-paperclip"></i> App Description.docx</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1,245 KB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo1.png" alt="Attachment"></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo1.png</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>2.67 MB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                        <li>
	                            <span class="mailbox-attachment-icon has-img"><img src="../../dist/img/photo2.png" alt="Attachment"></span>
	
	                            <div class="mailbox-attachment-info">
	                                <a href="#" class="mailbox-attachment-name"><i class="fas fa-camera"></i> photo2.png</a>
	                                <span class="mailbox-attachment-size clearfix mt-1">
	                                    <span>1.9 MB</span>
	                                    <a href="#" class="btn btn-default btn-sm float-right"><i class="fas fa-times"></i></a>
	                                </span>
	                            </div>
	                        </li>
	                    </ul>
	                </div>
	                <div class="card-footer bg-white">
	                    <div class="row">
	                        <div class="col-12">
	                            <input type="button" id="insertBtn" value="${name }" class="btn btn-secondary float-right">
	                            <c:if test="${status ne 'u' }">
		                            <input type="button" id="listBtn" value="목록" class="btn btn-dark float-right">
	                            </c:if>
	                            <c:if test="${status eq 'u' }">
		                            <input type="button" id="cancelBtn" value="취소" class="btn btn-dark float-right">
	                            </c:if>
	                        </div>
	                    </div>
	                </div>
                </form>
            </div>
        </div>
    </div>
</section>
<script>
	$(function(){
		CKEDITOR.replace("boContent");
		CKEDITOR.config.height = "500px"; // CKEDITOR 높이 설정
		
		var noticeForm = $("#noticeForm");	// 등록 폼 Element
		var insertBtn = $("#insertBtn");	// 등록 버튼 Element
		var listBtn = $("#listBtn");		// 목록 버튼 Element
		var cancelBtn = $("#cancelBtn");	// 취소 버튼 Element
		
		// 등록 버튼 클릭 시, 등록 진행
		insertBtn.on("click", function(){
			var title = $("#boTitle").val();
			
			// 일반적인 textarea일 때 가져오는 방법
			// var content = $("#boContent").val();
			
			// CKEDITOR를 이용한 내용 데이터 가져오는 방법
			var content = CKEDITOR.instances.boContent.getData();
			
			if(!title) {
				alert("제목을 입력해 주세요!");
				$("#boTitle").focus();
				return false;
			}
			
			if(!content) {
				alert("제목을 입력해 주세요!");
				$("#boContent").focus();
				return false;
			}
			
			if($(this).val() == "수정") {
				noticeForm.attr("action", "/notice/update.do");
			}
			
			noticeForm.submit();
		});
		
		// 목록 버튼 클릭 시, 게시판 목록 화면으로 이동
		listBtn.on("click", function(){
			location.href = "/notice/list.do";
		});
		
		// 취소 버튼 클릭 시, 상세보기 화면으로 이동
		cancelBtn.on("click", function(){
			location.href = "/notice/detail.do?boNo=${notice.boNo }";
		});
	});
</script>

- http://localhost/notice/update.do?boNo=66


 

'대덕인재개발원 > 대덕인재개발원_웹기반 애플리케이션' 카테고리의 다른 글

231130_SPRING 2 (7-3)  (0) 2023.11.30
231130_SPRING 2 (7-2)  (0) 2023.11.30
231129_SPRING 2 (6-3)  (0) 2023.11.29
231129_SPRING 2 (6-2)  (0) 2023.11.29
231129_SPRING 2 (6-1)  (0) 2023.11.29