336 lines
10 KiB
Markdown
336 lines
10 KiB
Markdown
물론이죠! 아래는 **스프링 부트(Spring Boot)**에서의 **파일 업로드**, **멀티 파일 업로드**, **파일 다운로드**를 다루는 실무 중심의 설명 글입니다.
|
|
|
|
---
|
|
|
|
# ✅ 스프링 부트에서 파일 업로드 및 다운로드 구현하기
|
|
|
|
현대 웹 애플리케이션에서는 사용자가 이미지를 업로드하거나 문서를 다운로드하는 기능이 필수입니다. 스프링 부트는 이러한 기능을 손쉽게 구현할 수 있도록 다양한 API를 제공합니다.
|
|
이 글에서는 다음을 다룹니다:
|
|
|
|
- 단일 파일 업로드
|
|
- 다중(멀티) 파일 업로드
|
|
- 파일 다운로드
|
|
- 실무에서 주의할 점
|
|
|
|
---
|
|
|
|
## 📁 1. 의존성 설정 (`build.gradle` 또는 `pom.xml`)
|
|
스프링 부트에서는 기본적으로 `spring-boot-starter-web`만으로도 파일 업로드/다운로드가 가능합니다.
|
|
|
|
**Gradle 예시:**
|
|
```groovy
|
|
dependencies {
|
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📤 2. 단일 파일 업로드
|
|
|
|
### ✅ 컨트롤러 예시
|
|
```java
|
|
@RestController
|
|
@RequestMapping("/files")
|
|
public class FileUploadController {
|
|
|
|
private final Path uploadDir = Paths.get("uploads");
|
|
|
|
public FileUploadController() throws IOException {
|
|
Files.createDirectories(uploadDir);
|
|
}
|
|
|
|
@PostMapping("/upload")
|
|
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
|
|
if (file.isEmpty()) return ResponseEntity.badRequest().body("파일이 비어 있습니다");
|
|
|
|
Path targetPath = uploadDir.resolve(file.getOriginalFilename());
|
|
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
|
|
|
|
return ResponseEntity.ok("업로드 성공: " + file.getOriginalFilename());
|
|
}
|
|
}
|
|
```
|
|
|
|
### 🔍 설명
|
|
- `@RequestParam("file")`: HTML `<input type="file">`의 name과 일치해야 합니다.
|
|
- `MultipartFile`: 업로드된 파일을 표현하는 객체입니다.
|
|
- `Files.copy(...)`: 서버의 특정 디렉토리에 저장.
|
|
|
|
---
|
|
|
|
## 📤 3. 멀티 파일 업로드
|
|
|
|
### ✅ 컨트롤러 예시
|
|
```java
|
|
@PostMapping("/upload-multiple")
|
|
public ResponseEntity<String> uploadMultiple(@RequestParam("files") List<MultipartFile> files) throws IOException {
|
|
if (files.isEmpty()) return ResponseEntity.badRequest().body("업로드된 파일이 없습니다.");
|
|
|
|
for (MultipartFile file : files) {
|
|
if (!file.isEmpty()) {
|
|
Path targetPath = uploadDir.resolve(file.getOriginalFilename());
|
|
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
|
|
}
|
|
}
|
|
|
|
return ResponseEntity.ok("멀티 파일 업로드 성공");
|
|
}
|
|
```
|
|
|
|
### 🔍 HTML에서 보내는 방법
|
|
```html
|
|
<form method="post" enctype="multipart/form-data" action="/files/upload-multiple">
|
|
<input type="file" name="files" multiple>
|
|
<button type="submit">업로드</button>
|
|
</form>
|
|
```
|
|
|
|
---
|
|
|
|
## 📥 4. 파일 다운로드
|
|
|
|
### ✅ 컨트롤러 예시
|
|
```java
|
|
@GetMapping("/download/{filename}")
|
|
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException {
|
|
Path filePath = uploadDir.resolve(filename);
|
|
if (!Files.exists(filePath)) {
|
|
return ResponseEntity.notFound().build();
|
|
}
|
|
|
|
Resource resource = new UrlResource(filePath.toUri());
|
|
|
|
return ResponseEntity.ok()
|
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
|
|
.body(resource);
|
|
}
|
|
```
|
|
|
|
### 🔍 주요 포인트
|
|
- `UrlResource`: 파일을 다운로드 가능한 형태로 변환
|
|
- `Content-Disposition`: 파일이 브라우저에 표시되지 않고 다운로드되도록 설정
|
|
|
|
---
|
|
|
|
## ⚠️ 실무에서 주의할 점
|
|
|
|
### 1. 파일 이름 중복 처리
|
|
- `UUID`를 붙이거나 시간 기반으로 처리해야 파일 덮어쓰기 방지 가능
|
|
```java
|
|
String safeName = UUID.randomUUID() + "_" + file.getOriginalFilename();
|
|
```
|
|
|
|
### 2. 저장 경로 보안
|
|
- 사용자 입력을 통한 경로 조작 공격 방지 (`../` 등)
|
|
- 반드시 경로를 정규화 (`normalize()`)하고 업로드 디렉토리 밖으로 벗어나지 않도록 체크
|
|
|
|
```java
|
|
Path normalized = uploadDir.resolve(file.getOriginalFilename()).normalize();
|
|
if (!normalized.startsWith(uploadDir)) {
|
|
throw new SecurityException("잘못된 경로입니다");
|
|
}
|
|
```
|
|
|
|
### 3. 용량 제한 설정
|
|
**application.properties**
|
|
```properties
|
|
spring.servlet.multipart.max-file-size=10MB
|
|
spring.servlet.multipart.max-request-size=20MB
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ 마무리
|
|
|
|
| 기능 | 구현 여부 |
|
|
|-----------------|-----------|
|
|
| 단일 파일 업로드 | ✅ |
|
|
| 멀티 파일 업로드 | ✅ |
|
|
| 파일 다운로드 | ✅ |
|
|
|
|
스프링 부트에서는 `MultipartFile`, `Resource`, `ResponseEntity` 등의 기본 컴포넌트를 통해 파일 업로드/다운로드 기능을 쉽게 구현할 수 있습니다. 다만 실무에서는 보안, 파일 크기 제한, 파일명 중복 등의 다양한 상황을 고려해야 합니다.
|
|
|
|
---
|
|
|
|
필요하다면 `AWS S3`, `FTP`, 혹은 DB BLOB 저장 방식도 활용 가능하며, 이 글은 **로컬 저장소 기반**의 기본 구현을 중심으로 설명했습니다.
|
|
더 확장하고 싶은 부분이 있으면 언제든지 말해주세요!
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
좋습니다! 이번엔 **파일을 데이터베이스에 BLOB(Binary Large Object)** 형태로 저장하고, 다시 꺼내서 다운로드하는 방식을 다룬 글을 정리해드릴게요.
|
|
|
|
---
|
|
|
|
# 🗃️ 스프링 부트에서 파일을 DB(BLOB)로 업로드 및 다운로드하기
|
|
|
|
웹 애플리케이션에서 파일을 **데이터베이스(BLOB 컬럼)**에 직접 저장하는 방식은 파일 보안이나 백업, 이식성 측면에서 유리할 수 있습니다. 이 글에서는 다음을 다룹니다:
|
|
|
|
- DB에 파일 업로드 (BLOB 저장)
|
|
- DB에서 파일 다운로드
|
|
- 실제 구현 예시 (Spring Boot + JPA + H2/MySQL)
|
|
|
|
---
|
|
|
|
## 📦 1. 테이블 설계 (DDL 예시)
|
|
|
|
### ✅ MySQL 예시
|
|
```sql
|
|
CREATE TABLE file_data (
|
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
file_name VARCHAR(255),
|
|
content_type VARCHAR(100),
|
|
data LONGBLOB
|
|
);
|
|
```
|
|
|
|
> `data` 컬럼이 실제 파일의 바이트 데이터를 저장하는 BLOB 필드입니다.
|
|
|
|
---
|
|
|
|
## 🧱 2. 엔터티 클래스 정의
|
|
|
|
```java
|
|
import jakarta.persistence.*;
|
|
|
|
@Entity
|
|
@Table(name = "file_data")
|
|
public class FileData {
|
|
|
|
@Id
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
private Long id;
|
|
|
|
private String fileName;
|
|
|
|
private String contentType;
|
|
|
|
@Lob
|
|
@Column(columnDefinition = "LONGBLOB")
|
|
private byte[] data;
|
|
|
|
// 생성자, Getter/Setter 생략
|
|
}
|
|
```
|
|
|
|
- `@Lob` 어노테이션은 `byte[]` 데이터를 BLOB으로 매핑합니다.
|
|
- `contentType`은 다운로드할 때 MIME 타입을 알려주기 위해 사용합니다.
|
|
|
|
---
|
|
|
|
## 📂 3. 리포지토리 인터페이스
|
|
|
|
```java
|
|
import org.springframework.data.jpa.repository.JpaRepository;
|
|
|
|
public interface FileDataRepository extends JpaRepository<FileData, Long> {
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📤 4. 파일 업로드 (DB 저장)
|
|
|
|
```java
|
|
@RestController
|
|
@RequestMapping("/files")
|
|
public class FileDataController {
|
|
|
|
private final FileDataRepository fileDataRepository;
|
|
|
|
public FileDataController(FileDataRepository repo) {
|
|
this.fileDataRepository = repo;
|
|
}
|
|
|
|
@PostMapping("/upload")
|
|
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
|
|
if (file.isEmpty()) return ResponseEntity.badRequest().body("빈 파일입니다");
|
|
|
|
FileData fileData = new FileData();
|
|
fileData.setFileName(file.getOriginalFilename());
|
|
fileData.setContentType(file.getContentType());
|
|
fileData.setData(file.getBytes());
|
|
|
|
fileDataRepository.save(fileData);
|
|
|
|
return ResponseEntity.ok("파일 업로드 성공: ID = " + fileData.getId());
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📥 5. 파일 다운로드 (DB에서 BLOB 조회)
|
|
|
|
```java
|
|
@GetMapping("/download/{id}")
|
|
public ResponseEntity<byte[]> downloadFile(@PathVariable Long id) {
|
|
return fileDataRepository.findById(id)
|
|
.map(file -> ResponseEntity.ok()
|
|
.contentType(MediaType.parseMediaType(file.getContentType()))
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFileName() + "\"")
|
|
.body(file.getData()))
|
|
.orElse(ResponseEntity.notFound().build());
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 6. Postman 또는 HTML 테스트 예시
|
|
|
|
### ✅ HTML 업로드 폼
|
|
```html
|
|
<form method="post" enctype="multipart/form-data" action="/files/upload">
|
|
<input type="file" name="file">
|
|
<button type="submit">업로드</button>
|
|
</form>
|
|
```
|
|
|
|
### ✅ 다운로드 링크 예시
|
|
```html
|
|
<a href="/files/download/1">파일 다운로드</a>
|
|
```
|
|
|
|
---
|
|
|
|
## ⚠️ 실무에서 주의할 점
|
|
|
|
### 1. 성능 문제
|
|
- BLOB 방식은 대용량 파일 처리 시 성능 저하가 발생할 수 있음
|
|
→ **작은 이미지나 문서 위주에 적합**
|
|
|
|
### 2. DB 백업 크기 증가
|
|
- 파일이 많아지면 DB 백업/복원 시간이 길어질 수 있음
|
|
|
|
### 3. 파일 크기 제한 설정
|
|
```properties
|
|
spring.servlet.multipart.max-file-size=10MB
|
|
spring.servlet.multipart.max-request-size=20MB
|
|
```
|
|
|
|
### 4. 파일 다운로드 속도
|
|
- 서버가 파일을 전부 로드해야 하므로, 큰 파일은 **스트리밍 방식 처리**를 고려할 것
|
|
|
|
---
|
|
|
|
## ✅ 정리
|
|
|
|
| 기능 | 구현 방식 |
|
|
|------------------|-----------|
|
|
| 파일 업로드 | `MultipartFile` → BLOB 저장 |
|
|
| 파일 다운로드 | `byte[]` → HTTP 응답 |
|
|
| 확장성/보안 | 파일 시스템보다 안전하나 대용량엔 부적합 가능 |
|
|
|
|
---
|
|
|
|
## 🔚 마무리
|
|
|
|
파일을 **데이터베이스 BLOB에 저장하는 방식**은 외부 파일 시스템 없이 관리가 간편하고, 트랜잭션 처리와 보안이 뛰어납니다. 단, 대용량 파일이 많을 경우에는 성능과 저장소 이슈를 고려하여 **S3, 파일 시스템 저장 방식**과 함께 혼합하여 쓰는 것이 좋습니다.
|
|
|
|
---
|
|
|
|
필요하다면 **썸네일 저장, 이미지 미리보기 API, 스트리밍 다운로드**까지 확장도 가능합니다. 원하시면 다음 단계도 이어서 도와드릴게요! |