0. 목적
JSP와 MySQL을 JDBC드라이버를 통해 연결한 후, 파일 업로드 / 다운로드 기능이 구현된 게시판을 만들어 보도록 한다. 게시판의 기능은 글 쓰기 / 글 열람 / 파일 업로드 / 파일 다운로드의 총 4가지 기능을 구현하도록 한다. CSS는 이용하지 않고 오직 기본 HTML 템플릿만 이용하여 구성하도록 하자.
또한, 각 게시글마다 서로다른 파일을 업로드 할 수 있으며, 사용자가 다운로드 요청 시 해당 게시글에 업로드된 파일이 정상적으로 다운로드 되어야 한다.
1. 참고
▣ 개발환경
프로그램 | 버전 |
MySQL DBMS | 8.0.31 |
Eclipse for Web Developers | 2020-06 |
Apache Tomcat | v9.0.65 |
J-Connector | 공식 홈페이지 제공 최신 버전 |
▣ DBMS 연결환경
환경 | 속성 |
포트 | 3306 |
ID | root |
PW | root |
▣ 기본 세팅이 준비되지 않았다면, 아래의 링크를 참고해 조치하도록 한다.
2. 프로젝트 기본 계획
▣ Web 파일
JSP 파일명 | 파일 설명 |
board_list.jsp | 게시글 리스트 출력 |
board_read.jsp | 게시글 상세내용 출력 (파일 다운로드 기능 포함) |
board_new.jsp | 신규 게시글 입력 (파일 업로드 기능 포함) |
board_new_send.jsp | 신규 게시글 입력 (파일 업로드 기능 포함) |
▣ Database
* num
게시글 번호로서, 게시글을 구분할 수 있게 하며AUTO_INCREMENT
속성을 부과해 게시글 작성시마다 자동으로+1
되어 삽입되도록 계획한다.
* title, content, date
각각제목
,내용
,게시글 작성 날짜
정보를 저장할 수 있도록 한다.date
의 경우,DATETIME
속성을 부과하면 DB 내용 삽입 시 자동으로 현재 날짜가 입력된다.
* file_name
서버에 파일 업로드시, 업로드를 희망했던 파일의 최초 이름을 저장하도록 한다.
* file_realName
서버에 파일 업로드시, 중복된 파일명의 경우 새 이름을 붙여 파일명이 변경되게 되는데 파일의 새 이름을 저장하도록 한다.
▣ 자바 라이브러리 파일 다운로드
파일 다운로드 기능을 구현하기 위한 자바 라이브러리 파일이다.
3. DB 생성
CREATE SCHEMA `file_board` ;
우선 SCHEMA
를 생성한다.
CREATE TABLE `file_board`.`post` (
`num` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(20) NOT NULL,
`content` TEXT NOT NULL,
`date` DATETIME NOT NULL,
`file_name` TEXT NULL,
`file_realName` TEXT NULL,
`file_route` TEXT NULL,
PRIMARY KEY (`num`));
생성한 SCHEMA
하위로, 계획했던 대로 TABLE
을 생성한다.
정상적으로 file_board.post
DB가 생성되었다.
4. Eclipse Web Project 생성
Eclipse Dynamic Web Project
를 하나 생성하고, 상기 계획했던 바와 같이 JSP
파일들을 생성하도록 한다.
file_board
> WebContent
> WEB-INF
> lib
폴더에 상기 다운로드 받았던 cos.jar
자바 라이브러리 파일을 프로젝트에서 인식할 수 있게 위치시켜 준다.
WebContent
하위에 파일이 업로드될 폴더인 upload
폴더를 하나 생성해 준다.
5. [게시글 리스트] board_list.jsp 작성
<!-- SQL 연결을 위한 import -->
<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="java.sql.DriverManager"%>
<%@page import="java.sql.Connection"%>
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>게시글 목록</title>
</head>
<body>
<!-- 게시글 목록 폼임을 표시 -->
<h1>게시글 목록</h1>
<%
try
{
// JDBC 드라이버 연결
Class.forName("com.mysql.jdbc.Driver");
String db_address = "jdbc:mysql://localhost:3306/file_board";
String db_username = "root";
String db_pwd = "root";
Connection connection = DriverManager.getConnection(db_address, db_username, db_pwd);
// MySQL로 전송하기 위한 쿼리문인 insertQuery 문자열 선언
String insertQuery = "SELECT * FROM file_board.post order by num desc";
// MySQL 쿼리문 실행
PreparedStatement psmt = connection.prepareStatement(insertQuery);
// 쿼리문을 전송해 받아온 정보를 result 객체에 저장
ResultSet result = psmt.executeQuery();%>
<!-- 게시글 목록을 표시할 기본 테이블 생성 -->
<table border="1">
<tr>
<td colspan="5">
<h3>게시글 제목 클릭시 상세 열람 가능</h3>
</td>
</tr>
<tr>
<td colspan="5">
<button type="button" value="신규 글 작성" onClick="location.href='board_new.jsp'">신규 글 작성</button>
</td>
</tr>
<tr>
<td>번호</td>
<td>제목</td>
<td>작성일</td>
</tr>
<%
// 받아온 정보를 입력하고, 하나씩 커서를 다음으로 넘김
while (result.next())
{%>
<tr>
<!-- 번호 <td> 아래에 DB에서 받아온 num 칼럼값 삽입 -->
<td><%=result.getInt("num") %></td>
<!-- 제목 <td> 아래에 DB에서 받아온 title 칼럼값 삽입, 제목 클릭시 board_read.jsp로 연결되며 num 칼럼값을 parameter로 넘김 -->
<td><a href="board_read.jsp?num=<%=result.getInt("num") %>"><%=result.getString("title") %></a></td>
<!-- 작성일 <td> 아래에 DB에서 받아온 date 칼럼값 삽입 -->
<td><%=result.getTimestamp("date") %></td>
</tr>
<%
}%>
</table>
<%
}
catch (Exception ex)
{
out.println("오류가 발생했습니다. 오류 메시지 : " + ex.getMessage());
}%>
</body>
</html>
DB에 기록된 게시글 내용들을 불러올 수 있는 코드 내용이다. 글 제목을 클릭하면, 게시글의 상세 내용을 읽을 수 있도록 코드를 작성했다. 신규 글 작성 버튼을 누르면, board_new.jsp
로 연결되어 신규 게시글을 작성할 수 있는 페이지로 이동한다.
▣ 임시 실행 화면
6. [신규 게시글 작성] board_new.jsp 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>신규 게시글 작성</title>
</head>
<body>
<!-- 게시글 작성 폼임을 표시 -->
<h1>게시글 작성</h1>
<!-- 파일 전송을 위한 multipart 선언 -->
<form method="post" action="board_new_send.jsp" enctype="multipart/form-data">
<table>
<tr>
<td><input type="text" placeholder="제목" name="title" maxlength="20" value=""></td>
</tr>
<tr>
<td><textarea placeholder="내용" name="content" maxlength="2048" style="height:150px;"></textarea></td>
</tr>
</table>
<input type="file" name="file">
<hr>
<input type="submit" value="글쓰기">
</form>
</body>
</html>
파일 전송 기능을 수행하기 위해 <form>
의 method
를 post
로 설정하고, enctype
속성을 multipart/form-data
로 선언해 주었다. multipart/form-data
는 JSP에서 파일 전송 등의 동작을 수행하기 위해 사용되는 속성값이다.
▣ 임시 실행 화면
7. [신규 게시글 작성내용 전송 / 파일 업로드 / DB기록] board_new_send.jsp 작성
<%@page import="java.sql.*"%>
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %>
<%@ page import="com.oreilly.servlet.MultipartRequest" %>
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<%
try
{
// JDBC 드라이버 연결
Class.forName("com.mysql.jdbc.Driver");
String db_address = "jdbc:mysql://127.0.0.1:3306/file_board";
String db_username = "root";
String db_pwd = "root";
Connection connection = DriverManager.getConnection(db_address, db_username, db_pwd);
// 인코딩 UTF-8 설정
request.setCharacterEncoding("UTF-8");
// 게시글 번호를 결정하기 위한 임시 정수형 변수 선언
int num = 0;
// 절대경로를 파악하기 위한 문자열 변수 선언
String uploadRoute = "";
// MySQL로 전송하기 위한 쿼리문인 insertQuery 문자열 선언 (현재 등록된 게시글의 갯수를 파악)
String insertQuery = "SELECT MAX(num) from file_board.post";
// SQL 쿼리문을 실행 (MySQL로 전송)하기 위한 객체 선언
PreparedStatement psmt = connection.prepareStatement(insertQuery);
// 조회된 결과물들을 저장하기 위한 ResultSet 객체 선언
ResultSet result = psmt.executeQuery();
// 받아온 정보가 있을때
while(result.next())
{
// 앞서 임시 선언한 num 변수에, 가져온 MAX(num) 칼럼값 + 1을 하여 저장
num = result.getInt("MAX(num)") + 1;
}
// 날짜
Timestamp today_date = new Timestamp(System.currentTimeMillis());
String uploadDir = this.getClass().getResource("").getPath();
// 파일이 업로드될 경로
uploadRoute = "file_board/WebContent/upload/";
uploadDir = uploadDir.substring(1, uploadDir.indexOf(".metadata")) + uploadRoute;
// 파일 크기 제한 byte 크기
int maxSize = 100 * 1024 * 1024;
// 인코딩 설정
String encoding = "UTF-8";
// Multipartrequest 객체 선언
MultipartRequest multipartRequest = new MultipartRequest(request, uploadDir, maxSize, encoding, new DefaultFileRenamePolicy());
String fileName = multipartRequest.getOriginalFileName("file");
String fileRealName = multipartRequest.getFilesystemName("file");
String title = multipartRequest.getParameter("title");
String content = multipartRequest.getParameter("content");
// MySQL로 전송하기 위한 쿼리문인 insertQuery 문자열 선언 (사용자가 신규 게시글 작성한 정보를 전송)
insertQuery = "INSERT INTO file_board.post(num, title, content, date, file_name, file_realName, file_route) VALUES (?, ?, ?, ?, ?, ?, ?)";
// SQL 쿼리문을, 새로운 내용을 토대로 재실행
psmt = connection.prepareStatement(insertQuery);
// VALUES ? 값에 하나씩 삽입하여 전송
psmt.setInt(1, num);
psmt.setString(2, title);
psmt.setString(3, content);
psmt.setTimestamp(4, today_date);
psmt.setString(5, fileName);
psmt.setString(6, fileRealName);
psmt.setString(7, "/file_board/upload/" + fileRealName);
// INSERT 하여 반영된 레코드의 건수결과를 반환
psmt.executeUpdate();
// 게시글 목록으로 redirect
response.sendRedirect("board_list.jsp");
}
catch (Exception ex)
{
out.println("오류가 발생했습니다. 오류 메시지 : " + ex.getMessage());
}
%>
파일 업로드를 구현하기 위한 MultipartRequest
의 실행 로직이다.
MultipartRequest
의 객체를 선언하며 인자값은 아래와 같이 부여했다.
인자값 | 지정 |
HttpServletRequest | request |
업로드된 파일이 저장될 위치 | uploadDir (* 본 프로젝트에서는 \file_board\upload) |
파일 제한 최대 크기 | 100 * 1024 * 1024 |
파일 인코딩 | UTF-8 |
FileRenamePolicy (동일 파일명 처리 방식) | new DefaultFileRenamePolicy() |
new DefaultFileRenamePolicy()
는, test.txt
파일을 업로드 할 시 동일한 파일명이 존재한다면 test1.txt
로 숫자를 붙여 파일을 저장하는 것을 의미한다.
서버에 파일을 전송하고 저장하기 위해서는 WebContent
폴더를 인식시켜야 한다.
하지만, 서버에서 파일을 내려받을 경로에서는, 즉 DB에 route
를 저장할 때에는 WebContent
폴더 경로를 생략해야만 다운로드 링크가 작동된다. 이에 유의하도록 하자.
8. [게시글 열람 / 파일 다운로드] board_read.jsp 작성
<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 열람</title>
</head>
<body>
<%
try
{
//JDBC 드라이버 연결
Class.forName("com.mysql.jdbc.Driver");
String db_address = "jdbc:mysql://127.0.0.1:3306/file_board";
String db_username = "root";
String db_pwd = "root";
Connection connection = DriverManager.getConnection(db_address, db_username, db_pwd);
// 문자열 인코딩 설정
request.setCharacterEncoding("UTF-8");
// 파라미터값을 통해 넘어온 게시글 번호를 문자열 변수에 저장
String num = request.getParameter("num");
// DB에 전송할 쿼리문 설정
String insertQuery = "SELECT * FROM file_board.post WHERE num=" + num;
// 쿼리문 실행
PreparedStatement psmt = connection.prepareStatement(insertQuery);
// 받아온 정보를 result 객체에 저장
ResultSet result = psmt.executeQuery();%>
<table border="1">
<%
// 받아온 정보가 있다면...
while(result.next())
{%>
<tbody>
<tr>
<td style="width: 20%;">글 제목</td>
<td colspan="2"><%=result.getString("title") %></td>
</tr>
<tr>
<td>작성 일자</td>
<td colspan="2"><%=result.getTimestamp("date") %></td>
</tr>
<tr>
<td>내용</td>
<td colspan="2" style="height: 200px; text-align: left;"><%=result.getString("content") %></td>
</tr>
<tr>
<td>첨부파일</td>
<td colspan="2">
<%
// 만약, 첨부파일이 null 이라면... == 첨부파일이 없는 것
if (result.getString("file_route").equals("/file_board/upload/null"))
{%>
첨부파일이 없습니다.
<%
}
else
{%>
<a style="color: blue; text-decoration: underline;" href="<%=result.getString("file_route") %>"><%=result.getString("file_realName") %></a>
<%
}%>
</td>
</tr>
</tbody>
</table>
<%} %>
<a href="board_list.jsp">목록</a>
<%
}
catch(Exception ex)
{
out.print(ex.getMessage());
}%>
</body>
</html>
게시글을 열람하기 위한 코드를 작성했다.
첨부파일이 없다면, DB에 저장된 file_route
의 경로가 /file_board/upload/null
일 것이다. 이를 판별해서, 게시글에 첨부파일이 있는지 없는지 여부를 판단할 수 있는 로직을 구현했다.
첨부파일이 있을 경우, 파일명이 출력되며 클릭 시 다운로드가 이루어진다.
첨부파일이 없을 경우, 첨부파일이 없습니다.
라는 문구가 출력된다.
▣ 임시 실행 화면 (첨부파일이 없을 경우)
▣ 임시 실행 화면 (첨부파일이 있을 경우)
9. 최종 작동 모습
10. 소스코드 다운로드
상기 폴더를 내려받아 Eclipse 프로젝트에 적용시키면 된다.
동시에, 본 소스코드는 필자의 GitHub에서도 다운로드가 제공된다. 상기 링크를 참고하여 다운로드 받을 수 있다.