2025-04-08T19:56:24

This commit is contained in:
2025-04-08 19:56:24 +09:00
parent a75a1dbd0f
commit eef061c1c9
100 changed files with 18639 additions and 0 deletions

336
docs/22_file upload.md Normal file
View File

@@ -0,0 +1,336 @@
물론이죠! 아래는 **스프링 부트(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, 스트리밍 다운로드**까지 확장도 가능합니다. 원하시면 다음 단계도 이어서 도와드릴게요!