0. 개요
- 이전 포스팅에서 구현한 파일 업로드 방식은 "백엔드에 파일 저장 + DB에 경로 저장" 이다.
- 이번 포스팅에서 구현하는 방식은 "변환한 파일을 DB에 직접 저장" 이다.
1. 업로드 방식
- 변환한 파일을 DB에 직접 저장할 것이다.
- 변환 방식은 'BLOB' 와 'Base64' 가 있다.
- 두가지 방식 모두 구현할 것이다.
2. 구현
- Entity
[FileBlob.java]
package study.imageHandlerTest.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Entity
@Getter
@Setter
public class FileBlob {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(nullable = false)
private String fileName;
@Column(nullable = false)
private String contentType;
@Lob //스프링 부트가 String / char 이 아니면 알아서 BLOB 타입으로 설정
@Column(columnDefinition = "LONGBLOB", nullable = false)
private byte[] data; //BLOB 필드
@Column(nullable = false)
private long size; // 파일 크기
@CreationTimestamp
private Timestamp createDate;
}
- @Lob 어노테이션
: DB의 컬럼 타입인 BLOB / CLOB를 매핑하기 위해 표현
data 속성은 DB에서 BLOB 타입의 컬럼으로 매핑되어야 한다.
• CLOB: String, char[], java.sql.CLOB
• BLOB: byte[], java.sql. BLOB
여기서 data 속성은 'byte[]' 타입이므로 DB에서 'BLOB' 타입으로 매핑된다.
DB Table을 살펴보면 data 필드가 'BLOB' 타입인 것을 확인할 수 있다.
[FileBase64.java]
package study.imageHandlerTest.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
@Entity
@Getter
@Setter
public class FileBase64 {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(nullable = false)
private String fileName;
@Column(nullable = false)
private String contentType;
@Lob //스프링 부트가 String / char 이 아니면 알아서 BLOB 타입으로 설정
@Column(nullable = false, columnDefinition = "LONGTEXT") //Base64 문자열이 크므로 LONGTEXT 지정
private String base64Data; //BLOB 필드
@Column(nullable = false)
private long size; // 파일 크기
@CreationTimestamp
private Timestamp createDate;
}
- @Column 의 'columnDefinition = "LONGTEXT" ' 속성
: Base64 필드에 저장할 문자열의 크기가 매우 크기 때문에 많은 메모리를 할당하기 위해 지정
- Repository
[FileBlobRepository.java]
[FileBase64Repository.java]
- JPA 를 이용하여 구현
- Service
[DBUploadService.java]
package study.imageHandlerTest.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.imageHandlerTest.entity.FileBase64;
import study.imageHandlerTest.entity.FileBlob;
import study.imageHandlerTest.repository.FileBase64Repository;
import study.imageHandlerTest.repository.FileBlobRepository;
@Service
@Transactional(readOnly=true)
@RequiredArgsConstructor
public class DBUploadService {
private final FileBlobRepository fileBlobRepository;
private final FileBase64Repository fileBase64Repository;
/**
* 저장 로직
*/
@Transactional
public FileBlob saveBlob(String fileName, String contentType, byte[] data) {
FileBlob fileBlob = new FileBlob();
fileBlob.setFileName(fileName);
fileBlob.setContentType(contentType);
fileBlob.setData(data);
fileBlob.setSize(data.length);
return fileBlobRepository.save(fileBlob);
}
@Transactional
public FileBase64 saveBase64(String fileName, String contentType, String data64Data) {
FileBase64 fileBase64 = new FileBase64();
fileBase64.setFileName(fileName);
fileBase64.setContentType(contentType);
fileBase64.setBase64Data(data64Data);
fileBase64.setSize(data64Data.length());
return fileBase64Repository.save(fileBase64);
}
/**
* 파일 불러오기
*/
public FileBlob getBlobById(Long id) {
return fileBlobRepository.findById(id).orElse(null);
}
public FileBase64 getBase64ById(Long id) {
return fileBase64Repository.findById(id).orElse(null);
}
}
- Controller
[DBUploadController.java]
package study.imageHandlerTest.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import study.imageHandlerTest.entity.FileBase64;
import study.imageHandlerTest.entity.FileBlob;
import study.imageHandlerTest.service.DBUploadService;
import java.io.IOException;
import java.util.Base64;
@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
public class DBUploadController {
private final DBUploadService dbUploadService;
/**
* BLOB 방식으로 업로드
*/
@PostMapping("/upload/blob")
public ResponseEntity<FileBlob> uploadBlob(@RequestParam("file") MultipartFile file) {
try {
//파일 데이터를 바이너리 형식으로 저장
FileBlob fileBlob = dbUploadService.saveBlob(
file.getOriginalFilename(),
file.getContentType(),
file.getBytes()
);
return ResponseEntity.status(HttpStatus.CREATED).body(fileBlob);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* Base64 방식으로 업로드
*/
@PostMapping("/upload/base64")
public ResponseEntity<FileBase64> uploadBase64(@RequestParam("file") MultipartFile file) {
try {
//파일 데이터를 Base64로 인코딩
String base64Data = Base64.getEncoder().encodeToString(file.getBytes());
FileBase64 fileBase64 = dbUploadService.saveBase64(
file.getOriginalFilename(),
file.getContentType(),
base64Data
);
return ResponseEntity.status(HttpStatus.CREATED).body(fileBase64);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* Health check 또는 테스트용 엔드포인트
*/
@GetMapping("/health")
public ResponseEntity<String> healthCheck() {
return ResponseEntity.ok("Image upload API is running");
}
/**
* BLOB -> file 변환
*/
@GetMapping("/view/blob/{id}")
public ResponseEntity<byte[]> viewBlob(@PathVariable Long id) {
FileBlob fileBlob = dbUploadService.getBlobById(id);
if (fileBlob == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok()
.contentType(org.springframework.http.MediaType.parseMediaType(fileBlob.getContentType()))
.body(fileBlob.getData());
}
/**
* Base64 -> file 변환
*/
@GetMapping("/view/base64/{id}")
public ResponseEntity<byte[]> viewBase64(@PathVariable Long id) {
FileBase64 fileBase64 = dbUploadService.getBase64ById(id);
if (fileBase64 == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
byte[] decodedData = Base64.getDecoder().decode(fileBase64.getBase64Data());
return ResponseEntity.ok()
.contentType(org.springframework.http.MediaType.parseMediaType(fileBase64.getContentType()))
.body(decodedData);
}
}
- Front - end
[index.html]
<form action="/api/files/upload/blob" method="POST" enctype="multipart/form-data">
<h2>BLOB 를 사용해서 DB에 저장</h2>
<div class="mb-3">
<label for="file-blob-title" class="form-label">파일 제목</label>
<input type="text" class="form-control" id="file-blob-title" name="title" required>
</div>
<div class="mb-3">
<label for="file-blob-file" class="form-label">파일 업로드</label>
<input type="file" class="form-control" id="file-blob-file" name="file" required>
</div>
<button type="submit" class="btn btn-primary">업로드</button>
</form>
<form action="/api/files/upload/base64" method="POST" enctype="multipart/form-data">
<h2>BASE64 형태로 DB에 저장</h2>
<div class="mb-3">
<label for="file-base64-title" class="form-label">파일 제목</label>
<input type="text" class="form-control" id="file-base64-title" name="title" required>
</div>
<div class="mb-3">
<label for="file-base64-file" class="form-label">파일 업로드</label>
<input type="file" class="form-control" id="file-base64-file" name="file" required>
</div>
<button type="submit" class="btn btn-primary">업로드</button>
</form>
3. 작동시현
1. 웹 서비스 화면
2. BLOB 방식 이용하여 업로드
'BLOB' 방식으로 'tiger.jpg' 파일을 업로드한다.
DB에 파일이 'data' 필드에 BLOB 타입으로 채워진 것을 확인 할 수 있다.
ID : 1 로서 존재한다.
"http://localhost:8080/api/files/view/blob/1" 을 통해 BLOB -> FILE 변환내용을 확인한다.
3. Base64 방식 이용하여 업로드
'Base64' 방식으로 'wombat.jpg' 파일을 업로드한다.
DB에 파일이 'base64data' 필드에 String 타입으로 채워진 것을 확인 할 수 있다.
ID : 1 로서 존재한다.
"http://localhost:8080/api/files/view/base64/1" 을 통해 base64 -> FILE 변환내용을 확인한다.
4. 마무리
이 방식의 구현한 의의는 실전에서 실용적으로 사용하기 위함 이라기 보다, 이러한 방식을 구현하는 그 자체에 있다.
왜냐하면, 이 방식은 실용적이지 않다. DB에 파일을 직접 저장하는 방식은 DB에 많은 저장공간을 할당해야 하며, 다시 파일을 불러오는 경우에도 많은 리소스를 사용하게 된다.
따라서 파일 업로드를 위한 방식은 "백엔드에 파일 저장 + DB에 경로 저장" 혹은, "클라우드 서비스를 이용" 을 사용하는 것이 가장 효율적이라고 생각한다.
BLOB / Base64 를 이용하여 DB에 직접적으로 파일을 저장하는 방식을 구현해보면서 파일 저장 기능 구현에 능숙해 질 수 있었고, 이에 자신감이 생겼다.
효율적인 방식이 아니더라도 굳이 구현해 보면서 동작 방식에 대한 사고를 할 수 있는 좋은 계기가 되었다.
만족스러웠다!
'Spring' 카테고리의 다른 글
[SpringBoot - 파일 다루기] 4. 기존 게시판 프로젝트에 '파일 업로드' 기능 추가하기 (완) (2) | 2025.01.29 |
---|---|
[SpringBoot - 파일 다루기] 3. 동영상 파일(MP4) 다루기 (0) | 2025.01.25 |
[SpringBoot - 파일 다루기] 2. 업로드한 이미지 파일 웹에서 확인하기 (1) | 2025.01.23 |
[SpringBoot - 파일 다루기] 1. 이미지 파일 업로드 및 저장하기 (1) | 2025.01.21 |
[SpringBoot - 파일 다루기] 0. 프로젝스 생성 및 설정 (0) | 2025.01.19 |