여러 개의 이미지를 한 번에 넘겨받아 저장해야 하는 경우 멀티파트(Multipart)로 처리해야 한다.
이미지 파일 업로드 기능 구현
상품에 대한 정보와 이미지들을 넘겨받아 DB에 저장하고, 로컬 경로로 이미지 파일을 업로드하는 예제
엔티티 생성
Product 엔티티
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
private String name;
private int price;
@OneToMany(mappedBy = "product")
List<ProductImage> productImageList = new ArrayList<>();
}
ProductImage 엔티티
public class ProductImage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
private String url;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_idx")
private Product product;
}
Product 컨트롤러
MultipartFile 로 파일들을 받아오게 설정한다.
상품에 대한 Request Dto와 이미지 파일들을 @RequestPart 를 사용해서 받는다.
@PostMapping("/create")
public ResponseEntity<ProductDto.ProductRes> create(
@RequestPart ProductDto.CreateReq dto,
@RequestPart MultipartFile[] files) {
ProductDto.ProductRes response = productService.create(dto, files);
return ResponseEntity.ok(response);
}
Product 서비스
받아온 상품 정보 DTO를 DB에 저장하고, 이미지들을 서버 컴퓨터에 업로드한다.
서버 컴퓨터에 업로드 후, 업로드 경로(이미지 저장 정보)를 DB에 저장한다.
@RequiredArgsConstructor
@Service
public class ProductService {
private final ProductRepository productRepository;
private final ImageService imageService;
private final ProductImageService productImageService;
public ProductDto.ProductRes create(ProductDto.CreateReq dto, MultipartFile[] files) {
// 상품 정보 DB에 저장
Product product = productRepository.save(dto.toEntity());
// 이미지를 서버 컴퓨터에 저장
List<String> uploadFilePaths = imageService.upload(files);
// 이미지 저장 정보를 DB에 저장
productImageService.create(uploadFilePaths, product);
return ProductDto.ProductRes.of(product);
}
}
ImageService 인터페이스
로컬 서버에 이미지 파일을 업로드하는 방식만 사용하면 인터페이스로 구현할 필요가 없다.
하지만 이미지 파일 업로드 요청이 많아지게 되면, 서버의 로컬 디스크에 의존하기 때문에 디스크 I/O가 증가해서 서버 부하가 심해질 수 있고, 서버의 CPU 및 메모리 사용량이 증가하여 성능 저하가 발생할 수 있다.
또한, 로컬 서버의 저장 공간이 한정적이므로 많은 이미지 파일들이 업로드될 경우 저장 공간이 꽉 차게 되어 서비스 장애가 발생할 수도 있다.
=> 이러한 문제를 해결하기 위해 클라우드 서버로 파일을 업로드하는 방식으로 쉽게 확장할 수 있도록 ImageService를 인터페이스로 구현한다.
public interface ImageService {
public List<String> upload(MultipartFile[] files);
}
application.yml에 파일 경로 추가
이미지 파일을 업로드해줄 경로를 설정해준다.
project:
upload:
path: c:/upload
LocalImageService
makeDir
- 파일을 저장할 디렉토리를 생성해주고, 경로를 반환해준다.
upload
- 파일들을 받아와서 업로드 경로를 반환해준다.
- 이때, 파일 이름이 같을 수도 있으니 파일 이름 앞에 랜덤값을 추가해준다.
@Service
public class LocalImageService implements ImageService {
@Value("${project.upload.path}")
private String defaultUploadPath;
// 디렉토리 생성
private String makeDir() {
String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String uploadPath = defaultUploadPath + File.separator + date;
File uploadDir = new File(defaultUploadPath + File.separator + date);
if(!uploadDir.exists()) {
uploadDir.mkdirs();
}
return uploadPath;
}
@Override
public List<String> upload(MultipartFile[] files) {
List<String> uploadFilePaths = new ArrayList<>();
String uploadPath = makeDir();
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
// 파일 이름이 같을 수도 있으니 앞에 랜덤값 추가해주기
String uploadFilePath = uploadPath+"/"+ UUID.randomUUID().toString()+"_"+originalFilename;
uploadFilePaths.add(uploadFilePath);
File uploadFile = new File(defaultUploadPath+"/"+uploadFilePath);
try {
file.transferTo(uploadFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return uploadFilePaths;
}
}
ProductImageService
받아온 이미지 경로들을 저장해주고, Product와 같이 ProductImage DB에 저장해준다.
@RequiredArgsConstructor
@Service
public class ProductImageService {
private final ProductImageRepository productImageRepository;
public void create(List<String> uploadFilePaths, Product product) {
for(String uploadFilePath: uploadFilePaths) {
productImageRepository.save(
ProductImage.builder()
.url(uploadFilePath)
.product(product)
.build())
;
}
}
}
파일 업로드 요청 테스트
postman으로 요청을 보낸다.
Body - form-data로 dto에 JSON 형식으로 입력하고, 파일들을 업로드한다.
경로에 저장이 잘 되는 것을 확인할 수 있다.
ProductImage 테이블에도 url과 product_idx가 저장된 걸 확인할 수 있다!
'BE > Spring Boot' 카테고리의 다른 글
[Spring Boot] 웹 소켓(Web Socket)을 사용해 실시간 채팅 구현 (0) | 2025.03.17 |
---|---|
[Spring Boot] 자주 쓰는 라이브러리 정리 (build.gradle) (0) | 2025.03.13 |
[Spring Boot] OAuth2 소셜 로그인 (카카오, 구글) (0) | 2025.03.01 |
[Spring Boot] 유효성 검사(Validation) (0) | 2025.02.27 |
[Spring Boot] 환경변수 설정 (IntelliJ에서 설정) (0) | 2025.02.25 |