[ 파일 업로드 ]
1. 파일 업로드 설정 파일
1) pom.xml : 라이브러리 의존성
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
commons-fileupload : multipart 요청 데이터를 파싱해서 업로드 파일과 일반 파라미터를 분리해주는 라이브러리
HTTP request body에 업로드할 데이터가 담기는데, 이를 직접 구분자로 나누어 처리하지 않고 commons-fileupload 라이브러리가 multipart 형식을 파싱해 파일 데이터와 일반 데이터를 분리해줌
https://commons.apache.org/
2) servlet-context.xml : MultipartResolver 빈 설정
MultipartResolver 빈 정의시 속성 지정
id는 반드시 multipartResolver로 설정해야 함
preserveFilename은 보통 false로 두는 게 안전
속성
타입
의미
defaultEncoding
String
요청 데이터의 문자 인코딩 (기본: ISO-8859-1)
maxUploadSize
long
업로드 가능한 최대 파일 크기 (-1이면 제한 없음)
maxInMemorySize
int
파일을 디스크 저장 전 메모리에 보관할 최대 크기
preserveFilename
boolean
업로드 시 원본 파일명(경로까지포 함한)유지 여부(기본값은 false)
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 요청 데이터 문자 인코딩 (기본값: ISO-8859-1) -->
<property name="defaultEncoding" value="UTF-8" />
<!-- 업로드 가능한 최대 파일 크기: 10MB (-1이면 제한 없음) -->
<property name="maxUploadSize" value="10485760" />
<!-- 디스크 저장 전 메모리에 보관할 최대 크기: 1MB -->
<property name="maxInMemorySize" value="1048576" />
<!-- 원본 파일명(경로 포함) 유지 여부 -->
<property name="preserveFilename" value="false" />
</bean>
2. DB 테이블과 DTO
1) HTML의 <form> 설정
파일을 업로드하는 입력 폼을 만들 때 태그에 enctype="multipart/form-data” 속성을 추가해야함
method="post" : 파일 데이터는 반드시 POST 방식으로 전송
enctype="multipart/form-data" : 파일을 포함한 데이터를 전송할 때 반드시 설정. 없으면 파일 데이터가 제대로 전송되지 않음
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="text" name="category" placeholder="카테고리 입력"/>
<input type="file" name="file"/>
<button type="submit">업로드</button>
</form>
2) DB의 File 테이블
원본 파일명이랑 서버에 저장할 파일명(UUID 기반 등) 을 분리하려면 컬럼을 하나 더 두는걸 권장
java.util.UUID : 고유한 식별자 값을 만들 때 쓰는 Java 클래스
FILE_SIZE : 파일 크기 정보 표시, 다운로드 용량 안내, 업로드 제한 체크 등에 활용 가능
BLOB : 파일 데이터를 DB에 직접 저장할 때 사용(Oracle의 BLOB은 최대 4GB까지 저장 가능)
CREATE TABLE UPLOAD_FILE (
FILE_ID NUMBER(10) PRIMARY KEY,
CATEGORY_NAME VARCHAR2(260) DEFAULT '/', -- 폴더로 보이게 할 수 있음
FILE_NAME VARCHAR2(260) NOT NULL, -- 원본 파일명
UUID_FILE_NAME VARCHAR2(100) NOT NULL, -- 서버 저장용 파일명
FILE_SIZE NUMBER(10),
FILE_CONTENT_TYPE VARCHAR2(255),
FILE_UPLOAD_DATE TIMESTAMP NOT NULL,
FILE_DATA BLOB
);
3) DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "fileData") // 포함하면 오래걸림
public class UploadFileDto {
private Long fileId;
private String categoryName;
private String fileName; // 원본 파일명
private String uuidFileName; // 서버 저장용 파일명
private Long fileSize; // 파일 크기
private String fileContentType; // MIME 타입
private Timestamp fileUploadDate;
private byte[] fileData; // BLOB 데이터
}
3. Controller
1) DB에 저장할때
@PostMapping("/file/upload")
public String upString(String category, MultipartFile file) {
String fileName = file.getOriginalFilename(); // 파일 원래 이름
String fileExt = fileName.substring(fileName.lastIndexOf(".")); // 확장자
UUID uuid = UUID.randomUUID();
String uuidFileName = uuid + fileExt;
try {
if (file != null && !file.isEmpty()) {
UploadFile newFile = new UploadFile();
newFile.setCategoryName(category);
newFile.setFileName(file.getOriginalFilename());
newFile.setUuidFileName(uuidFileName);
newFile.setFileSize(file.getSize());
newFile.setFileContentType(file.getContentType());
newFile.setFileData(file.getBytes());
uploadFileService.uploadFile(newFile);
}
} catch (Exception e) {
log.error(e.getMessage());
}
return "redirect:/file/list";
}
2) 서버 특정 디렉토리에 저장할때
// 파일 업로드
@PostMapping("/file/upload")
public String uploadFile(String category, MultipartFile file) {
log.info("{}, {}", category, file.getOriginalFilename());
String uploadDir = "C:/dev/upload";
log.info("Upload dir: {}", uploadDir);
try {
if (file != null && !file.isEmpty()) {
String fileName = file.getOriginalFilename();
String fileExt = fileName.substring(fileName.lastIndexOf("."));
UUID uuid = UUID.randomUUID();
String uuidFileName = uuid + fileExt;
File saveFilePath = new File(uploadDir, uuidFileName);
byte[] fileData = file.getBytes();
// FileOutputStream으로 실제 파일을 로컬 디렉토리에 저
try (FileOutputStream fos = new FileOutputStream(saveFilePath)) {
fos.write(fileData);
}
UploadFile newFile = new UploadFile();
newFile.setCategoryName(category);
newFile.setFileName(file.getOriginalFilename());
newFile.setUuidFileName(uuidFileName);
newFile.setFileSize(file.getSize());
newFile.setFileContentType(file.getContentType());
uploadFileService.uploadFile(newFile);
}
} catch (Exception e) {
log.error(e.getMessage());
}
return "redirect:/file/list";
}
2) Multipartfile 인터페이스
클라이언트가 업로드한 파일 정보를 서버에서 받기 위한 인터페이스
주로 <input type="file">로 전송된 파일을 Spring에서 받을 때 사용
파일명, 크기, 타입 조회와 파일 데이터 읽기 등의 기능을 제공
메서드
반환 타입
설명
getName()
String
form 태그의 파라미터 이름 반환
getOriginalFilename()
String
업로드한 원본 파일명 반환
getContentType()
String
파일의 MIME 타입 반환
getSize()
long
파일 크기(byte) 반환
isEmpty()
boolean
업로드된 파일이 비어있는지 확인
getBytes()
byte[]
파일 데이터를 바이트 배열로 반환
getInputStream()
InputStream
파일 데이터를 스트림으로 읽음
transferTo(File dest)
void
업로드 파일을 지정한 파일로 저장
transferTo(Path dest)
void
업로드 파일을 지정한 경로에 저장
3) MIME 타입 (Multipurpose Internet Mail Extensions)
타입/서브타입
MIME 주요 타입
의미
text/plain
일반 텍스트
text/html
HTML 문서
image/png
PNG 이미지
image/jpeg
JPG 이미지
application/pdf
PDF 파일
application/json
JSON 데이터
application/octet-stream
알 수 없는 바이너리
[ 파일 다운로드 ]
서버에 저장된 파일을 클라이언트에게 응답으로 내려주는 것
다운로드 시에는 응답 헤더 설정이 중요함
Spring에서는 ResponseEntity<byte[]>와 HttpHeaders를 사용해 구현 가능
1. Controller
@GetMapping("/download/{fileId}")
public ResponseEntity<byte[]> downloadFile(@PathVariable intfileId) throws Exception {
UploadFileDto fileDto = fileService.selectFile(fileId);
String encodedFileName = URLEncoder.encode(fileDto.getFileName(), "UTF-8")
.replaceAll("\\\\+", "%20");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(fileDto.getFileContentType()));
headers.setContentLength(fileDto.getFileSize());
headers.set("Content-Disposition", "attachment; filename=\\"" + encodedFileName + "\\"");
return new ResponseEntity<>(fileDto.getFileData(), headers, HttpStatus.OK);
}
1) HttpHeaders 클래스
HTTP 응답 헤더 정보를 저장하는 클래스
MultiValueMap<String, String> 구현체로, 헤더를 key-value 형태로 저장.
헤더
의미
Content-Type
파일의 MIME 타입
Content-Disposition
다운로드 여부, 파일명 지정
Content-Length
파일 크기
Content-Disposition 값
의미
attachment
파일을 다운로드하도록 처리
inline
브라우저에서 바로 열기 (이미지나 PDF 미리보기에 활용)
2) 파일명 인코딩 (한글 깨짐 방지)
파일명에 한글/공백이 있으면 깨질 수 있음
URLEncoder로 인코딩해서 사용
공백이 +로 인코딩되므로 %20으로 교체해줘야 함
StringencodedFileName=URLEncoder.encode(fileName,"UTF-8")
.replaceAll("\\\\+","%20");
⚠️ web.xml의 encodingFilter는 HTTP 요청을 인코딩하는 설정이고, 파일명 인코딩은 별도로 처리해야 함.
3) 핸들러 메서드 리턴객체
파일 다운로드시 ResponseEntity<T> 객체 반환
생성자
status code
header
body
ResponseEntity(HttpStatus status)
O
X
X
ResponseEntity(MultiValueMap<String,String> headers, HttpStatus status)
O
O
X
ResponseEntity(T body, HttpStatus status)
O
X
O
ResponseEntity(T body, MultiValueMap<String,String> headers, HttpStatus status)
O
O
O
(1) MultiValueMap
하나의 키에 여러 개의 값을 넣을 수 있는 Map.
HTTP 헤더는 같은 이름으로 값이 여러 개 들어갈 수 있어서 MultiValueMap을 많이 사용함
// MultiValueMap → 키 하나에 값 여러 개 가능
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Accept", "text/html");
headers.add("Accept", "application/json");
// Accept: [text/html, application/json]