프로젝트 주제
•
JSP를 활용하여 누구나 이용 가능한 게시판 개발
프로젝트 구조
ERD
Table
Search
사용 기술 스택
•
Language With Appliocation:
◦
Java, JSP, HTML, CSS, JavaScript
•
DBMS : MySQL
•
Java Version : Java 17
•
IDE : Intellij IDEA Ultimate Edition (2023.2)
•
Framework : bootstrap 5.0.2
프로젝트 기능
•
게시글 작성
•
게시판 기본 목록 조회
•
특정 사용자 게시글 조회
•
게시글 수정
•
게시글 삭제
도메인 생성
•
Board
package com.board.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
public class Board {
private int id;
private String title;
private String content;
private String author;
private String img;
private LocalDateTime created_at;
private LocalDateTime updated_at;
}
Java
복사
DB 연결
package com.board.util;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionPool {
public static class DBPool {
private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DB_URL = "jdbc:mysql://localhost:3306/news_db";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "Dmove1122!";
// 아파치에 DBCP (DataBase Connection Pooling) 라이브러리에서 제공하는 클래스를 사용
static final BasicDataSource dbcp = new BasicDataSource();
static {
dbcp.setDriverClassName(JDBC_DRIVER);
dbcp.setUrl(DB_URL);
dbcp.setUsername(DB_USER);
dbcp.setPassword(DB_PASSWORD);
// 연결 수 관리
dbcp.setInitialSize(10); // 초기 연결 수 10개 설정
dbcp.setMaxTotal(50); // 최대 연결 수 50개 설정
dbcp.setMaxIdle(20); // 최대 유휴 연결 수 20개 설정
dbcp.setMinIdle(5); // 최소 유휴 연결 수 5개 설정
}
public static Connection getDBPool() throws SQLException {
return dbcp.getConnection();
}
}
}
Java
복사
컨트롤러
•
BoardController
package com.board.controller;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface BoardController {
void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}
Java
복사
•
ListController
package com.board.controller;
import com.board.model.Board;
import com.board.service.BoardService;
import com.board.service.BoardServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public class ListController implements BoardController {
BoardService bs = new BoardServiceImpl();
@Override
public void process(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
try {
String action = req.getParameter("action");
// 글 추가
if (action != null && action.equals("addWrite")) {
System.out.println("글 추가");
bs.addWrite(req);
// 중복 응답 방지
res.sendRedirect(req.getRequestURI());
return;
} else if (action != null && action.equals("deleteWrite")) {
System.out.println("글 삭제");
bs.deleteWrite(req);
res.sendRedirect(req.getRequestURI());
return;
} else {
// 작성글 전체 보기
System.out.println("글 전체 보기");
List<Board> boardList = bs.getAll();
req.setAttribute("boardList", boardList);
}
} catch (Exception e) {
e.printStackTrace();
req.setAttribute("error", e.getMessage());
}
String view = "/WEB-INF/views/BoardList.jsp";
req.getRequestDispatcher(view).forward(req, res);
}
}
Java
복사
•
ViewController
package com.board.controller;
import com.board.model.Board;
import com.board.service.BoardService;
import com.board.service.BoardServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ViewController implements BoardController {
BoardService bs = new BoardServiceImpl();
@Override
public void process(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
// 특정 작성글 보기(우선적으로) + 글 수정 로직 구현
try {
String action = req.getParameter("action");
if (action != null && action.equals("modifyWrite")) {
bs.modifyWrite(req);
res.sendRedirect("/board/boardView?id=" + req.getParameter("id"));
return;
} else {
Board board = bs.getWrite(req);
req.setAttribute("board", board);
}
} catch (Exception e) {
e.printStackTrace();
res.sendRedirect("/board/boardList");
return;
}
String view = "/WEB-INF/views/boardView.jsp";
req.getRequestDispatcher(view).forward(req, res);
}
}
Java
복사
•
BoardServlet
package com.board.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet("/board/*")
@MultipartConfig(maxFileSize = 1024 * 1024 * 2, location = "/Users/haminsung/Documents/testimage")
public class BoardServlet extends HttpServlet {
private Map<String, BoardController> controllerMap = new HashMap<>();
// 페이지 추가
public BoardServlet() {
controllerMap.put("/board/boardList", new ListController());
controllerMap.put("/board/boardView", new ViewController());
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String requestURI = req.getRequestURI();
BoardController controller = controllerMap.get(requestURI);
if (controller == null);
controller.process(req, resp);
}
}
Java
복사
서비스
•
BoardService
package com.board.service;
import com.board.model.Board;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
// 서비스
public interface BoardService {
// 작성글 추가
void addWrite(HttpServletRequest req) throws Exception;
// 작성글 전체 보기
List<Board> getAll() throws Exception;
// 특정 작성글 보기
Board getWrite(HttpServletRequest req) throws Exception;
// 작성글 삭제
void deleteWrite(HttpServletRequest req) throws Exception;
// 작성글 수정
void modifyWrite(HttpServletRequest req) throws Exception;
}
Java
복사
•
BoardServiceImpl
package com.board.service;
import com.board.dao.BoardDAO;
import com.board.dao.BoardDAOImpl;
import com.board.model.Board;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.util.List;
public class BoardServiceImpl implements BoardService {
BoardDAO boardDAO = new BoardDAOImpl();
@Override
public void addWrite(HttpServletRequest req) throws Exception {
// 제목, 내용, 작성자, 이미지
String title = req.getParameter("title");
String content = req.getParameter("content");
String author = req.getParameter("author");
System.out.println("제목과 내용, 작성자 : " + title + ", " + content + ", " + author);
// img 가져오기
Part part = req.getPart("img");
String header = part.getHeader("content-disposition");
System.out.println(header);
int start = header.indexOf("filename=");
String img = header.substring(start + 10, header.length() - 1);
// img 저장
if (img != null && !img.isEmpty()) {
part.write(img);
}
// Board 객체 생성
Board board = new Board(title, content, author, img);
// DAO에 요청하여 DB에 Insert하기
boardDAO.addWrite(board);
}
@Override
public List<Board> getAll() throws Exception {
return boardDAO.getAll();
}
@Override
public Board getWrite(HttpServletRequest req) throws Exception {
int id = Integer.parseInt(req.getParameter("id"));
return boardDAO.getWrite(id);
}
@Override
public void deleteWrite(HttpServletRequest req) throws Exception {
int id = Integer.parseInt(req.getParameter("id"));
boardDAO.deleteWrite(id);
}
@Override
public void modifyWrite(HttpServletRequest req) throws Exception {
// id값 가져오기
int id = Integer.parseInt(req.getParameter("id"));
Board board = boardDAO.getWrite(id);
// 요청 파라미터를 가지고 Board 객체 수정
String title = req.getParameter("title");
String content = req.getParameter("content");
board.setTitle(title);
board.setContent(content);
// img 가져오기
Part part = req.getPart("img");
String header = part.getHeader("content-disposition");
System.out.println(header);
int start = header.indexOf("filename=");
String img = header.substring(start + 10, header.length() - 1);
// img 저장
if (img != null && !img.isEmpty()) {
part.write(img);
board.setImg(img);
}
// DAO에 접근
boardDAO.modifyWrite(board);
}
}
Java
복사
DAO
•
BoardDAO
package com.board.dao;
import com.board.model.Board;
import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.util.List;
// DAO (Data Access Object)
public interface BoardDAO {
// 작성글 추가
void addWrite(Board board) throws Exception;
// 작성글 전체 보기
List<Board> getAll() throws Exception;
// 특정 작성글 보기
Board getWrite(int id) throws Exception;
// 특정 작성글 삭제
void deleteWrite(int id) throws Exception;
// 특정 작성글 수정
void modifyWrite(Board board) throws SQLException;
}
Java
복사
•
BoardDAOImpl
package com.board.dao;
import com.board.model.Board;
import com.board.util.ConnectionPool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class BoardDAOImpl implements BoardDAO {
private static final String[] info = {"title", "content", "author", "img", "created_at", "updated_at"};
@Override
public void addWrite(Board board) throws Exception {
String sql = "INSERT INTO boards (title, content, author, img) VALUES (?, ?, ?, ?)";
try (
Connection conn = ConnectionPool.DBPool.getDBPool();
PreparedStatement pstmt = conn.prepareStatement(sql);
) {
pstmt.setString(1, board.getTitle());
pstmt.setString(2, board.getContent());
pstmt.setString(3, board.getAuthor());
pstmt.setString(4, board.getImg());
pstmt.executeUpdate();
}
}
@Override
public List<Board> getAll() throws Exception {
String sql = "SELECT * FROM boards";
List<Board> boardList = new ArrayList<>();
try (
Connection conn = ConnectionPool.DBPool.getDBPool();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
) {
while (rs.next()) {
int id = rs.getInt("id");
String title = rs.getString(info[0]);
String content = rs.getString(info[1]);
String author = rs.getString(info[2]);
String img = rs.getString(info[3]);
String created_at = rs.getString(info[4]);
Board boards = new Board(id, title, content, author, img, created_at);
boardList.add(boards);
}
}
return boardList;
}
@Override
public Board getWrite(int id) throws Exception {
String sql = "SELECT * FROM boards WHERE id = ?";
Board board = null;
try (
Connection conn = ConnectionPool.DBPool.getDBPool();
PreparedStatement pstmt = conn.prepareStatement(sql);
) {
pstmt.setInt(1, id);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String title = rs.getString(info[0]);
String content = rs.getString(info[1]);
String author = rs.getString(info[2]);
String img = rs.getString(info[3]);
String created_at = rs.getString(info[4]);
String updated_at = rs.getString(info[5]);
board = new Board(id, title, content, author, img, created_at, updated_at);
}
}
return board;
}
@Override
public void deleteWrite(int id) throws Exception {
String sql = "DELETE FROM boards WHERE id = ?";
try (
Connection conn = ConnectionPool.DBPool.getDBPool();
PreparedStatement pstmt = conn.prepareStatement(sql);
) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
}
}
@Override
public void modifyWrite(Board board) throws SQLException {
String sql = "UPDATE boards SET title = ?, content = ?, author = ?, img = ?, updated_at = NOW() WHERE id = ?";
try (
Connection conn = ConnectionPool.DBPool.getDBPool();
PreparedStatement pstmt = conn.prepareStatement(sql);
) {
pstmt.setString(1, board.getTitle());
pstmt.setString(2, board.getContent());
pstmt.setString(3, board.getAuthor());
pstmt.setString(4, board.getImg());
// pstmt.setString(5, board.getUpdated_at());
pstmt.setInt(5, board.getId());
pstmt.executeUpdate();
}
}
}
Java
복사
JSP
•
BoardList.jsp
<%--
Created by IntelliJ IDEA.
User: haminsung
Date: 8/8/24
Time: 9:35 AM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<title>미니 게시판</title>
<link href="${pageContext.request.contextPath}/style.css" rel="stylesheet">
<link href="${pageContext.request.contextPath}/starter-template.css" rel="stylesheet">
<link href="${pageContext.request.contextPath}/custom.css" rel="stylesheet">
</head>
<body>
<nav th:fragment="fragment-nav" class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand">Java JSP 미니 게시판</a>
<div class="collapse navbar-collapse" id="navbarsExampleDefault_admin">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/board/boardList">게시판 목록</a>
</li>
</ul>
</div>
</nav>
</br></br>
<div class="container w-75 mt-5 mx-auto">
<h2 class="text-center mb-4">게시판 목록</h2>
<hr/>
<c:choose>
<c:when test="${not empty boardList}">
<ul class="list-group mb-4">
<c:forEach var="board" items="${boardList}" varStatus="b">
<li class="list-group-item list-group-item-action
d-flex justify-content-between
align-items-center">
<a href="/board/boardView?id=${board.getId()}">[${b.count}] ${board.getTitle()}</a>
<div>
<span>${board.getCreated_at()}</span>
<!-- x 버튼에 url 담아줌 -->
<a href="/board/boardList?action=deleteWrite&id=${board.getId()}">
<span class="badge bg-secondary"> × </span>
</a>
</div>
</li>
</c:forEach>
</ul>
</c:when>
<c:otherwise>
<div class="no-news-msg">작성된 글이 없습니다.</div>
</c:otherwise>
</c:choose>
<c:if test="${error != null}">
<div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
에러 발생 : ${error}
<button class="btn-close" data-bs-dismiss="alert"></button>
</div>
</c:if>
<button class="btn btn-outline-success collapse-button"
data-bs-toggle="collapse" data-bs-target="#addForm"
aria-expanded="false" aria-controls="addForm">
게시글 작성
</button>
<div class="collapse" id="addForm">
<br/>
<div class="card card-body">
<form action="/board/boardList?action=addWrite"
method="post" enctype="multipart/form-data">
<label for="title" class="form-label">제목</label>
<input id="title" name="title" class="form-control" required/>
<br/>
<label for="content" class="form-label">글 내용</label>
<textarea id="content" rows="5" cols="50" name="content" class="form-control" required></textarea>
<br/>
<label for="author" class="form-label">작성자</label>
<input id="author" name="author" class="form-control" required/>
<br/>
<label for="img" class="form-label">이미지</label>
<input id="img" type="file" name="img" class="form-control" required/>
<button class="btn btn-success mt-3">저장</button>
</form>
</div>
</div>
</div>
</body>
</html>
HTML
복사
•
BoardView.jsp
<%--
Created by IntelliJ IDEA.
User: haminsung
Date: 8/8/24
Time: 9:35 AM
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>글 보기</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<!-- Custom styles for this template -->
<link href="${pageContext.request.contextPath}/starter-template.css" rel="stylesheet">
<link href="${pageContext.request.contextPath}/custom.css" rel="stylesheet">
<script>
function updateAlarm() {
alert("수정되었습니다.");
}
</script>
</head>
<body>
<nav fragment="fragment-nav" class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand">Java JSP 미니 게시판</a>
<div class="collapse navbar-collapse" id="navbarsExampleDefault_admin">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/board/boardList">게시판 목록</a>
</li>
</ul>
</div>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
</div>
</nav>
<div class="container mt-5 mx-auto">
<table class="table table-striped" style="text-align: center; border: 1px solid #dddddd;">
<thead>
<tr>
<th colspan="2" style="background-color: #eeeeee;
text-align: center;"><h3>${board.getAuthor()} 의 게시글</h3></th>
</tr>
</thead>
<tbody>
<tr>
<td>글 제목</td>
<td>${board.getTitle()}</td>
</tr>
<tr>
<td>작성자</td>
<td>${board.getAuthor()}</td>
</tr>
<tr>
<td>작성일자</td>
<td>${board.getCreated_at()}</td>
</tr>
<tr>
<td>수정일자</td>
<td>${board.getUpdated_at()}</td>
</tr>
<tr>
<td>내용</td>
<td>${board.getContent()}</td>
</tr>
</tbody>
</table>
<br/>
<h5>첨부 이미지</h5>
<div class="card mx-auto">
<img src="/img/${board.getImg()}" alt="게시글 이미지"/>
</div>
<br/>
<a href="javascript:history.back()" class="btn btn-dark">메인으로</a>
<!-- 수정하기 버튼 만들어서 토글 형태로 수정 양식 나타나게 하기 -->
<button class="btn btn-primary"
data-bs-toggle="collapse" data-bs-target="#modifyForm"
aria-expanded="false" aria-controls="modifyForm">
글 수정
</button>
<form action="/board/boardList?action=deleteWrite" method="post"
class="d-inline" onsubmit="return confirm('삭제하시겠습니까?')">
<input type="hidden" name="id" value="${board.getId()}"/>
<button class="btn btn-danger">삭제</button>
</form>
</div>
<div class="collapse" id="modifyForm">
<br/>
<div class="card card-body">
<form action="/board/boardView?action=modifyWrite"
method="post" enctype="multipart/form-data" >
<input type="hidden" name="id" value="${board.getId()}"/>
<label for="title" class="form-label">제목</label>
<input id="title" name="title" class="form-control" value="${board.getTitle()}" required/>
<br/>
<label for="content" class="form-label">글 내용</label>
<textarea id="content" rows="5" cols="50" name="content" class="form-control"
value="${board.getContent()}" required></textarea>
<br/>
<label for="author" class="form-label">작성자</label>
<input id="author" name="author" class="form-control" value="${board.getAuthor()}" required/>
<br/>
<label for="img" class="form-label">이미지</label>
<input id="img" type="file" name="img" class="form-control"/>
<button class="btn btn-success mt-3" onclick="updateAlarm()">수정하기</button>
</form>
</div>
</div>
</body>
</html>
Java
복사
CSS
•
custom.css
@import url(http://fonts.googleapis.com/earlyaccess/nanumgothic.css);
@import url(http://fonts.googleapis.com/earlyaccess/hanna.css);
* {
font-family: 'Nanum Gothic';
}
h1 {
font-family: Hanna;
}
CSS
복사
•
starter-template.css
body {
padding-top: 5rem;
}
.starter-template {
padding: 3rem 1.5rem;
text-align: center;
}
CSS
복사
•
style.css
@charset "UTF-8";
.no-news-msg {
background-color: lightyellow;
color: black;
padding: 10px;
border: 1px solid gray;
border-radius: 5px;
text-align: center;
margin-top: 20px;
}
CSS
복사