數據庫存儲文件並且進行索引
- java
- Hibernate
- JPA
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
替換為另一個資源的實現,例如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();
}
}
首先,我們將圖像保存在文件系統中。然後,將包含其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
提供了下載功能,以允許將文件流式傳輸到調用方。