종식당
현대오토에버 모빌리티 SW 스쿨 웹/앱 스프링부트 파일 업로드 본문
이번 시간에는 스프링 부트를 통해 파일 업로드하는 것을 간단하게 알아보려 한다. 이미지를 업로드하는 과정을 알아보겠다.
파일 구조
간단하게라도 다양한 프로젝트를 만들어보면서 배운 점은 아직 view쪽은 구성하지 않았지만 MVC패턴을 사용할 때 먼저 Entity를 구성한다. 다음으로 repository, service, controller를 구성하면 좀 편하고 자연스럽게 구성할 수 있는 것 같다.
service에서 repository에 있는 crud기능을 사용해서 로직 처리를 하고 이를 또 controller에서 요청에 따라 로직을 호출하니까 이 순서가 꽤나 자연스럽다.
그럼 위 순서대로 넘어가면서 보겠다. 먼저 Entity이다.
Entity
package com.sample.spring.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "filedata")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FileEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private String filePath;
}
@GeneratedValue(strategy = GenerationType.IDENTITY)는 JPA에서 기본 키(primary key) 값을 자동으로 생성할 때 사용하는 어노테이션이다.
Repository
package com.sample.spring.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.sample.spring.domain.FileEntity;
public interface FiledDataRepository extends JpaRepository<FileEntity, Long> {
}
JpaRepository<FileEntity, Long>: JpaRepository를 상속받아 FileEntity 엔티티와 연관된 CRUD(Create, Read, Update, Delete) 작업을 자동으로 제공해 준다.
JpaRepository <T, ID>:
- JpaRepository<T, ID>는 Spring Data JPA에서 제공하는 인터페이스로, JPA를 사용한 데이터베이스 작업을 간단하게 처리할 수 있게 해 준다.
- T: 엔티티 클래스 타입 (FileEntity).
- ID: 엔티티의 기본 키 타입 (Long).
Service
package com.sample.spring.service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.sample.spring.domain.FileEntity;
import com.sample.spring.repository.FiledDataRepository;
import lombok.extern.log4j.Log4j2;
@Service
@Log4j2
public class FileDataService {
private final String FOLDER_PATH = "c:\\images\\";
@Autowired
private FiledDataRepository filedDataRepository;
public String uploadImageToFileSystem(MultipartFile file) throws IOException {
log.info("upload file : " + file.getOriginalFilename());
String filePath = FOLDER_PATH + file.getOriginalFilename();
FileEntity fileData = filedDataRepository.save(FileEntity.builder().name(file.getOriginalFilename())
.type(file.getContentType()).filePath(filePath).build());
file.transferTo(new File(filePath));
if (filePath != null) {
return "file upload success!!!" + filePath;
}
return null;
}
public byte[] downLoadImageFileSystem(Long id) throws IOException {
FileEntity fileData = filedDataRepository.findById(id).orElseThrow();
String filePath = fileData.getFilePath();
log.info("download fileData : " + filePath);
return Files.readAllBytes(new File(filePath).toPath());
}
}
@Log4j2
- Lombok 어노테이션으로, log 객체를 자동으로 생성하여 로그를 쉽게 출력할 수 있게 한다.
- log.info(), log.debug(), log.error() 등의 메서드를 사용하여 로그를 남길 수 있다.
private final String FOLDER_PATH = "c:\\images\\";
먼저 파일을 저장할 디렉터리의 경로를 상수로 나타내주었다. 그리고 해당 경로에 images폴더 또한 만들어주었다.
- uploadImageToFileSystem
public String uploadImageToFileSystem(MultipartFile file) throws IOException {
log.info("upload file : " + file.getOriginalFilename());
String filePath = FOLDER_PATH + file.getOriginalFilename();
FileEntity fileData = filedDataRepository.save(FileEntity.builder()
.name(file.getOriginalFilename())
.type(file.getContentType())
.filePath(filePath)
.build());
file.transferTo(new File(filePath));
if (filePath != null) {
return "file upload success!!!" + filePath;
}
return null;
}
uploadImageToFileSystem(MultipartFile file) 메서드에서는 MultipartFile file를 인자로 받는다. 이는 클라이언트가 HTTP 요청을 통해 서버에 파일을 전송할 때 사용되며 파일 업로드를 처리하는 데 사용되는 객체라고 생각하면 된다.
주요 메서드:
- getBytes(): 파일의 내용을 byte [] 배열로 반환
- getInputStream(): 파일의 내용을 읽을 수 있는 InputStream을 반환
- getOriginalFilename(): 업로드된 파일의 원본 파일명을 반환
- isEmpty(): 파일이 비어 있는지 여부를 확인
- transferTo(File dest): 파일을 지정한 경로로 이동하거나 저장
String filePath = FOLDER_PATH + file.getOriginalFilename();
filePath에 위에서 상수값으로 저장했던 경로와 업로드하려는 파일의 원래 파일명을 더해서 저장해 준다.
FileEntity fileData = filedDataRepository.save(FileEntity.builder()
.name(file.getOriginalFilename())
.type(file.getContentType())
.filePath(filePath)
.build());
FileEntity 객체를 빌더 패턴을 사용해 생성한다. 파일의 원래 이름, 콘텐츠 유형, 파일이 저장될 경로를 매핑해서 JpaRepository의 save메서드를 이용해서 저장해 준다.
이때 save메서드의 인자는 Entity타입이 들어가야 한다.
file.transferTo(new File(filePath));
MultipartFile의 데이터를 실제 파일 시스템에 저장한다.
- downLoadImageFileSystem
public byte[] downLoadImageFileSystem(Long id) throws IOException {
FileEntity fileData = filedDataRepository.findById(id).orElseThrow();
String filePath = fileData.getFilePath();
log.info("download fileData : " + filePath);
return Files.readAllBytes(new File(filePath).toPath());
}
FileEntity fileData = filedDataRepository.findById(id).orElseThrow();
JpaRepository의 메서드 findById를 통해서 해당 id의 값으로 FileEntity를 데이터베이스에서 조회한다.
그리고 FileEntity가 존재하지 않을 경우에는 orElseThrow()를 통해서 예외를 던진다.
String filePath = fileData.getFilePath();
데이터베이스에 저장된 fileData의 경로를 가져와 filePath에 저장한다.
return Files.readAllBytes(new File(filePath).toPath());
Files.readAllBytes를 통해서 지정된 파일의 모든 바이트를 읽어서 byte [] 배열로 반환한다.
Controller
package com.sample.spring.controller;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.sample.spring.service.FileDataService;
@RestController
@RequestMapping("/api")
public class FileController {
@Autowired
public FileDataService fileDataService;
@PostMapping("/file")
public ResponseEntity<?> uploadImage(@RequestParam(name = "images") List<MultipartFile> files) {
List<String> uploadResult = files.stream().map(file -> {
try {
return fileDataService.uploadImageToFileSystem(file);
} catch (IOException e) {
e.printStackTrace();
return "Failed to upload: " + file.getOriginalFilename(); // 실패 메시지를 반환
}
}).collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.OK).body(uploadResult); // 업로드 결과를 반환
}
@GetMapping("/file/{id}")
public ResponseEntity<?> downImage(@PathVariable(name = "id") Long id) throws IOException {
byte[] downLoadImage = fileDataService.downLoadImageFileSystem(id);
if (downLoadImage != null) {
return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.valueOf("image/png")).body(downLoadImage);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
}
@PostMapping("/file")
public ResponseEntity<?> uploadImage(@RequestParam(name = "images") List<MultipartFile> files) {
ResponseEntity <?> 메서드는 ResponseEntity 객체를 반환하며 이는 HTTP 응답의 상태 코드와 본문을 포함할 수 있는 객체다. 제네릭 타입 <?>는 반환할 데이터의 타입이 다양할 수 있음을 나타낸다
이를 통해서 클라이언트에서 전송한 여러 개의 파일을 받을 수 있다.
List<String> uploadResult = files.stream().map(file -> {
파일 리스트를 스트림으로 바꾸고 이를 map을 사용해서 각각의 파일에 대해 처리한다.
기본적으로 파일을 저장하는 역할을 하고 IOException이 발생하면 오류 스택 트레이스를 출력하고 실패 메시지를 반환한다.
}).collect(Collectors.toList());
스트림에서 처리된 결과를 리스트 형태로 수집하며 이 리스트는 각 파일의 업로드 결과를 담고 있다.
return ResponseEntity.status(HttpStatus.OK).body(uploadResult); // 업로드 결과를 반환
마지막으로 업로드 결과를 반환한다.
@GetMapping("/file/{id}")
public ResponseEntity<?> downImage(@PathVariable(name = "id") Long id) throws IOException {
@PathVariable(name = "id") Long id를 통해서 http://localhost:8080/api/file/3 이런 식으로 URL경로를 요청받을 수 있다.
byte[] downLoadImage = fileDataService.downLoadImageFileSystem(id);
FileDataService 클래스의 downLoadImageFileSystem 메서드를 호출하여 주어진 ID에 해당하는 이미지를 읽어오며 이 메서드는 이미지 파일의 바이트 배열을 반환한다.
if (downLoadImage != null) {
return ResponseEntity.status(HttpStatus.OK).contentType(MediaType.valueOf("image/png")).body(downLoadImage);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
성공
- ResponseEntity.status(HttpStatus.OK): HTTP 상태 코드를 200 OK로 설정한다.
- . contentType(MediaType.valueOf("image/png")): 응답의 Content-Type을 image/png로 설정한다. 이는 클라이언트가 받을 데이터의 형식이다.
- . body(downLoadImage): 다운로드된 이미지의 바이트 배열을 응답 본문에 담아 반환한다.
실패
- ResponseEntity.status(HttpStatus.NOT_FOUND): HTTP 상태 코드를 404 NOT FOUND로 설정하고 이는 요청한 ID에 해당하는 이미지가 존재하지 않을 때 반환한다.
- . build(): 본문 없이 응답을 반환한다.
PostMan & Dbeaver를 통해 확인
서버를 실행하고 Key에 images를 적고 File형식으로 바꾼 다음에 value에서 업로드할 파일을 선택해 주었다.
성공적으로 업로드된 모습을 확인할 수 있다.
지정해 준 디렉터리 경로에도 성공적으로 저장된 모습을 확인할 수 있다.
dbeaver에서도 id : 4의 값으로 테이블에 저장된 모습을 확인할 수 있다.
Get요청을 통해 id값이 4인 파일의 데이터를 가져왔다. 성공적으로 이미지가 나오는 모습을 postman을 통해 확인할 수 있다.
정말 간단하게 이미지를 업로드하고 업로드한 이미지를 확인하는 과정을 진행해 봤는데 확실히 정리를 하니까 조금 알게 되고 어려웠다고 생각했던 내용들도 보고 나니까 쉬운 내용이었던 것 같다.
다음에도 파일 업로드 수업이 한번 더 있을 것 같은데 그때는 더 많은 내용을 다룰 것 같으니 그때 또다시 정리를 하면 좋을 것 같다.
'현대오토에버 모빌리티 sw 스쿨' 카테고리의 다른 글
현대오토에버 모빌리티 SW 스쿨 웹/앱 스프링 시큐리티 (1) | 2024.10.17 |
---|---|
현대오토에버 모빌리티 SW 스쿨 웹/앱 스프링부트 파일 업로드 2 (3) | 2024.10.16 |
현대오토에버 모빌리티 SW 스쿨 웹/앱 스프링부트 어노테이션 (1) | 2024.10.14 |
현대오토에버 모빌리티 SW 스쿨 웹/앱 데이터베이스 JDBC & MyBatis (2) | 2024.10.14 |
현대오토에버 모빌리티 SW 스쿨 웹/앱 SQL JOIN (1) | 2024.10.14 |