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