Servlet/JSP의 일반 게시판(Board)를 만들어 보자
일반 게시판
✔️ 게시판 기본 설정, 게시판 전체 조회, 게시판 상세 조회, 수정, 삭제
더미 데이터 활용하는 법
(= 다량의 insert문 한 번에 만드는 법)
- 하단의 엑셀 파일 다운로드
- sql Deveolper에서 내가 삽입하기를 원하는 테이블 선택 후 데이터 임포트 선택
- 경로 선택하여 열기 헤드 체크 후 다음
- 임포트 행 제한 체크하지 말고 다음 (엑셀 파일에 더미 데이터 행 100개 이상임)
- 열 선택 확인 후 다음
- 열 정의 확인 후 다음
- 완료
- 삽입 실패! 오류 무시하고 계속하시겠습니까? 예
- 가장 하단에 COMMIT; 구문 작성
- 실행할 계정 접속 후 스크립트 실행
*게시판 기본 설정
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 라이브러리 다운로드 받기
👉🏻 첨부파일 업로드를 위해 필요한 기능들을 제공함 필수!
- 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);
}
}
*게시판 상세조회
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>