학습 목표
- commons-fileupload2 라이브러리의 구조와 역할 이해
- multipart/form-data 방식의 파일 업로드 처리 과정 파악
- 파일명 중복 방지를 위한 고유 파일명 생성 방법 학습
- 파일 업로드와 DB 저장의 트랜잭션 처리 방법 이해
- 실제 프로젝트에서의 파일 업로드 구현 과정 체험
<commons-fileupload2 라이브러리 개요>
- 라이브러리 구성 요소
commons-fileupload2는 파일 업로드를 처리하기 위한 Apache Commons 프로젝트의 라이브러리다. 주요 구성 요소는 다음과 같다:
// 필요한 라이브러리들
commons-fileupload2-core-2.0.0-M4.jar // 멀티파트 요청 처리 기능
commons-fileupload2-jakarta-2.0.0-M1.jar // Jakarta Servlet 지원
commons-io-2.20.0.jar // 파일 I/O 스트림 처리
- multipart/form-data 방식의 필요성
기본적인 form 전송 시 인코딩 타입은 application/x-www-form-urlencoded이다. 이 방식은 모든 데이터를 문자열로 인코딩해서 한 줄의 텍스트로 전달한다. 하지만 파일 업로드 시에는 파일의 바이너리 형태 데이터가 URL 인코딩 방식으로 변경되면서 데이터가 너무 커지고, 이 과정에서 파일이 손상될 수 있다.
<!-- 파일 업로드를 위한 form 설정 -->
<form action="${pageContext.request.contextPath}/insert.bo"
method="post"
enctype="multipart/form-data">
<input type="file" name="upfile">
</form>
<파일 업로드 처리 과정>
- multipart 요청 검증
서버에서 multipart 요청인지 먼저 확인한다.
// multipart 요청인지 검증
if(JakartaServletFileUpload.isMultipartContent(request)) {
// 파일 업로드 처리 로직
}
- 파일 크기 제한 설정
서버 리소스 보호를 위해 파일 크기와 전체 요청 크기를 제한한다.
// 파일 용량 제한 설정
int fileMaxSize = 1024 * 1024 * 50; // 개별 파일 50MB
int requestMaxSize = 1024 * 1024 * 60; // 전체 요청 60MB
- 파일 저장 경로 설정
업로드된 파일을 저장할 물리적 경로를 설정한다.
// 파일 저장 경로 설정
String savePath = request.getServletContext()
.getRealPath("/resources/board-file/");
<FileItem 객체를 통한 데이터 처리>
- DiskFileItemFactory와 JakartaServletFileUpload 설정
파일을 임시로 저장하는 객체와 HTTP 요청을 파싱하는 객체를 생성한다.
// DiskFileItemFactory: 파일을 임시로 저장하는 객체
DiskFileItemFactory factory = DiskFileItemFactory.builder().get();
// JakartaServletFileUpload: HTTP 요청 파싱 객체
JakartaServletFileUpload upload = new JakartaServletFileUpload(factory);
upload.setFileSizeMax(fileMaxSize);
upload.setSizeMax(requestMaxSize);
- 요청 데이터 파싱 및 분류
전달받은 데이터를 파싱하여 일반 폼 필드와 파일을 구분한다.
// 요청 데이터 파싱
List<FileItem> formItems = upload.parseRequest(request);
for(FileItem item : formItems) {
if(item.isFormField()) {
// 일반 폼 필드 처리
switch(item.getFieldName()) {
case "category":
int categoryNo = Integer.parseInt(
item.getString(Charset.forName("UTF-8")));
b.setCategoryNo(categoryNo);
break;
case "title":
b.setBoardTitle(item.getString(Charset.forName("UTF-8")));
break;
case "content":
b.setBoardContent(item.getString(Charset.forName("UTF-8")));
break;
}
} else {
// 파일 처리
String originName = item.getName();
// 파일 처리 로직...
}
}
<고유 파일명 생성 및 파일 저장>
- 파일명 중복 방지 전략
동일한 파일명이 업로드될 경우 덮어쓰기를 방지하기 위해 고유한 파일명을 생성한다.
if(originName.length() > 0) {
// 고유 파일명 생성
String tmpName = "kh_" + System.currentTimeMillis() +
((int)(Math.random() * 100000) + 1);
String type = originName.substring(originName.lastIndexOf("."));
String changeName = tmpName + type;
// 파일 저장
File f = new File(savePath, changeName);
item.write(f.toPath());
// Attachment 객체 생성
at = new Attachment();
at.setOriginName(originName);
at.setChangeName(changeName);
at.setFilePath("resources/board-file/");
}
실행 결과:
원본 파일명: document.pdf
변경된 파일명: kh_17030012345678901234.pdf
<DB 저장 및 트랜잭션 처리>
- Attachment VO 클래스 구조
파일 정보를 저장하기 위한 VO 클래스를 정의한다.
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Attachment {
private int fileNo;
private int refBoardNo;
private String originName; // 원본 파일명
private String changeName; // 변경된 파일명
private String filePath; // 파일 경로
private Date uploadDate;
private int fileLevel;
private String status;
}
- 게시글과 첨부파일 동시 저장
게시글과 첨부파일을 하나의 트랜잭션으로 처리하여 데이터 일관성을 보장한다.
// BoardService의 insertBoard 메서드
public int insertBoard(Board b, Attachment at) {
Connection conn = getConnection();
BoardDao bDao = new BoardDao();
// 1. 게시글 먼저 저장
int result = bDao.insertBoard(conn, b);
// 2. 첨부파일이 있으면 저장
if(at != null) {
result *= bDao.insertAttachment(conn, at);
}
// 3. 트랜잭션 처리
if(result > 0) {
commit(conn);
} else {
rollback(conn);
}
close(conn);
return result;
}
- SQL 매퍼 설정
게시글과 첨부파일 저장을 위한 SQL 쿼리를 정의한다.
<!-- 게시글 저장 -->
<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>
<에러 처리 및 롤백>
- 실패 시 파일 삭제 처리
DB 저장이 실패할 경우 이미 업로드된 파일을 삭제하여 불필요한 파일이 남지 않도록 한다.
int result = new BoardService().insertBoard(b, at);
if(result > 0) {
// 성공 시
session.setAttribute("alertMsg", "일반게시글 작성 성공");
response.sendRedirect(request.getContextPath() + "/list.bo");
} else {
// 실패 시 업로드된 파일 삭제
if(at != null){
new File(savePath + at.getChangeName()).delete();
}
request.setAttribute("errorMsg", "일반게시글 작성 실패");
request.getRequestDispatcher("views/common/error.jsp")
.forward(request, response);
}
'Servlet' 카테고리의 다른 글
| JSP/Servlet_09) Ajax를 사용한 비동기 방식 (0) | 2025.10.16 |
|---|---|
| JSP/Servlet_08) Pagination 게시글 페이징 처리 (0) | 2025.10.15 |
| Servlet_06) 웹 애플리케이션 세션 관리와 보안 강화 (0) | 2025.10.09 |
| Servlet_05) Lombok과 Factory Method 패턴 (0) | 2025.10.08 |
| Servlet_04) JSP/Servlet을 활용한 MVC 패턴 (0) | 2025.10.02 |