1. 개요
일종의 콘텐츠 관리 솔루션을 구축할 때 두 가지 문제를 해결해야 합니다. 파일 자체를 저장할 장소가 필요하고 이를 색인화할 데이터베이스가 필요합니다.
파일의 내용을 데이터베이스 자체에 저장하거나 내용을 다른 곳에 저장하고 데이터베이스와 함께 색인을 생성할 수 있습니다.
이 기사에서는 기본 이미지 아카이브 애플리케이션을 사용하여 이 두 가지 방법을 모두 설명합니다. 업로드 및 다운로드를 위한 REST API도 구현합니다.
2. 사용 사례
이미지 아카이브 애플리케이션을 사용하면 JPEG 이미지 를 업로드 및 다운로드 할 수 있습니다 .
이미지를 업로드하면 애플리케이션에서 이미지에 대한 고유 식별자를 생성합니다. 그런 다음 이 식별자를 사용하여 다운로드할 수 있습니다.
Spring Data JPA 및 Hibernate 와 함께 관계형 데이터베이스를 사용할 것 입니다.
3. 데이터베이스 저장
데이터베이스부터 시작하겠습니다.
3.1. 이미지 개체
먼저 Image 엔터티를 생성해 보겠습니다 .
@Entity
class Image {
@Id
@GeneratedValue
Long id;
@Lob
byte[] content;
String name;
// Getters and Setters
}
아이디 필드에 어노테이션이 @GeneratedValue . 이것은 데이터베이스가 우리가 추가하는 각 레코드에 대해 고유한 식별자를 생성함을 의미합니다. 이러한 값으로 이미지를 인덱싱하면 동일한 이미지의 여러 업로드가 서로 충돌하는 것에 대해 걱정할 필요가 없습니다.
둘째, Hibernate @Lob 어노테이션이 있습니다. 잠재적으로 큰 바이너리를 저장하려는 의도를 JPA에 알리는 방법 입니다.
3.2. 이미지 저장소
다음으로 데이터베이스에 연결할 저장소 가 필요 합니다 .
우리는 Spring JpaRepository를 사용할 것입니다 :
@Repository
interface ImageDbRepository extends JpaRepository<Image, Long> {}
이제 이미지를 저장할 준비가 되었습니다. 애플리케이션에 업로드할 방법만 있으면 됩니다.
4. REST 컨트롤러
MultipartFile 을 사용하여 이미지 를 업로드 합니다. 업로드하면 나중에 이미지를 다운로드하는 데 사용할 수 있는 imageId 가 반환 됩니다.
4.1. 이미지 업로드
업로드를 지원하는 ImageController 를 생성하여 시작하겠습니다 .
@RestController
class ImageController {
@Autowired
ImageDbRepository imageDbRepository;
@PostMapping
Long uploadImage(@RequestParam MultipartFile multipartImage) throws Exception {
Image dbImage = new Image();
dbImage.setName(multipartImage.getName());
dbImage.setContent(multipartImage.getBytes());
return imageDbRepository.save(dbImage)
.getId();
}
}
의 MultipartFile 객체는 파일의 내용과 원래의 이름이 포함되어 있습니다. 이것을 사용 하여 데이터베이스에 저장하기 위한 Image 객체 를 구성 합니다.
이 컨트롤러는 생성된 ID를 Response body으로 반환합니다.
4.2. 이미지 다운로드
이제 다운로드 경로를 추가해 보겠습니다 .
@GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE)
Resource downloadImage(@PathVariable Long imageId) {
byte[] image = imageRepository.findById(imageId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND))
.getContent();
return new ByteArrayResource(image);
}
imageId의 경로 변수는 업로드에 생성 된 ID를 포함합니다. 잘못된 ID가 제공되면 ResponseStatusException 을 사용하여 HTTP 응답 코드 404(찾을 수 없음)를 반환합니다. 그렇지 않으면 저장된 파일 바이트를 다운로드할 수 있는 ByteArrayResource 에 래핑합니다 .
5. 데이터베이스 이미지 아카이브 테스트
이제 이미지 아카이브를 테스트할 준비가 되었습니다.
먼저 애플리케이션을 빌드해 보겠습니다.
mvn package
둘째, 시작해보자:
java -jar target/image-archive-0.0.1-SNAPSHOT.jar
5.1. 이미지 업로드 테스트
애플리케이션이 실행된 후 curl 명령줄 도구 를 사용하여 이미지를 업로드합니다 .
curl -H "Content-Type: multipart/form-data" \
-F "image=@baeldung.jpeg" http://localhost:8080/image
업로드 서비스로 응답이입니다 imageId는 , 이 우리의 첫 번째 요청이고, 출력은 다음과 같습니다
1
5.2. 이미지 다운로드 테스트
그런 다음 이미지를 다운로드할 수 있습니다.
curl -v http://localhost:8080/image/1 -o image.jpeg
-o image.jpeg의 옵션라는 파일이 만들어집니다 image.jpeg을 하고 거기에 응답 콘텐츠를 저장 :
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /image/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Accept-Ranges: bytes
< Content-Type: image/jpeg
< Content-Length: 9291
우리는 HTTP/1.1 200을 얻었습니다. 이것은 다운로드가 성공했음을 의미합니다.
http://localhost:8080/image/1 을 눌러 브라우저에서 이미지를 다운로드할 수도 있습니다 .
6. 별도의 콘텐츠 및 위치
지금까지 데이터베이스 내에서 이미지를 업로드 및 다운로드할 수 있습니다.
또 다른 좋은 옵션은 파일 콘텐츠를 다른 위치에 업로드하는 것입니다. 그런 다음 파일 시스템 위치 만 DB에 저장 합니다 .
이를 위해 Image 엔터티에 새 필드를 추가해야 합니다 .
String location;
여기에는 일부 외부 저장소의 파일에 대한 논리적 경로가 포함됩니다. 우리의 경우, 그것은 우리 서버의 파일 시스템에 있는 경로가 될 것입니다.
그러나 우리는 이 아이디어를 다른 상점에 동등하게 적용할 수 있습니다. 예를 들어 Google Cloud Storage 또는 Amazon S3와 같은 클라우드 스토리지를 사용할 수 있습니다 . 위치는 URI 형식을 사용할 수도 있습니다(예 : s3://somebucket/path/to/file ) .
우리의 업로드 서비스는 파일의 바이트를 데이터베이스에 쓰는 대신 파일을 적절한 서비스(이 경우 파일 시스템)에 저장한 다음 파일의 위치를 데이터베이스에 넣습니다.
7. 파일 시스템 스토리지
파일 시스템 에 이미지 를 저장 하는 기능을 솔루션에 추가해 보겠습니다 .
7.1. 파일 시스템에 저장
먼저 이미지를 파일 시스템에 저장해야 합니다.
@Repository
class FileSystemRepository {
String RESOURCES_DIR = FileSystemRepository.class.getResource("/")
.getPath();
String save(byte[] content, String imageName) throws Exception {
Path newFile = Paths.get(RESOURCES_DIR + new Date().getTime() + "-" + imageName);
Files.createDirectories(newFile.getParent());
Files.write(newFile, content);
return newFile.toAbsolutePath()
.toString();
}
}
한 가지 중요한 사항은 각 이미지 에 업로드 시 서버 측에서 정의된 고유한 위치 가 있는지 확인해야 한다는 것입니다 . 그렇지 않으면 업로드가 서로 덮어쓸 수 있습니다.
고유 키를 생성해야 하는 모든 클라우드 스토리지에도 동일한 규칙이 적용됩니다. 이 예에서는 현재 날짜를 밀리초 형식으로 이미지 이름에 추가합니다.
/workspace/archive-achive/target/classes/1602949218879-baeldung.jpeg
7.2. 파일 시스템에서 검색
이제 파일 시스템에서 이미지를 가져오는 코드를 구현해 보겠습니다.
FileSystemResource findInFileSystem(String location) {
try {
return new FileSystemResource(Paths.get(location));
} catch (Exception e) {
// Handle access or file not found problems.
throw new RuntimeException();
}
}
여기서 우리는 위치를 사용하여 이미지를 찾고 있습니다 . 그런 다음 FileSystemResource 를 반환합니다 .
또한 파일을 읽는 동안 발생할 수 있는 모든 예외를 포착하고 있습니다. 특정 HTTP 상태에서 예외를 던지고 싶을 수도 있습니다 .
7.3. 데이터 스트리밍과 Spring의 리소스
우리 findInFileSystem의 방법은 반환 FileSystemResource , 의 구현 Spring의 자원 인터페이스를.
그것은 우리가 그것을 사용할 때만 우리의 파일을 읽기 시작할 것 입니다. 우리의 경우 RestController 를 통해 클라이언트로 보낼 때 입니다. 또한 파일 시스템에서 사용자에게 파일 콘텐츠를 스트리밍하여 모든 바이트를 메모리에 로드하지 않도록 합니다 .
이 접근 방식은 클라이언트에 파일을 스트리밍하기 위한 좋은 일반적인 솔루션입니다. 파일 시스템 대신 클라우드 스토리지를 사용하는 경우 FileSystemResource 를 InputStreamResource 또는 ByteArrayResource 와 같은 다른 리소스의 구현으로 대체할 수 있습니다 .
8. 파일 내용과 위치 연결하기
이제 FileSystemRepository 가 있으므로 ImageDbRepository 와 연결해야 합니다 .
8.1. 데이터베이스 및 파일 시스템에 저장
저장 흐름부터 시작 하여 FileLocationService 를 만들어 보겠습니다 .
@Service
class FileLocationService {
@Autowired
FileSystemRepository fileSystemRepository;
@Autowired
ImageDbRepository imageDbRepository;
Long save(byte[] bytes, String imageName) throws Exception {
String location = fileSystemRepository.save(bytes, imageName);
return imageDbRepository.save(new Image(imageName, location))
.getId();
}
}
먼저 파일 시스템에 이미지를 저장합니다 . 그런 다음 데이터베이스에 위치 가 포함된 레코드를 저장 합니다 .
8.2. 데이터베이스 및 파일 시스템에서 검색
이제 id를 사용하여 이미지를 찾는 방법을 만들어 보겠습니다 .
FileSystemResource find(Long imageId) {
Image image = imageDbRepository.findById(imageId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return fileSystemRepository.findInFileSystem(image.getLocation());
}
먼저 데이터베이스에서 이미지를 찾습니다 . 그런 다음 위치를 가져와 파일 시스템에서 가져옵니다 .
데이터베이스 에서 imageId 를 찾지 못하면 ResponseStatusException을 사용하여 HTTP Not Found 응답을 반환합니다 .
9. 파일 시스템 업로드 및 다운로드
마지막으로 FileSystemImageController를 생성해 보겠습니다 .
@RestController
@RequestMapping("file-system")
class FileSystemImageController {
@Autowired
FileLocationService fileLocationService;
@PostMapping("/image")
Long uploadImage(@RequestParam MultipartFile image) throws Exception {
return fileLocationService.save(image.getBytes(), image.getOriginalFilename());
}
@GetMapping(value = "/image/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE)
FileSystemResource downloadImage(@PathVariable Long imageId) throws Exception {
return fileLocationService.find(imageId);
}
}
먼저 "/ file-system "으로 시작하는 새 경로를 만들었습니다 .
그런 다음 ImageController 에서와 유사한 업로드 경로를 생성 했지만 dbImage 객체 는 없었습니다 .
마지막으로 FileLocationService 를 사용 하여 이미지를 찾고 FileSystemResource 를 HTTP 응답으로 반환하는 다운로드 경로가 있습니다 .
10. 파일 시스템 이미지 아카이브 테스트
이제 경로가 " file-system "으로 시작하지만 데이터베이스 버전과 동일한 방식으로 파일 시스템 버전을 테스트할 수 있습니다 .
curl -H "Content-Type: multipart/form-data" \
-F "image=@baeldung.jpeg" http://localhost:8080/file-system/image
1
그런 다음 다운로드합니다.
curl -v http://localhost:8080/file-system/image/1 -o image.jpeg
11. 결론
이 기사에서는 파일 내용을 동일한 행이나 외부 위치에 두고 데이터베이스에 파일 정보를 저장하는 방법을 배웠습니다.
또한 멀티파트 업로드를 사용하여 REST API를 구축 및 테스트했으며 리소스 를 사용하여 다운로드 기능을 제공 하여 호출자에게 파일을 스트리밍할 수 있도록 했습니다.
항상 그렇듯이 코드 샘플은 GitHub 에서 찾을 수 있습니다 .