Servlet/JSP의 일반 게시판(Board)를 만들어 보자
일반 게시판
✔️ 게시판 기본 설정, 게시판 전체 조회, 게시판 상세 조회, 수정, 삭제
더미 데이터 활용하는 법
(= 다량의 insert문 한 번에 만드는 법)
- 하단의 엑셀 파일 다운로드
- sql Deveolper에서 내가 삽입하기를 원하는 테이블 선택 후 데이터 임포트 선택
- 경로 선택하여 열기 헤드 체크 후 다음
- 임포트 행 제한 체크하지 말고 다음 (엑셀 파일에 더미 데이터 행 100개 이상임)
- 열 선택 확인 후 다음
- 열 정의 확인 후 다음
- 완료
- 삽입 실패! 오류 무시하고 계속하시겠습니까? 예
- 가장 하단에 COMMIT; 구문 작성
- 실행할 계정 접속 후 스크립트 실행
![](https://blog.kakaocdn.net/dn/wvDhC/btrOD5sn9v0/G1Lew9Z74V7LUPMYTDNQDK/img.png)
![](https://blog.kakaocdn.net/dn/JFSp6/btrO34Z2285/U4LUTYymuFVvcKaobCxapk/img.png)
![](https://blog.kakaocdn.net/dn/ci4Vge/btrO2WobGEa/yx3MDKqK5d5XbQlVQ92Mck/img.png)
![](https://blog.kakaocdn.net/dn/WuwpJ/btrOQx9ei1N/MKgyJ1epK0VneKCP5565Nk/img.png)
![](https://blog.kakaocdn.net/dn/ccmSCB/btrO3TR14gH/BjBGKAy8HEypv2kowUmJe1/img.png)
![](https://blog.kakaocdn.net/dn/cVo6ZY/btrO4tZmYy4/HuceVhwy47KoMZMVwl1ko1/img.png)
![](https://blog.kakaocdn.net/dn/bLwXtf/btrO2oZtYb9/3BIa6oy9YUH7RYlty3jwK1/img.png)
![](https://blog.kakaocdn.net/dn/QY1nS/btrO3UXIxje/1XR0JCiP4bdEtpk4B5kWyk/img.png)
![](https://blog.kakaocdn.net/dn/LtL93/btrO3FNgrBU/EW2kxTy48rfOwVQ1Ckgo7K/img.png)
*게시판 기본 설정
BoardDao 클래스 생성
package com.kh.board.model.dao;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class BoardDao {
private Properties prop = new Properties();
public BoardDao() {
String fileName = BoardDao.class.getResource("/sql/board-mapper.xml").getPath();
try {
prop.loadFromXML(new FileInputStream(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
BoardService 클래스 생성
👉🏻 일단 생성만!
Board 클래스 생성
package com.kh.board.model.vo;
import java.sql.Date;
public class Board {
// 필드부
private int boardNo; // BOARD_NO NUMBER PRIMARY KEY,
private int boardType; // BOARD_TYPE NUMBER, -- 일반 게시판(1)이냐 사진 게시판(2)이냐
private String category; // CATEGORY_NO NUMBER,
// 10 같은 카테고리 번호, "공통" 같은 카테고리명을 모두 담을 수 있어야 함 => String 타입으로
private String boardTitle; // BOARD_TITLE VARCHAR2(100) NOT NULL,
private String boardContent; // BOARD_CONTENT VARCHAR2(4000) NOT NULL,
private String boardWriter; // BOARD_WRITER NUMBER NOT NULL,
private int count; // COUNT NUMBER DEFAULT 0,
private Date createDate; // CREATE_DATE DATE DEFAULT SYSDATE NOT NULL,
private String status; // STATUS VARCHAR2(1) DEFAULT 'Y' CHECK (STATUS IN('Y', 'N')),
// 생성자부
public Board() { }
public Board(int boardNo, int boardType, String category, String boardTitle, String boardContent,
String boardWriter, int count, Date createDate, String status) {
super();
this.boardNo = boardNo;
this.boardType = boardType;
this.category = category;
this.boardTitle = boardTitle;
this.boardContent = boardContent;
this.boardWriter = boardWriter;
this.count = count;
this.createDate = createDate;
this.status = status;
}
// 메소드부
public int getBoardNo() {
return boardNo;
}
public void setBoardNo(int boardNo) {
this.boardNo = boardNo;
}
public int getBoardType() {
return boardType;
}
public void setBoardType(int boardType) {
this.boardType = boardType;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getBoardTitle() {
return boardTitle;
}
public void setBoardTitle(String boardTitle) {
this.boardTitle = boardTitle;
}
public String getBoardContent() {
return boardContent;
}
public void setBoardContent(String boardContent) {
this.boardContent = boardContent;
}
public String getBoardWriter() {
return boardWriter;
}
public void setBoardWriter(String boardWriter) {
this.boardWriter = boardWriter;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Board [boardNo=" + boardNo + ", boardType=" + boardType + ", category=" + category + ", boardTitle="
+ boardTitle + ", boardContent=" + boardContent + ", boardWriter=" + boardWriter + ", count=" + count
+ ", createDate=" + createDate + ", status=" + status + "]";
}
}
*일반 게시판 전체 조회
boardListView.jsp 생성 (하드코딩 VER.)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.outer {
background-color: black;
color : white;
width : 1000px;
height : 550px;
margin : auto;
margin-top : 50px
}
.list-area {
border : 1px solid white;
text-align: center;
}
.list-area>tbody>tr:hover {
background-color: grey;
cursor : pointer;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<h2 align="center">일반게시판</h2>
<br>
<!-- 로그인한 회원만 보여지는 글 작성 버튼 -->
<div style="width:850px;" align="right">
<a href="" class="btn btn-secondary">글작성</a>
</div>
<br>
<!-- 조회할 게시물이 보여질 자리 -->
<table align="center" class="list-area">
<thead>
<tr>
<!-- th[width=]*6 + Enter -->
<th width="70">글번호</th>
<th width="70">카테고리</th>
<th width="300">제목</th>
<th width="100">작성자</th>
<th width="50">조회수</th>
<th width="100">작성일</th>
</tr>
</thead>
<tbody>
<tr>
<td>10</td>
<td>게임</td>
<td>게시글 제목이다</td>
<td>user02</td>
<td>10</td>
<td>2022-05-01</td>
</tr>
<tr>
<td>10</td>
<td>게임</td>
<td>게시글 제목이다</td>
<td>user02</td>
<td>10</td>
<td>2022-05-01</td>
</tr>
<tr>
<td>10</td>
<td>게임</td>
<td>게시글 제목이다</td>
<td>user02</td>
<td>10</td>
<td>2022-05-01</td>
</tr>
<tr>
<td>10</td>
<td>게임</td>
<td>게시글 제목이다</td>
<td>user02</td>
<td>10</td>
<td>2022-05-01</td>
</tr>
</tbody>
</table>
<br><br>
<!-- 페이징바 -->
<div align="center" class="paging-area">
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>7</button>
<button>8</button>
<button>9</button>
<button>10</button>
</div>
</div>
</body>
</html>
menubar.jsp
👉🏻 링크 포워딩
<div class="nav-area" align="center">
<!-- (div.menu>a)*4 + Enter -->
<div class="menu"><a href="<%= contextPath %>">HOME</a></div>
<div class="menu"><a href="<%= contextPath %>/list.no">공지사항</a></div>
<div class="menu"><a href="<%= contextPath %>/list.bo?currentPage=1">일반게시판</a></div>
<div class="menu"><a href="">사진게시판</a></div>
</div>
BoardListController 서블릿 생성
👉🏻 url mapping: list.bo
package com.kh.board.controller;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Board;
import com.kh.common.model.vo.PageInfo;
/**
* Servlet implementation class BoardListController
*/
@WebServlet("/list.bo")
public class BoardListController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardListController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// -- 페이징 처리 방법 --
// 어떤 페이지를 클릭했느냐에 따라 페이징바의 숫자, 게시글 번호 등이 모두 달라짐
// 총 7개의 변수 필요 => 기본적으로 알아내야 하는 변수 4개, 계산해서 구해야 하는 변수 3개
int listCount; // 현재 총 게시글 개수
int currentPage; // 현재 요청한 페이지(즉, 사용자가 요청한 페이지 수)
int pageLimit; // 페이지 하단에 보여질 페이징바의 페이지 최대 개수
int boardLimit; // 한 페이지에 보여질 게시글의 최대 개수(몇 개 단위씩 리스트가 보여질 건지)
int maxPage; // 가장 마지막 페이지가 몇 번 페이지인지(총 페이지 수)
int startPage; // 페이지 하단에 보여질 페이징바의 시작 수
int endPage; // 페이지 하단에 보여질 페이징바의 끝 수
// *listCount: 총 게시글 개수
listCount = new BoardService().selectListCount();
// System.out.println(listCount);
// *currentPage: 현재 페이지 (즉, 사용자가 요청한 페이지)
// => 쿼리스트링으로 대놓고 넘김!!
currentPage = Integer.parseInt(request.getParameter("currentPage"));
// * pageLimit: 페이지 하단에 보여질 페이징 바의 페이지 최대 개수
// (페이지 목록들을 몇 개 단위씩 보여 줄 건지)
pageLimit = 10;
// * boardLimit: 한 페이지에 보여질 게시글의 최대 개수
// (게시글을 몇 개 단위씩 보여 줄 건지)
boardLimit = 10;
// * maxPage: 가장 마지막 페이지가 몇 번 페이지인지 => 총 페이지 수를 나타냄
/*
* listCount, boardLimit에 영향을 받음
*
* - 공식 구하기
* 단, boardLimit가 10이라는 가정 하에 규칙을 구해 보자!
*
* listCount(총 개수) boardLimit maxPage
* 100.0 / 10 => 몫 10.0 10 번 페이지
* 101.0 / 10 => 몫 10.1 11 번 페이지
* 105.0 / 10 => 몫 10.5 11 번 페이지
* 109.0 / 10 => 몫 10.9 11 번 페이지
* 110.0 / 10 => 몫 11.0 11 번 페이지
* 111.0 / 10 => 몫 11.1 12 번 페이지
*
* => 나눗셈 연산한 결과를 "올림" 처리한다면?
*
* 1) listCount를 double로 강제 형변환
* 2) listCount / boardLimit
* 3) 결과값에 올림 처리 => Math.ceil();
* 4) 결과값을 int형으로 강제 형변환
*/
maxPage = (int)Math.ceil((double)listCount / boardLimit);
// * startPage : 페이지 하단에 보일 페이징바의 시작 수
/*
* pageLimit, currentPage에 영향을 받음
*
* - 공식 구하기
* 단, pageLimit가 10이라는 가정 하에 규칙을 구해 보자!
*
* pageLimit가 10일 경우
* startPage: 1, 11, 21, 31, 41, ... => n * 10 + 1
*
* pageLimit가 5일 경우
* startPage: 1, 6, 11, 16, 21, 26, ... => n * 5 + 1
*
* => 즉, n * pageLimit + 1
*
* currentPage startPage
* 1 1 => 0 * pageLimit + 1 => 1
* 5 1 => 0 * pageLimit + 1 => 1
* 10 1 => 0 * pageLimit + 1 => 1
* 11 11 => 1 * pageLimit + 1 => 11
* 15 11 => 1 * pageLimit + 1 => 11
* 20 11 => 1 * pageLimit + 1 => 11
*
* => 1 ~ 10 : n = 0 => 0 ~ 9 / pageLimit = 0
* 11 ~ 20 : n = 1 => 10 ~ 19 / pageLimit = 1
* 21 ~ 30 : n = 2 => 20 ~ 29 / pageLimit = 2
* 31 ~ 40 : n = 3 => 30 ~ 39 / pageLimit = 3
* ...
* n = (currentPage - 1) / pageLimit
* startPage = n * pageLimit + 1
* = (currentPage - 1) / pageLimit * pageLimit + 1
*/
startPage = (currentPage - 1) / pageLimit * pageLimit + 1;
// * endPage : 페이지 하단에 보여질 페이징바의 끝 수
/*
* startPage, pageLimit에 영향을 받음 (단, maxPage도 영향을 주긴 함)
*
* - 공식 구하기
* 단, pageLimit가 10이라는 가정 하에 규칙을 구해 보자!
*
* startPage: 1 => endPage: 10
* startPage: 11 => endPage: 20
* startPage: 21 => endPage: 30
*
* endPage = startPage + pageLimit - 1
*/
endPage = startPage + pageLimit - 1;
// 근데 maxPage가 고작 13까지밖에 안 된다면?
// => endPage를 maxPage로 변경
if(endPage > maxPage) {
endPage = maxPage;
}
// 페이지 정보들 (7개의 변수)을 하나의 공간에 담아서 보내자
// => 페이지 정보들을 담을 VO 클래스를 하나 더 만들 것!
// (공지사항이나 사진 게시판에서도 쓰일 수 있으므로 common 패키지에 만들 것!)
// 1. PageInfo 객체로 가공 (조회할 때, 페이징바 만들 때 필요)
PageInfo pi = new PageInfo(listCount, currentPage, pageLimit, boardLimit
, maxPage, startPage, endPage);
// pi를 넘기면서 서비스로 요청
// 2. list에는 해당 페이지에서 보여져야 할 게시글들의 목록들(목록 만들 때 필요)
ArrayList<Board> list = new BoardService().selectList(pi);
request.setAttribute("pi", pi);
request.setAttribute("list", list);
request.getRequestDispatcher("views/board/boardListView.jsp").forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
BoardService
package com.kh.board.model.service;
import static com.kh.common.JDBCTemplate.*;
import java.sql.Connection;
import com.kh.board.model.dao.BoardDao;
public class BoardService {
public int selectListCount() {
Connection conn = getConnection();
int listCount = new BoardDao().selectListCount(conn);
close(conn);
return listCount;
}
public ArrayList<Board> selectList(PageInfo pi) {
Connection conn = getConnection();
ArrayList<Board> list = new BoardDao().selectList(conn, pi);
close(conn);
return list;
}
}
BoardDao
package com.kh.board.model.dao;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import static com.kh.common.JDBCTemplate.*;
public class BoardDao {
private Properties prop = new Properties();
public BoardDao() {
String fileName = BoardDao.class.getResource("/sql/board/board-mapper.xml").getPath();
try {
prop.loadFromXML(new FileInputStream(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
public int selectListCount(Connection conn) {
// SELECT문 => ResultSet 객체 (그룹함수를 써서 한 행 조회)
int listCount = 0;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectListCount");
try {
pstmt = conn.prepareStatement(sql);
rset = pstmt.executeQuery();
// while(rset.next()) 로 뽑아왔지만 지금은 107이라는 1개의 행으로 조회되므로 if문 씀
if(rset.next()) {
listCount = rset.getInt("COUNT");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return listCount;
}
public ArrayList<Board> selectList(Connection conn, PageInfo pi) {
// SELECT문 => ResultSet 객체 (여러 행 조회)
ArrayList<Board> list = new ArrayList<>();
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectList");
try {
pstmt = conn.prepareStatement(sql);
/*
* boardLimit가 10이라는 가정 하에
* currentPage = 1 => 시작값 1, 끝값 10
* currentPage = 2 => 시작값 11, 끝값 20
* currentPage = 3 => 시작값 21, 끝값 30
*
* 시작값 = (currentPage - 1) * boardLimit + 1
* 끝값 = 시작값 + boardLimit - 1
*/
int startRow = (pi.getCurrentPage() - 1) * pi.getBoardLimit() + 1;
int endRow = startRow + pi.getBoardLimit() - 1;
pstmt.setInt(1, startRow);
pstmt.setInt(2, endRow);
rset = pstmt.executeQuery();
while(rset.next()) {
list.add(new Board(rset.getInt("BOARD_NO")
, rset.getString("CATEGORY_NAME")
, rset.getString("BOARD_TITLE")
, rset.getString("USER_ID")
, rset.getInt("COUNT")
, rset.getDate("CREATE_DATE")));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return list;
}
}
Board-mapper.xml
<entry key="selectListCount">
SELECT COUNT(*) COUNT
FROM BOARD
WHERE STATUS = 'Y'
AND BOARD_TYPE = 1
</entry>
<entry key="selectList">
SELECT *
FROM (SELECT ROWNUM RNUM, A.*
FROM (SELECT BOARD_NO
, CATEGORY_NAME
, BOARD_TITLE
, USER_ID
, COUNT
, CREATE_DATE
FROM BOARD B
JOIN CATEGORY USING (CATEGORY_NO)
JOIN MEMBER ON (BOARD_WRITER = USER_NO)
WHERE BOARD_TYPE = 1
AND B.STATUS = 'Y'
ORDER BY BOARD_NO DESC) A)
WHERE RNUM BETWEEN ? AND ?
</entry>
PageInfo 클래스 생성
👉🏻 처음 Controller에서 정의했던 7가지의 변수를 정의할 곳이며 사진 게시판, 공지사항 등에도 쓰일 수 있음
package com.kh.common.model.vo;
public class PageInfo {
// 필드부
private int listCount; // 현재 총 게시글 개수
private int currentPage; // 현재 요청한 페이지
private int pageLimit; // 페이지 하단에 보여질 페이징 바의 최대 개수
private int boardLimit; // 한 페이지에 보여질 게시글의 최대 개수
private int maxPage; // 가장 마지막 페이지 수
private int startPage; // 페이지 하단에 보여질 페이징 바의 시작 수
private int endPage; // 페이지 하단에 보여질 페이징 바의 끝 수
// 생성자부
public PageInfo() { }
public PageInfo(int listCount, int currentPage, int pageLimit, int boardLimit, int maxPage, int startPage,
int endPage) {
super();
this.listCount = listCount;
this.currentPage = currentPage;
this.pageLimit = pageLimit;
this.boardLimit = boardLimit;
this.maxPage = maxPage;
this.startPage = startPage;
this.endPage = endPage;
}
// 메소드부
public int getListCount() {
return listCount;
}
public void setListCount(int listCount) {
this.listCount = listCount;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getPageLimit() {
return pageLimit;
}
public void setPageLimit(int pageLimit) {
this.pageLimit = pageLimit;
}
public int getBoardLimit() {
return boardLimit;
}
public void setBoardLimit(int boardLimit) {
this.boardLimit = boardLimit;
}
public int getMaxPage() {
return maxPage;
}
public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
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;
}
@Override
public String toString() {
return "PageInfo [listCount=" + listCount + ", currentPage=" + currentPage + ", pageLimit=" + pageLimit
+ ", boardLimit=" + boardLimit + ", maxPage=" + maxPage + ", startPage=" + startPage + ", endPage="
+ endPage + "]";
}
}
Board
👉🏻 조회용 생성자 추가
public Board(int boardNo, String category, String boardTitle, String boardWriter, int count, Date createDate) {
super();
this.boardNo = boardNo;
this.category = category;
this.boardTitle = boardTitle;
this.boardWriter = boardWriter;
this.count = count;
this.createDate = createDate;
}
boardListView.jsp (동적코딩 VER.)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="com.kh.common.model.vo.PageInfo, java.util.ArrayList, com.kh.board.model.vo.Board" %>
<%
// 필요한 데이터 뽑기
PageInfo pi = (PageInfo)request.getAttribute("pi"); // 페이징바 만들기
ArrayList<Board> list = (ArrayList<Board>)request.getAttribute("list"); // 조회된 내용물 출력하기
int currentPage = pi.getCurrentPage();
int startPage = pi.getStartPage();
int endPage = pi.getEndPage();
int maxPage= pi.getMaxPage();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.outer {
background-color: black;
color : white;
width : 1000px;
height : 550px;
margin : auto;
margin-top : 50px
}
.list-area {
border : 1px solid white;
text-align: center;
}
.list-area>tbody>tr:hover {
background-color: grey;
cursor : pointer;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<h2 align="center">일반게시판</h2>
<br>
<!-- 로그인한 회원만 보여지는 글 작성 버튼 -->
<% if(loginUser != null) { %>
<div style="width:850px;" align="right">
<a href="<%= contextPath %>/enrollForm.bo" class="btn btn-secondary">글작성</a>
</div>
<% } %>
<br>
<!-- 조회할 게시물이 보여질 자리 -->
<table align="center" class="list-area">
<thead>
<tr>
<!-- th[width=]*6 + Enter -->
<th width="70">글번호</th>
<th width="70">카테고리</th>
<th width="300">제목</th>
<th width="100">작성자</th>
<th width="50">조회수</th>
<th width="100">작성일</th>
</tr>
</thead>
<tbody>
<% if(list.isEmpty()) { %>
<tr>
<td colspan="6">조회된 리스트가 없습니다.</td>
</tr>
<% } else { %>
<!--
<tr>
<td>10</td>
<td>게임</td>
<td>게시글 제목이다</td>
<td>user02</td>
<td>10</td>
<td>2022-05-01</td>
</tr>
-->
<% for(Board b : list) { %>
<tr>
<td><%= b.getBoardNo() %></td>
<td><%= b.getCategory() %></td>
<td><%= b.getBoardTitle() %></td>
<td><%= b.getBoardWriter() %></td>
<td><%= b.getCount() %></td>
<td><%= b.getCreateDate() %></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
<script>
$(function() {
<!-- 게시글에 클릭 버튼 걸기 -->
$(".list-area>tbody>tr").click(function() {
<!-- 선택된 td 태그의 글 번호 내용물 추출 -->
location.href = "<%= contextPath %>/detail.bo?bno=" + $(this).children().eq(0).text();
});
});
</script>
<br><br>
<!-- 페이징바 -->
<div align="center" class="paging-area">
<!--
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>7</button>
<button>8</button>
<button>9</button>
<button>10</button>
-->
<button onclick="location.href='<%= contextPath %>/list.bo?currentPage=<%= currentPage - 1 %>';"><</button>
<% for(int p = startPage; p <= endPage; p++) { %>
<% if(p != currentPage) { %>
<button onclick="location.href='<%= contextPath %>/list.bo?currentPage=<%= p %>';"><%= p %></button>
<% } else { %>
<!-- 현재 내가 보고 있는 페이지일 경우에는 버튼이 클릭되지 않게끔 -->
<button disabled><%= p %></button>
<% } %>
<% } %>
<% if(currentPage != maxPage) { %>
<button onclick="location.href='<%= contextPath %>/list.bo?currentPage=<%= currentPage + 1 %>';">></button>
<% } %>
</div>
</div>
</body>
</html>
*게시판 작성
BoardEnrollFormController 서블릿 생성
👉🏻 url mapping: /enrollForm.bo
package com.kh.board.controller;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Category;
/**
* Servlet implementation class BoardEnrollFormController
*/
@WebServlet("/enrollForm.bo")
public class BoardEnrollFormController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardEnrollFormController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 일반 게시판 작성하기 페이지를 띄우는 용도
// 포워딩하기 전에, 우선 카테고리 테이블로부터 전체 카테고리들을 조회해서 request에 담기
ArrayList<Category> list = new BoardService().selectCategoryList();
request.setAttribute("list", list);
request.getRequestDispatcher("views/board/boardEnrollForm.jsp").forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
BoardEnrollForm.jsp 생성
👉🏻 경로: WebContent\views\board
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.ArrayList, com.kh.board.model.vo.Category" %>
<%
// 카테고리 리스트 먼저 뽑기
ArrayList<Category> list = (ArrayList<Category>)request.getAttribute("list");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.outer {
background-color: black;
color: white;
width: 1000px;
height: 550px;
margin: auto;
margin-top: 50px;
}
#enroll-form>table { border : 1px solid white; }
#enroll-form>table input, #enroll-form>table textarea {
width : 100%;
box-sizing : border-box;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<br>
<h2 align="center">일반 게시판 작성하기</h2>
<br>
<form id="enroll-form" action="<%= contextPath %>/insert.bo" method="post" enctype="multipart/form-data">
<!-- 첨부파일을 넘기기 위해서는 form 태그에 enctype="multipart/form-data" 속성을 반드시! 넣어 줘야 함!-->
<!--
카테고리, 제목, 내용, 첨부파일을 입력받고 작성자의 회원번호는 hidden으로 넘김
-->
<input type="hidden" name="userNo" value="<%= loginUser.getUserNo() %>">
<table align="center">
<!-- (tr>th+td)*4 + Enter -->
<tr>
<!--
만약 카테고리가 추가되거나, 삭제되거나, 수정 가능하다면
DB로부터 카테고리를 조회해서 보여 주게끔 해야 함
-->
<th width="100">카테고리</th>
<td width="500">
<select name="category"> <!-- 드롭다운 형식으로 선택할 수 있게-->
<!--
<option value="10">공통</option>
<option value="20">운동</option>
<option value="30">등산</option>
<option value="40">게임</option>
<option value="50">낚시</option>
<option value="60">요리</option>
<option value="70">기타</option>
-->
<% for(Category c : list) { %>
<option value="<%= c.getCategoryNo() %>">
<%= c.getCategoryName() %>
</option>
<% } %>
</select>
</td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" name="title" required></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="content" rows="10" required style="resize:none;"></textarea>
</td>
</tr>
<tr>
<th>첨부파일</th>
<td><input type="file" name="upfile"></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit">작성하기</button>
<button type="reset">취소하기</button>
</div>
</form>
</div>
</body>
</html>
Category 클래스 생성
👉🏻 글 작성 시 카테고리 드롭다운박스에 동적으로 카테고리명을 받기 위해서 생성
package com.kh.board.model.vo;
public class Category {
// 필드부
private int categoryNo; // CATEGORY_NO NUMBER PRIMARY KEY,
private String categoryName; // CATEGORY_NAME VARCHAR2(30) NOT NULL
// 생성자부
public Category() { }
public Category(int categoryNo, String categoryName) {
super();
this.categoryNo = categoryNo;
this.categoryName = categoryName;
}
//메소드부
public int getCategoryNo() {
return categoryNo;
}
public void setCategoryNo(int categoryNo) {
this.categoryNo = categoryNo;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
@Override
public String toString() {
return "Category [categoryNo=" + categoryNo + ", categoryName=" + categoryName + "]";
}
}
BoardService
public ArrayList<Board> selectList(PageInfo pi) {
Connection conn = getConnection();
ArrayList<Board> list = new BoardDao().selectList(conn, pi);
close(conn);
return list;
}
public ArrayList<Category> selectCategoryList() {
Connection conn = getConnection();
ArrayList<Category> list = new BoardDao().selectCategoryList(conn);
close(conn);
return list;
}
public int insertBoard(Board b, Attachment at) {
Connection conn = getConnection();
// 주의사항 => DAO의 메소드 1개 == 쿼리문 1개
// BOARD 테이블 INSERT 요청 먼저 (첨부파일이 있든 없든 간에 무조건 일어나야 함)
int result1 = new BoardDao().insertBoard(conn, b); // 성공: 1, 실패: 0
// 한 개의 트랜잭션에 테이블의 변동이 있는 DML이 두 번 실행
// => 그 두 번의 DML 중 하나라도 실패한다면 전부 rollback 처리해야 함
// => 그 두 번의 DML이 모두 성공해야지만 커밋!
// 만약 첨부파일이 있다면 ATTACHMENT 테이블에 INSERT 요청을 보내기
// 두 번째 요청에 대한 결과값을 담을 변수 먼저 세팅
int result2 = 1;
if(at != null) { // 첨부파일이 있다면
result2 = new BoardDao().insertAttachment(conn, at);
}
// 트랜잭션 처리
// result1 > 0 && result2 > 0 => 둘 다 성공일 경우
if(result1 > 0 && result2 > 0) {
// 경우의 수
// 1. 첨부파일이 있는 경우: result1 == 1, result2 == 1
// 2. 첨부파일이 없는 경우: result1 == 1, result2 == 1
// => result2를 0으로 초기화해 놓는다면, 첨부파일이 없는 경우 트랜잭션이 모두 성공했을 때도 result2는 여전히 0이기 때문에 else 블럭으로 빠져나감
// => 애초에 result2 변수 세팅을 1로 변경해 줄 것임!
commit(conn);
} else {
rollback(conn);
}
close(conn);
// 자바 문법상 메소드 한 개의 리턴값은 한 개뿐 => result1, result2를 같이 넘겨야 함
// 경우의 수
// result1 == 1, result2 == 1 => result1 * result2 == 1 (모두 성공인 경우)
// result1 == 1, result2 == 0 => result1 * result2 == 0 (1만 성공인 경우)
// result1 == 0, result2 == 1 => result1 * result2 == 0 (2만 성공인 경우)
// result1 == 0, result2 == 0 => result1 * result2 == 0 (모두 실패한 경우)
// => 혹시라도 하나라도 실패해서 0이 될 경우 아예 실패값을 전달하기 위해 곱셈 결과를 보냄
return result1 * result2;
}
BoardDao
public ArrayList<Category> selectCategoryList(Connection conn) {
// SELECT문 => ResultSet 객체 (여러 행 조회)
ArrayList<Category> list = new ArrayList<>();
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectCategoryList");
try {
pstmt = conn.prepareStatement(sql);
rset = pstmt.executeQuery();
while(rset.next()) {
list.add(new Category(rset.getInt("CATEGORY_NO")
, rset.getString("CATEGORY_NAME")));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return list;
}
public int insertBoard(Connection conn, Board b) {
// INSERT문 => int (처리된 행의 갯수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, Integer.parseInt(b.getCategory()));
pstmt.setString(2, b.getBoardTitle());
pstmt.setString(3, b.getBoardContent());
pstmt.setInt(4, Integer.parseInt(b.getBoardWriter()));
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
// 여기서 conn 반납하면 절대 안 됨!!!
// 이 트랜잭션에서 2개의 insert문을 실행하기 때문에 하나라도 실패하면 rollback 되어 버림
}
return result;
}
public int insertAttachment(Connection conn, Attachment at) {
// INSERT문 => int (처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertAttachment");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, at.getOriginName());
pstmt.setString(2, at.getChangeName());
pstmt.setString(3, at.getFilePath());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
board-mapper.xml
<entry key="selectCategoryList">
SELECT *
FROM CATEGORY
</entry>
<entry key="insertBoard">
INSERT INTO BOARD (BOARD_NO
, BOARD_TYPE
, CATEGORY_NO
, BOARD_TITLE
, BOARD_CONTENT
, BOARD_WRITER)
VALUES(SEQ_BNO.NEXTVAL
, 1
, ?
, ?
, ?
, ?)
</entry>
<entry key="insertAttachment">
INSERT INTO ATTACHMENT (FILE_NO
, REF_BNO
, ORIGIN_NAME
, CHANGE_NAME
, FILE_PATH)
VALUES (SEQ_FNO.NEXTVAL
, SEQ_BNO.CURRVAL
, ?
, ?
, ?)
</entry>
BoardInsertController 서블릿 생성
👉🏻 url mapping: /insert.bo
package com.kh.board.controller;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Attachment;
import com.kh.board.model.vo.Board;
import com.kh.common.MyFileRenamePolicy;
import com.oreilly.servlet.MultipartRequest;
/**
* Servlet implementation class BoardInsertController
*/
@WebServlet("/insert.bo")
public class BoardInsertController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardInsertController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 일반 게시글 추가 가능
// 1. Board 테이블에 insert
// 2. 만약 첨부파일이 있다면 Attachment 테이블도 insert
// 인코딩 설정
request.setCharacterEncoding("UTF-8");
/*
// 뽑아야 할 값
// userNo: 작성자 번호
String boardWriter = request.getParameter("userNo"); // "3"
// category: 카테고리 번호
String category = request.getParameter("category"); // "20"
// title: 제목
String boardTitle = request.getParameter("title");
// content: 내용
String boardContent = request.getParameter("content");
// upfile: 첨부파일
*/
// System.out.println(boardWriter);
// System.out.println(category);
// System.out.println(boardTitle);
// System.out.println(boardContent);
// 폼 전송을 일반 방식이 아닌 multipart/form-data로 전송하는 경우
// 일반 request 객체로부터 값 뽑기가 불가함 => null 나옴
// => MultipartRequest 타입으로 변환 후 뽑기 가능
// 우선 해당 요청이 multipart/form-data 형식이지 먼저 검사
if(ServletFileUpload.isMultipartContent(request)) {
// ServletFileUpload.isMultipartContent(request)
// => 해당 request (요청)이 multipart/form-data 형식이라면 true 반환
// System.out.println("이거 multipart 어쩌고 맞다");
// HttpServletRequest 타입을 MultipartRequest 타입으로 변환하기
// => request 객체, 저장할 파일의 경로값, 파일의 용량 제한, 인코딩 형식, 파일명을 수정시켜 주는 객체
// (변환하는 구문의 역할은 타입을 변환할 뿐만 아니라 그 요청에 딸린 첨부파일까지 다 저장해 주는 역할)
// 1. 전송되는 파일을 처리할 작업 내용
// 1_1. 전송 파일의 용량 제한
// (정수 타입 => int, byte 단위의 값을 기술해야 함)
// => 10Mbte로 제한
/*
* 단위 정리
* 1byte == 8bit
* byte -> kbyte -> mbte -> gbyte -> tbyte -> ...
* 1kbyte == 1024byte (2의 10승)
* 1mbyte == 1024kbyte (2의 10승)
* == 1024 * 1024byte
*/
int maxSize = 10 * 1024 * 1024;
// 1_2. 전달된 파일을 저장할 서버의 실 경로 지정
/*
* application 내장 객체로부터 경로 알아내기
*
* 참고
* session 내장 객체 얻어내기: request.getSession();
* application 내장 객체 얻어내기: request.getSession().getServletContext()
*
*/
String savePath = request.getSession().getServletContext().getRealPath("/resources/board_upfiles/");
// => 가장 처음 /가 의미하는 것: WebContent
// => 가장 마지막 /가 의미하는 것 : 해당 폴더의 내부
System.out.println(maxSize);
System.out.println(savePath);
// 2. 전달된 파일명 수정 및 서버에 업로드 작업
/*
* - HtttpServletRequest 타입을 MultipartRequest 타입으로 변환
*
* [ 표현법 ]
* MultipartRequest xxx
* = new MultipartRequest(request객체, 저장할폴더의경로, 용량제한,
* 인코딩값, 파일명을수정시켜주는객체);
*
* => 위의 매개변수 생성자로 생성
* => 위 구문 한 줄만으로 넘어온 첨부파일들이 해당 폴더에 업로드됨!
* => MultipartRequest 타입은 (cos.jar에서 제공하는 클래스)
*
* * cos.jar 라이브러리
* com.oreilly.servlet의 약자
* 다운로드 링크: http://www.servlets.com
* => 파일 업로드 기능을 구현하려면 필수적으로 필요함
*
* * 사용자가 올린 파일은 보통 파일명을 수정해서 저장하게끔 되어 있음
* => 같은 파일명이 있을 경우 덮어씌워질 수도 있고, 한글/특수문자/띄어쓰기가 포함된 파일명의 경우
* 서버에 따라 문제가 발생할 가능성이 있음
* => 기본적으로 파일명 수정 작업을 해 주는 개체를 cos.jar 에서 제공해 줌
* DefaultFileRenamePolicy 객체
* (내부적으로 rename() 메소드가 호출되면서 파일명이 수정됨,
* 기본적으로 동일한 파일명이 이미 존재할 경우 카운팅된 숫자를 붙여서 업로드됨)
* 예) aaa.jpg, aaa1.jpg, aaa2.jpg
*
* => 하지만 우리 입맛대로 절대 파일명이 겹치지 않게끔 rename 해 볼 것임!
* (DefaultFileRenamePolicy 객체 사용 x)
* => 내 입맛대로 파일명을 rename 해 주는 객체 생성 (com.kh.common.MyfileRenamePolicy 클래스 만들기)
*
*
*/
MultipartRequest multiRequest = new MultipartRequest(request, savePath
, maxSize, "UTF-8", new MyFileRenamePolicy());
// 3. DB에 기록할 데이터를 뽑아서 VO 객체에 담기
// 뽑아야 할 값
// - 카테고리번호 (category), 제목(title), 내용(content), 작성자 회원번호(userNo)
// => Board 로 가공
String category = multiRequest.getParameter("category");
String boardTitle = multiRequest.getParameter("title");
String boardContent = multiRequest.getParameter("content");
String boardWriter = multiRequest.getParameter("userNo");
Board b = new Board();
b.setCategory(category);
b.setBoardTitle(boardTitle);
b.setBoardContent(boardContent);
b.setBoardWriter(boardWriter);
// 만약 첨부파일이 있다면 첨부파일에 대한 정보도 뽑아서 VO 객체에 담기
// => upfile이라는 키값으로 뽑기
// => Attachment로 가공
Attachment at = null;
// 첨부파일이 있나 검사
// multiRequest.getOriginalFileName(): 해당 키값으로 딸려온 파일의 원본 파일명을 찾아 리턴
// => 없다면 null
if(multiRequest.getOriginalFileName("upfile") != null) { // 첨부파일이 있다면
at = new Attachment();
at.setOriginName(multiRequest.getOriginalFileName("upfile")); // 원본명
at.setChangeName(multiRequest.getFilesystemName("upfile")); // 수정명, 실제 서버에 업로드된 파일명
at.setFilePath("resources/board_upfiles/");
}
// 이 시점 기준으로 첨부파일이 없다면 at == null
// 4. 서비스 요청
int result = new BoardService().insertBoard(b, at);
// 5. 결과에 따른 응답페이지 지정
if(result > 0) { // 성공 => /jsp/list.bo?currentPage=1 요청 (가장 최신글이므로)
request.getSession().setAttribute("alertMsg", "게시글 작성에 성공했습니다.");
response.sendRedirect(request.getContextPath() + "/list.bo?currentPage=1");
} else { // 실패 => 실패 문구 담아서 에러 페이지로 포워딩
// 첨부파일이 있었을 경우 이미 업로드된 첨부파일을 굳이 서버에 보관할 필요가 없음
// => 지워 줌(가지고 있어 봤자 용량만 차지함!)
if(at != null) {
// 삭제하고자 하는 파일 객체를 생성 후 delete() 메소드 호출
new File(savePath + at.getChangeName()).delete();
}
request.setAttribute("errorMsg", "게시글 작성 실패");
request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
Attachment 클래스 생성
👉🏻 메인 기능은 아니지만 게시글 쓸 때 있으면 편하니까 따로 클래스 생성
package com.kh.board.model.vo;
import java.sql.Date;
public class Attachment {
// 필드부
private int fileNo; // FILE_NO NUMBER PRIMARY KEY,
private int refNo; // REF_BNO NUMBER NOT NULL,
private String originName; // ORIGIN_NAME VARCHAR2(255) NOT NULL,
private String changeName; // CHANGE_NAME VARCHAR2(255) NOT NULL,
private String filePath; // FILE_PATH VARCHAR2(1000),
private Date uploadDate; // UPLOAD_DATE DATE DEFAULT SYSDATE NOT NULL,
private int fileLevel; // FILE_LEVEL NUMBER,
private String status; // STATUS VARCHAR2(1) DEFAULT 'Y' CHECK(STATUS IN('Y', 'N')),
// 생성자부
public Attachment() { }
public Attachment(int fileNo, int refNo, String originName, String changeName, String filePath, Date uploadDate,
int fileLevel, String status) {
super();
this.fileNo = fileNo;
this.refNo = refNo;
this.originName = originName;
this.changeName = changeName;
this.filePath = filePath;
this.uploadDate = uploadDate;
this.fileLevel = fileLevel;
this.status = status;
}
// 메소드부
public int getFileNo() {
return fileNo;
}
public void setFileNo(int fileNo) {
this.fileNo = fileNo;
}
public int getRefNo() {
return refNo;
}
public void setRefNo(int refNo) {
this.refNo = refNo;
}
public String getOriginName() {
return originName;
}
public void setOriginName(String originName) {
this.originName = originName;
}
public String getChangeName() {
return changeName;
}
public void setChangeName(String changeName) {
this.changeName = changeName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public Date getUploadDate() {
return uploadDate;
}
public void setUploadDate(Date uploadDate) {
this.uploadDate = uploadDate;
}
public int getFileLevel() {
return fileLevel;
}
public void setFileLevel(int fileLevel) {
this.fileLevel = fileLevel;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Attachment [fileNo=" + fileNo + ", refNo=" + refNo + ", originName=" + originName + ", changeName="
+ changeName + ", filePath=" + filePath + ", uploadDate=" + uploadDate + ", fileLevel=" + fileLevel
+ ", status=" + status + "]";
}
}
board_upfiles 폴더 생성
👉🏻 첨부파일이 저장될 공간
cos.jar 라이브러리 다운로드 받기
👉🏻 첨부파일 업로드를 위해 필요한 기능들을 제공함 필수!
Servlets.com
Home What's New? COS File Upload Library Servlet Polls Mailing Lists Servlet Engines Servlet ISPs Servlet Tools Documentation Online Articles The Soapbox "Java Servlet Programming, Second Edition" "Java Enterprise Best Practices" Speaking & Slides About Ja
www.servlets.com
![](https://blog.kakaocdn.net/dn/VWCOR/btrPajI1XLZ/e7kUSKXTMBVlFC3y4oeXpk/img.png)
![](https://blog.kakaocdn.net/dn/bw67Dz/btrO31JYBQZ/Knv7jBEiJXNYpwo85DTWt0/img.png)
![](https://blog.kakaocdn.net/dn/cdV9Km/btrOQsH0Vws/caaNJkdJuDLvjV3Gc5yG71/img.png)
![](https://blog.kakaocdn.net/dn/5fEuo/btrOQBSvceM/HIHkXzKV9CnilKVY4INEG1/img.png)
- HtttpServletRequest 타입을 MultipartRequest 타입으로 변환
[ 표현법 ]
MultipartRequest xxx
= new MultipartRequest(request객체, 저장할폴더의경로, 용량제한,
인코딩값, 파일명을수정시켜주는객체);
=> 위의 매개변수 생성자로 생성
=> 위 구문 한 줄만으로 넘어온 첨부파일들이 해당 폴더에 업로드됨!
=> MultipartRequest 타입은 (cos.jar에서 제공하는 클래스)
* 사용자가 올린 파일은 보통 파일명을 수정해서 저장하게끔 되어 있음
=> 같은 파일명이 있을 경우 덮어씌워질 수도 있고, 한글/특수문자/띄어쓰기가 포함된 파일명의 경우
서버에 따라 문제가 발생할 가능성이 있음
=> 기본적으로 파일명 수정 작업을 해 주는 개체를 cos.jar 에서 제공해 줌
DefaultFileRenamePolicy 객체
(내부적으로 rename() 메소드가 호출되면서 파일명이 수정됨,
기본적으로 동일한 파일명이 이미 존재할 경우 카운팅된 숫자를 붙여서 업로드됨)
예) aaa.jpg, aaa1.jpg, aaa2.jpg
* => 하지만 우리 입맛대로 절대 파일명이 겹치지 않게끔 rename 해 볼 것임!
(DefaultFileRenamePolicy 객체 사용 x)
=> 내 입맛대로 파일명을 rename 해 주는 객체 생성 (com.kh.common.MyfileRenamePolicy 클래스 만들기)
MyFileRenamePolicy 클래스 생성
👉🏻 사용자가 올린 파일명을 자동으로 rename 해 주는 기능
👉🏻 cos.jar에서 제공하는 FileRenamePolicy 인터페이스를 상속받아서 진행할 것!
package com.kh.common;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.oreilly.servlet.multipart.FileRenamePolicy;
// 인터페이스 구현 => 반드시 미완성된 메소드를 구현해서 써야 됨 (오버라이딩)
public class MyFileRenamePolicy implements FileRenamePolicy {
// 기존의 파일을 전달받아서 파일명 수정 작업 후 수정된 파일 자체를 return 해 줄 것!
@Override
public File rename(File originFile) {
// 원본파일명("aaa.jpg")
String originName = originFile.getName();
// 수정 파일명
// => 파일 업로드 시간 (년월일시분초) + 5자리 랜덤값 (10000 ~ 99999)
// => 최대한 이름이 겹치지 않게!
// 확장자
// => 원본파일의 확장자 그대로
// 원본명 -------> 수정명
// aaa.jpg 20221020163855xxxxx.jpg
// 1. 파일이 업로드된 시간 (년월일시분초)
// *주의* java.util.Date로 import 해 줘야 함
String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 2. 5자리 랜덤값
int ranNum = (int)(Math.random() * 90000) + 10000;
// 3. 원본파일 확장자
// => 파일명 중간에 .이 들어가는 경우도 있기 때문에
// 원본 파일명에서 가장 맨 마지막에 나오는 . 기준으로 파일명과 확장자를 나눔
String ext = originName.substring(originName.lastIndexOf("."));
// 결합
String changeName = currentTime + ranNum + ext;
// 원본파일(originFile)을 수정된 파일명으로 적용시켜서 파일객체로 변환
return new File(originFile.getParent(), changeName);
}
}
![](https://blog.kakaocdn.net/dn/cvcklZ/btrPaHci8t7/qmagyH0sHbQ6NYlB3Ev061/img.png)
![](https://blog.kakaocdn.net/dn/9JsMx/btrO9Ch6Uqv/Yd8eV4WB5WLH0IOR6ofKPK/img.png)
*게시판 상세조회
boardDetailView.jsp 생성
👉🏻 진행 순서: 정적 코딩으로 기술한 코드들이 잘 표현되는지부터 확인 후 동적 코딩으로 진행할 것
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="com.kh.board.model.vo.*" %>
<!--
이렇게 따로 import 해 줘도 되지만 같은 경로니까 위처럼 별 찍어 줘도 됨!
<%@ page import="com.kh.board.model.vo.Board, com.kh.board.model.vo.Attachment" %>
-->
<%
// 필요한 데이터들 먼저 뽑기
Board b = (Board)request.getAttribute("b");
// 게시글 번호, 카테고리명, 글 제목, 글 내용, 작성자 아이디, 작성일
Attachment at = (Attachment)request.getAttribute("at");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.outer {
background-color: black;
color: white;
width: 1000px;
height: 700;
margin: auto;
margin-top: 50px;
}
.outer table { border-color : white; }
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<br>
<h2 align="center">일반게시판 상세보기</h2>
<br>
<table id="detail-area" align="center" border="1">
<!-- (tr>th+td+th+td)*4 + Enter -->
<tr>
<th width="70">카테고리</th>
<td width="70"><%= b.getCategory() %></td>
<th width="70">제목</th>
<td width="350"><%= b.getBoardTitle() %></td>
</tr>
<tr>
<th>작성자</th>
<td><%= b.getBoardWriter() %></td>
<th>작성일</th>
<td><%= b.getCreateDate() %></td>
</tr>
<tr>
<th>내용</th>
<td colspan="3">
<p style="height:200px;">
<%= b.getBoardContent() %>
</p>
</td>
</tr>
<tr>
<th>첨부파일</th>
<td colspan="3">
<% if(at == null) { %>
<!-- 첨부파일이 없는 경우: '첨부파일이 없습니다.' 출력 -->
첨부파일이 없습니다.
<% } else { %>
<!-- 첨부파일이 있는 경우: 첨부파일을 다운로드받을 수 있게끔 링크 걸기 (첨부파일의 원본명)-->
<!--
링크를 눌렀을 때 조회만 되게끔
<a href="<%= contextPath %>/<%= at.getFilePath() + at.getChangeName() %>">
-->
<!-- 링크를 눌렀을 때 다운로드도 가능하게끔, download 속성에서 "원본명"으로 다운로드 가능하게끔 설정 -->
<a download="<%= at.getOriginName() %>" href="<%= contextPath %>/<%= at.getFilePath() + at.getChangeName() %>">
<%= at.getOriginName() %>
</a>
<% } %>
</td>
</tr>
</table>
<br>
<div align="center">
<a href="<%= contextPath %>/list.bo?currentPage=1" class="btn btn-secondary btn-sm">목록가기</a>
<% if(loginUser != null && loginUser.getUserId().equals(b.getBoardWriter())) { %>
<!-- 로그인한 사용자가 게시글 작성자일 경우에만 보여지게끔 -->
<a href="" class="btn btn-warning btn-sm">수정하기</a>
<a href=""class="btn btn-danger btn-sm">삭제하기</a>
<% } %>
</div>
<br>
<!-- 우선 화면 구현만! 기능구현은 AJAX 배우고 나서 할 것! -->
<div id="reply-area">
<table border="1" align="center">
<thead>
<!-- 로그인이 되어 있을 경우 -->
<!--
<tr>
<th>댓글 작성</th>
<td>
<textarea id="" cols="50" rows="3" style="resize:none;"></textarea>
</td>
<td><button>댓글등록</button></td>
</tr>
-->
<!-- 로그인이 되어 있지 않은 경우 -->
<tr>
<th>댓글 작성</th>
<td>
<textarea id="" cols="50" rows="3" style="resize:none;" readonly>로그인 후 이용 가능한 서비스입니다.</textarea>
</td>
<td><button disabled>댓글등록</button></td>
</tr>
</thead>
<tbody>
<tr>
<td>admin</td>
<td>댓글 내용이 들어갈 자리~!</td>
<td>2022년 10월 22일</td>
</tr>
<tr>
<td>admin</td>
<td>댓글 내용이 들어갈 자리~!</td>
<td>2022년 10월 22일</td>
</tr>
</tbody>
</table>
<br>
<br>
</div>
</div>
</body>
</html>
boardListView.jsp
👉🏻 게시글 클릭 가능하도록 스크립트 넣기
👉🏻 기존 내용에서 <script>만 추가함!
<% for(Board b : list) { %>
<tr>
<td><%= b.getBoardNo() %></td>
<td><%= b.getCategory() %></td>
<td><%= b.getBoardTitle() %></td>
<td><%= b.getBoardWriter() %></td>
<td><%= b.getCount() %></td>
<td><%= b.getCreateDate() %></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
<script>
$(function() {
<!-- 게시글에 클릭 버튼 걸기 -->
$(".list-area>tbody>tr").click(function() {
<!-- 선택된 td 태그의 글 번호 내용물 추출 -->
location.href = "<%= contextPath %>/detail.bo?bno=" + $(this).children().eq(0).text();
});
});
</script>
<br><br>
BoardDetailController
👉🏻 url mapping: /detail.bo
package com.kh.board.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Attachment;
import com.kh.board.model.vo.Board;
/**
* Servlet implementation class BoardDetailController
*/
@WebServlet("/detail.bo")
public class BoardDetailController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardDetailController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 해당 게시글 번호 먼저 뽑기
int boardNo = Integer.parseInt(request.getParameter("bno"));
// 조회 수 증가 / 게시글 조회(Board) / 첨부파일 조회(Attachment)
// => BoardService로 요청을 3번 보내야 함
BoardService bService = new BoardService();
// 1. 조회 수 증가 요청
int result = bService.increaseCount(boardNo);
if(result > 0) { // 조회 수 증가에 성공했다면
// 게시글 조회, 첨부파일 조회
Board b = bService.selectBoard(boardNo);
Attachment at = bService.selectAttachment(boardNo);
// 게시글 정보 보내기
request.setAttribute("b", b);
request.setAttribute("at", at);
// 게시글 상세조회 페이지로 포워딩
request.getRequestDispatcher("views/board/boardDetailView.jsp").forward(request, response);
} else { // 조회 수 증가에 실패했다면
// 에러 문구 담아서 에러페이지로 포워딩
// 키값 오타 나면 null 값 찍힘!
request.setAttribute("errorMsg", "게시글 상세 조회 실패");
request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
BoardService
public int increaseCount(int boardNo) {
Connection conn = getConnection();
int result = new BoardDao().increaseCount(conn, boardNo);
if(result > 0) {
commit(conn);
} else {
rollback(conn);
}
close(conn);
return result;
}
public Board selectBoard(int boardNo) {
Connection conn = getConnection();
Board b = new BoardDao().selectBoard(conn, boardNo);
close(conn);
return b;
}
public Attachment selectAttachment(int boardNo) {
Connection conn = getConnection();
Attachment at = new BoardDao().selectAttachment(conn, boardNo);
close(conn);
return at;
}
BoardDao
public int increaseCount(Connection conn, int boardNo) {
// UPDATE문 => int(처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("increaseCount");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public Board selectBoard(Connection conn, int boardNo) {
// SELECT문 => ResultSet 객체 (많아 봤자 1행 조회)
Board b = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
rset = pstmt.executeQuery();
if(rset.next()) {
b = new Board(rset.getInt("BOARD_NO")
, rset.getString("CATEGORY_NAME")
, rset.getString("BOARD_TITLE")
, rset.getString("BOARD_CONTENT")
, rset.getString("USER_ID")
, rset.getDate("CREATE_DATE"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return b;
}
public Attachment selectAttachment(Connection conn, int boardNo) {
// SELECT문 => ResultSet 객체 (1행 조회)
Attachment at = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
String sql = prop.getProperty("selectAttachment");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
rset = pstmt.executeQuery();
if(rset.next()) {
at = new Attachment();
at.setFileNo(rset.getInt("FILE_NO"));
at.setOriginName(rset.getString("ORIGIN_NAME"));
at.setChangeName(rset.getString("CHANGE_NAME"));
at.setFilePath(rset.getString("FILE_PATH"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
}
return at;
}
board-mapper.xml
<entry key="increaseCount">
UPDATE BOARD
SET COUNT = COUNT + 1
WHERE BOARD_NO = ?
AND STATUS = 'Y'
</entry>
<entry key="selectBoard">
SELECT BOARD_NO
, CATEGORY_NAME
, BOARD_TITLE
, BOARD_CONTENT
, USER_ID
, CREATE_DATE
FROM BOARD B
LEFT JOIN CATEGORY USING (CATEGORY_NO)
JOIN MEMBER ON (BOARD_WRITER = USER_NO)
WHERE BOARD_NO = ?
AND B.STATUS = 'Y'
</entry>
/* CATEGORY 테이블과 그냥 join 해도 되지만
추후 사진 게시판에서 유용하게 쓰기 위해 LEFT OUTER JOIN */
<entry key="selectAttachment">
SELECT FILE_NO
, ORIGIN_NAME
, CHANGE_NAME
, FILE_PATH
FROM ATTACHMENT
WHERE REF_BNO = ?
AND STATUS = 'Y'
</entry>
Board
👉🏻 일반게시글 상세 조회용 생성자 추가
// 일반게시글 상세조회용 생성자
public Board(int boardNo, String category, String boardTitle, String boardContent, String boardWriter,
Date createDate) {
super();
this.boardNo = boardNo;
this.category = category;
this.boardTitle = boardTitle;
this.boardContent = boardContent;
this.boardWriter = boardWriter;
this.createDate = createDate;
}
*게시글 수정
boardUpdateForm.jsp 생성
👉🏻 boardEnrollForm.jsp 에서 form 태그 내부의 내용 복붙 후 수정함
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.ArrayList, com.kh.board.model.vo.*" %>
<%
// 필요한 데이터 먼저 뽑기
ArrayList<Category> list = (ArrayList<Category>)request.getAttribute("list");
Board b = (Board)request.getAttribute("b");
Attachment at = (Attachment)request.getAttribute("at");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
.outer {
background-color: black;
color : white;
width : 1000px;
height : 550px;
margin : auto;
margin-top : 50px;
}
#update-form>table { border : 1px solid white; }
#update-form>table input, #update-form>table textarea {
width : 100%;
box-sizing : border-box;
}
</style>
</head>
<body>
<%@ include file="../common/menubar.jsp" %>
<div class="outer">
<br>
<h2 align="center">일반 게시판 수정하기</h2>
<br>
<form id="update-form" action="<%= contextPath %>/update.bo" method="post" enctype="multipart/form-data">
<!-- 게시글 번호를 hidden으로 넘기기 -->
<input type="hidden" name="bno" value="<%= b.getBoardNo() %>">
<table align="center">
<!-- (tr>th+td)*4 + Enter -->
<tr>
<!--
만약 카테고리가 추가되거나, 삭제되거나, 수정 가능하다면
DB로부터 카테고리를 조회해서 보여 주게끔 해야 함
-->
<th width="100">카테고리</th>
<td width="500">
<select name="category"> <!-- 드롭다운 형식으로 선택할 수 있게-->
<% for(Category c : list) { %>
<option value="<%= c.getCategoryNo() %>"><%= c.getCategoryName() %></option>
<% } %>
</select>
<script>
$(function() {
$("#update-form option").each(function() {
if($(this).text() == "<%= b.getCategory() %>") {
$(this).attr("selected", true);
}
});
});
</script>
</td>
</tr>
<tr>
<th>제목</th>
<td><input type="text" name="title" required value="<%= b.getBoardTitle() %>"></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="content" rows="10" required style="resize:none;"><%= b.getBoardContent() %></textarea>
</td>
</tr>
<tr>
<th>첨부파일</th>
<td>
<% if(at != null) { %>
<%= at.getOriginName() %>
<!-- 기존파일의 파일번호, 수정명을 hidden으로 추가적으로 넘기기 => 찾아서 삭제해야 하니까 -->
<input type="hidden" name="originFileNo" value="<%= at.getFileNo() %>">
<input type="hidden" name="originFileName" value="<%= at.getChangeName() %>">
<% } %>
<input type="file" name="reUpfile">
</td>
</tr>
</table>
<br>
<div align="center">
<button type="submit">수정하기</button>
</div>
</form>
</div>
</body>
</html>
boardDetailView.jsp
👉🏻 수정하기 버튼에 링크 달기
<!-- 로그인한 사용자가 게시글 작성자일 경우에만 보여지게끔 -->
<a href="<%= contextPath %>/updateForm.bo?bno=<%= b.getBoardNo() %>" class="btn btn-warning btn-sm">수정하기</a>
<a href=""class="btn btn-danger btn-sm">삭제하기</a>
<% } %>
</div>
BoardUpdateFormController 생성
👉🏻 url mapping: /updateForm.bo
👉🏻 updateForm.bo 라는 url을 가진 서블릿 만들기
package com.kh.board.controller;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Attachment;
import com.kh.board.model.vo.Board;
import com.kh.board.model.vo.Category;
/**
* Servlet implementation class BoardUpdateFormController
*/
@WebServlet("/updateForm.bo")
public class BoardUpdateFormController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardUpdateFormController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 카테고리 정보들 전체 조회, 해당 게시글 정보 조회, 해당 게시글에 딸린 첨부파일 정보 조회
BoardService bService = new BoardService();
int boardNo = Integer.parseInt(request.getParameter("bno"));
ArrayList<Category> list = bService.selectCategoryList();
Board b = bService.selectBoard(boardNo);
// 글번호, 카테고리명, 글 제목, 글 내용, 작성자 아이디, 작성일이 b에 담길 것!
Attachment at = bService.selectAttachment(boardNo);
// 첨부파일번호, 원본명, 수정명, 저장경로
request.setAttribute("list", list);
request.setAttribute("b", b);
request.setAttribute("at", at);
// 일반게시판 수정하기 페이지 포워딩
request.getRequestDispatcher("views/board/boardUpdateForm.jsp").forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
BoardUpdateController 서블릿 생성
👉🏻 url mapping: /update.bo
package com.kh.board.controller;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import com.kh.board.model.service.BoardService;
import com.kh.board.model.vo.Attachment;
import com.kh.board.model.vo.Board;
import com.kh.common.MyFileRenamePolicy;
import com.oreilly.servlet.MultipartRequest;
/**
* Servlet implementation class BoardUpdateController
*/
@WebServlet("/update.bo")
public class BoardUpdateController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardUpdateController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 인코딩 설정
request.setCharacterEncoding("UTF-8");
// multipart/form-data 형식으로 요청이 들어왔는지 검사
if(ServletFileUpload.isMultipartContent(request)) {
// true라면, (== multipart/form-data가 맞다면)
// 1. 전송된 파일에 대한 정보들 먼저 지정 (전송 파일 용량 제한, 전달된 파일을 저장할 서버의 실경로)
// 1_1. 전송 파일 용량 제한 (int 자료형으로 byte 단위 기준)
int maxSize = 10 * 1024 * 1024; // 10mbyte
// 1_2. 전달된 파일을 저장할 서버의 실제 경로
String savePath = request.getSession().getServletContext().getRealPath("/resources/board_upfiles");
// 2. 전달된 파일명 수정 작업 후 서버에 업로드 & MultipartRequest 타입으로 변환
// => 매개변수 생성자 호출
MultipartRequest multiRequest = new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
// 3. 본격적으로 요청 시 전달값을 뽑을 수 있음!
// => 변수 및 객체에 기록
// - 공통적으로 수행: Board 테이블 Update에 필요한 데이터들 먼저 가공
int boardNo = Integer.parseInt(multiRequest.getParameter("bno"));
String category = multiRequest.getParameter("category");
String boardTitle = multiRequest.getParameter("title");
String boardContent = multiRequest.getParameter("content");
Board b = new Board();
b.setBoardNo(boardNo);
b.setCategory(category);
b.setBoardTitle(boardTitle);
b.setBoardContent(boardContent);
// 첨부파일에 대한 정보를 담아둘 변수
Attachment at = null;
// 요청 시 전달값 중 넘어온 첨부파일이 있는지 먼저 검사
if(multiRequest.getOriginalFileName("reUpfile") != null) {
// 넘어온 첨부파일이 있을 경우
// => Attachment 테이블에 Update 또는 Insert 해야 함
// 2개의 쿼리문 중 공통적으로 필요한 항목들 먼저 세팅
// => ORIGIN_NAMEM, CHANGE_NAME 컬럼값
at = new Attachment();
at.setOriginName(multiRequest.getOriginalFileName("reUpfile"));
at.setChangeName(multiRequest.getFilesystemName("reUpfile"));
// Insert 구문에서 추가적으로 필요로 하는 FILE_PATH 컬럼값도 세팅
at.setFilePath("resources/board_upfiles/");
// 기존 파일이 있었는지 없었는지 검사
// => 기존 파일이 있었다면 : Attachment 테이블에 Update 구문 실행
// => 기존 파일이 없었다면 : Attachment 테이블에 Insert 구문 실행
// 기존 파일이 있을 경우 originFileNo, originFileName을 넘겼음
if(multiRequest.getParameter("originFileNo") != null) {
// 기존 첨부파일이 있을 경우
// Update 구문에서 추가적으로 필요로 하는 기존 파일의 고유번호 담기
at.setFileNo(Integer.parseInt(multiRequest.getParameter("originFileNo")));
} else {
// 기존 첨부파일이 없을 경우
// Insert 구문에서 추가적으로 필요로 하는 참조 게시글 번호를 담기
at.setRefNo(boardNo);
}
}
// 이 시점 기준으로 각 케이스별로 필요한 데이터들이 at에 담겨 있음
// b at
// CASE 1. 기존 첨부파일 X, 새로운 첨부파일 X => b, null => BOARD 테이블 UPDATE만 실행
// CASE 2. 기존 첨부파일 O, 새로운 첨부파일 O => b, fileNo이 담긴 at => BOARD UPDATE, ATTACHMENT UPDATE
// CASE 3. 기존 첨부파일 X, 새로운 첨부파일 O => b, refNo이 담긴 at => BOARD UPDATE, ATTACHMENT INSERT
// 모두 하나의 트랜잭션으로 처리해야 함!
int result = new BoardService().updateBoard(b, at);
if(result > 0) { // 수정 성공 => 상세 페이지로 url 요청
// 만약에 기존 첨부파일이 있고, 새로운 첨부파일도 있을 경우! 서버에 있던 기존 첨부파일을 삭제
if(multiRequest.getParameter("originFileName") != null
&& multiRequest.getOriginalFileName("reUpfile") != null) {
new File(savePath + multiRequest.getParameter("originFileName")).delete();
}
request.getSession().setAttribute("alertMsg", "게시글이 성공적으로 수정되었습니다.");
response.sendRedirect(request.getContextPath() + "/detail.bo?bno=" + boardNo); // url 요청
} else { // 수정 실패 => 에러 문구 담아서 에러 페이지로 포워딩
request.setAttribute("errorMsg", "게시글 수정 실패");
request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
BoardService
public int updateBoard(Board b, Attachment at) {
Connection conn = getConnection();
// 3가지 경우 모두 공통적으로 실행해야 하는 BOARD UPDATE 구문 요청
int result1 = new BoardDao().updateBoard(conn, b);
// 두 번째 요청 결과를 받을 변수 세팅
// 0으로 세팅했을 때, 기존 첨부파일이 없었다면 넘겨 줄 곱하기 결과값은 무조건 0이 되므로 애초에 1로 세팅
int result2 = 1;
if(at != null) { // 새롭게 첨부된 파일이 있을 경우 => Attachment 테이블에 Update 또는 Insert
if(at.getFileNo() != 0) { // 기존 첨부파일이 있는 경우 => Attachment 테이블에 UPDATE 요청
result2 = new BoardDao().updateAttachment(conn, at);
} else { // 기존 첨부파일이 없는 경우 => Attachment 테이블에 INSERT 요청
// 기존에 우리가 만들어 놨던 insertAttachment 재활용 불가! (쿼리문이 다르기 때문)
result2 = new BoardDao().insertNewAttachment(conn, at);
}
}
// 모든 요청이 다 성공했을 경우만 commit
if(result1 > 0 && result2 > 0) {
commit(conn);
} else {
rollback(conn);
}
close(conn);
return result1 * result2;
}
BoardDao
public int updateBoard(Connection conn, Board b) {
// UPDATE문 => int (처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, Integer.parseInt(b.getCategory()));
pstmt.setString(2, b.getBoardTitle());
pstmt.setString(3, b.getBoardContent());
pstmt.setInt(4, b.getBoardNo());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int updateAttachment(Connection conn, Attachment at) {
// UPDATE문 => int (처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("updateAttachment");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, at.getOriginName());
pstmt.setString(2, at.getChangeName());
pstmt.setInt(3, at.getFileNo());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
public int insertNewAttachment(Connection conn, Attachment at) {
// INSERT문 => int (처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("insertNewAttachment");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, at.getRefNo());
pstmt.setString(2, at.getOriginName());
pstmt.setString(3, at.getChangeName());
pstmt.setString(4, at.getFilePath());
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
board-mapper.xml
👉🏻 다양한 경우를 생각해서 로직을 짜 줘야 함
<로직 흐름>
-- 일반 게시판 수정 기능
-- 일반 게시글 수정뿐 아니라 첨부파일도 수정 가능
-- 게시글 수정 요청 => BOARD 테이블 UPDATE 구문 실행 (첨부파일이 수정되든 안 되든 무조건 실행할 구문
UPDATE BOARD
SET CATEGORY_NO = ?
, BOARD_TITLE = ?
, BOARD_CONTENT = ?
WHERE BOARD_NO = ?
AND STATUS = 'Y'
-- CASE 1. 기존의 첨부파일이 없었는데 새로운 첨부파일도 없을 경우
-- 추가적으로 ATTACHMENT 테이블의 DML 문을 실행하지 않아도 됨
-- CASE 2. 기존의 첨부파일이 있었는데 새로운 첨부파일이 업로드 된 경우
-- 기존의 첨부파일을 찾아서 (FILE_NO) ATTACHMENT 테이블에 UPDATE 구문 실행
-- 기존 첨부파일의 파일번호, 수정명을 넘겨야 함 (input type="hidden" 사용)
UPDATE ATTACHMENT
SET ORIGIN_NAME = ?
, CHANGE_NAME = ?
, UPLOAD_DATE = SYSDATE
WHERE FILE_NO = ?
-- CASE 3. 기존의 첨부파일이 없었는데 새로운 첨부파일이 업로드 된 경우
-- ATTACHMENT 테이블에 INSERT 구문 실행
INSERT INTO ATTACHMENT(FILE_NO
, REF_BNO
, ORIGIN_NAME
, CHANGE_NAME
, FILE_PATH)
VALUES (SEQ_FNO.NEXTVAL
, ?
, ?
, ?
, ?)
<entry key="updateBoard">
UPDATE BOARD
SET CATEGORY_NO = ?
, BOARD_TITLE = ?
, BOARD_CONTENT = ?
WHERE BOARD_NO = ?
AND STATUS = 'Y'
</entry>
<entry key="updateAttachment">
UPDATE ATTACHMENT
SET ORIGIN_NAME = ?
, CHANGE_NAME = ?
, UPLOAD_DATE = SYSDATE
WHERE FILE_NO = ?
</entry>
<entry key="insertNewAttachment">
INSERT INTO ATTACHMENT(FILE_NO
, REF_BNO
, ORIGIN_NAME
, CHANGE_NAME
, FILE_PATH)
VALUES (SEQ_FNO.NEXTVAL
, ?
, ?
, ?
, ?)
</entry>
*게시글 삭제(숙제)
boardDetailView
👉🏻 삭제하기 버튼에 링크 추가
<% if(loginUser != null && loginUser.getUserId().equals(b.getBoardWriter())) { %>
<!-- 로그인한 사용자가 게시글 작성자일 경우에만 보여지게끔 -->
<a href="<%= contextPath %>/updateForm.bo?bno=<%= b.getBoardNo() %>" class="btn btn-warning btn-sm">수정하기</a>
<a href="<%= contextPath %>/delete.bo?bno=<%= b.getBoardNo() %>" class="btn btn-danger btn-sm">삭제하기</a>
<% } %>
</div>
BoardDeleteController 서블릿 생성
👉🏻 url mapping: /delete.bo
package com.kh.board.controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.kh.board.model.service.BoardService;
/**
* Servlet implementation class BoardDeleteController
*/
@WebServlet("/delete.bo")
public class BoardDeleteController extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public BoardDeleteController() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 삭제하고자 하는 게시글 번호
int boardNo = Integer.parseInt(request.getParameter("bno"));
// 서비스단으로 글 번호 넘기며 삭제 요청 및 결과 받기
int result = new BoardService().deleteBoard(boardNo);
if(result > 0) { // 성공 => alertMsg 담기, 일반 게시판 리스트 페이지로 url 요청
request.getSession().setAttribute("alertMsg", "성공적으로 게시글이 삭제되었습니다.");
response.sendRedirect(request.getContextPath() + "/list.bo?currentPage=1"); // /jsp/list.bo
} else { // 실패 => 에러문구를 담아서 에러 페이지로 포워딩
request.setAttribute("errorMsg", "게시글 삭제 실패");
request.getRequestDispatcher("views/common/errorPage.jsp").forward(request, response);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
boardService
// 게시글 삭제용 서비스
public int deleteBoard(int boardNo) {
Connection conn = getConnection();
int result = new BoardDao().deleteBoard(conn, boardNo);
if(result > 0) {
commit(conn);
} else {
rollback(conn);
}
return result;
}
boardDao
public int deleteBoard(Connection conn, int boardNo) {
// UPDATE문 -> int (처리된 행의 개수)
int result = 0;
PreparedStatement pstmt = null;
String sql = prop.getProperty("deleteBoard");
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
board-mapper.xml
<entry key="deleteBoard">
UPDATE BOARD
SET STATUS = 'N'
WHERE BOARD_NO = ?
</entry>