1. 개요

일종의 콘텐츠 관리 솔루션을 구축할 때 두 가지 문제를 해결해야 합니다. 파일 자체를 저장할 장소가 필요하고 이를 색인화할 데이터베이스가 필요합니다.

파일의 내용을 데이터베이스 자체에 저장하거나 내용을 다른 곳에 저장하고 데이터베이스와 함께 색인을 생성할 수 있습니다.

이 기사에서는 기본 이미지 아카이브 애플리케이션을 사용하여 이 두 가지 방법을 모두 설명합니다. 업로드 및 다운로드를 위한 REST API도 구현합니다.

2. 사용 사례

이미지 아카이브 애플리케이션을 사용하면 JPEG 이미지업로드 및 다운로드 할 수 있습니다 .

이미지를 업로드하면 애플리케이션에서 이미지에 대한 고유 식별자를 생성합니다. 그런 다음 이 식별자를 사용하여 다운로드할 수 있습니다.

Spring Data JPAHibernate 와 함께 관계형 데이터베이스를 사용할 것 입니다.

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 를 통해 클라이언트로 보낼 때 입니다. 또한 파일 시스템에서 사용자에게 파일 콘텐츠를 스트리밍하여 모든 바이트를 메모리에 로드하지 않도록 합니다 .

이 접근 방식은 클라이언트에 파일을 스트리밍하기 위한 좋은 일반적인 솔루션입니다. 파일 시스템 대신 클라우드 스토리지를 사용하는 경우 FileSystemResourceInputStreamResource 또는 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 에서 찾을 수 있습니다  .

Persistence footer banner