數據庫存儲文件並且進行索引

1.概述

在構建某種內容管理解決方案時,我們需要解決兩個問題。我們需要一個地方來存儲文件本身,並且需要某種數據庫來對它們進行索引。

可以將文件的內容存儲在數據庫本身中,或者我們可以將內容存儲在其他位置並用數據庫建立索引。

在本文中,我們將通過基本的Image Archive Application演示這兩種方法。我們還將實現用於上傳和下載的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

 }

id字段用@GeneratedValue註釋。這意味著數據庫將為我們添加的每條記錄創建一個唯一的標識符。通過使用這些值索引圖像,我們無需擔心同一圖像的多次上傳會相互衝突。

其次,我們有Hibernate @Lob批註。這就是我們告訴JPA我們打算存儲潛在的大型二進製文件的方式

3.2 Image Repository

接下來,我們需要一個存儲庫以連接到數據庫

我們將使用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作為其響應的主體。

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 "[email protected]" 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.內容和位置分開

到目前為止,我們已經能夠在數據庫中上載和下載圖像。

另一個不錯的選擇是將文件內容上傳到其他位置。然後,我們僅將其文件系統location**保存**在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();

 }

 }

一個重要的注意事項–我們需要確保我們的每個圖像在上傳時都在服務器端定義了唯一的location 。否則,我們的上傳內容可能會相互覆蓋。

相同的規則適用於任何云存儲,我們應該在其中創建唯一的密鑰。在此示例中,我們將以毫秒格式將當前日期添加到圖像名稱:

/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();

 }

 }

在這裡,我們**使用其location**查找圖像。然後,我們返回FileSystemResource

另外,我們正在捕獲讀取文件時可能發生的任何異常。我們可能還希望拋出具有特定HTTP狀態的異常。

7.3 數據流和Spring的資源

我們的findInFileSystem方法返回FileSystemResource ,這是Spring的Resource接口的實現。

僅當我們使用文件時,它才會開始讀取文件。在我們的例子中,將是通過RestController將其發送到客戶端的時候。同樣,它將把文件內容從文件系統流傳輸到用戶,從而避免了將所有字節加載到內存中的麻煩

這種方法是將文件流式傳輸到客戶端的良好通用解決方案。如果使用的是雲存儲而不是文件系統,則可以將FileSystemResource替換為另一個資源的實現,例如InputStreamResourceByteArrayResource

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();

 }

 }

首先,我們將圖像保存在文件系統中。然後,將包含其location的記錄保存在數據庫中

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 "[email protected]" http://localhost:8080/file-system/image



 1

然後我們下載:

curl -v http://localhost:8080/file-system/image/1 -o image.jpeg

11.結論

在本文中,我們學習瞭如何將文件信息保存在數據庫中,文件內容位於同一行或位於外部位置。

我們還使用分段上傳來構建和測試REST API,並使用Resource提供了下載功能,以允許將文件流式傳輸到調用方。