一、前言和開發環境及配置
可以轉載,但請註明出處。
之前自己寫的SpringBoot整合MongoDB的聚合查詢操作,感興趣的可以點選查閱。
https://www.cnblogs.com/zaoyu/p/springboot-mongodb.html
使用mongodb儲存檔案並實現讀取,透過springboot整合mongodb操作。
可以有兩種實現方式:
1. 單個檔案小於16MB的,可以直接把檔案轉成二進位制或者使用如Base64編碼對檔案做編碼轉換,以二進位制或者string格式存入mongodb。
讀取時,把二進位制資料或者string資料轉成對應的IO流或做解碼,再返回即可。
2. 對於單個檔案大於16MB的,可以使用mongodb自帶的GridFS
開發環境、工具:JDK1.8,IDEA 2021
Springboot版本:2.7.5
Mongodb依賴版本:4.6.1
SpringBoot的配置 application.properties 如下
# 應用名稱 spring.application.name=demo #server.port=10086 不配置的話,預設8080 # springboot下mongoDB的配置引數。 spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 # 指定資料庫庫名 spring.data.mongodb.database=temp
二、實現步驟和程式碼
1. 小檔案儲存
1.1 說明和限制
由於MongoDB限制單個文件大小不能超過16MB,所以這種方式僅適用於單個檔案小於16MB的。
如果傳入大於16MB的檔案,儲存是會失敗的,報錯如下。
1.2 實現程式碼
package com.onepiece.mongo; import org.bson.Document; import org.bson.types.Binary; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import java.io.*; import java.util.Base64; import java.util.List; /** * @author zaoyu * @description 用於演示Mongodb的檔案儲存(單個檔案不大於16MB) */ @SpringBootTest public class MongoSaveFiles { @Autowired private MongoTemplate mongoTemplate; // collection名 private String IMAGE_COLLECTION = "image"; // 原始檔完整路徑 private String FILE_PATH = "D:\\temp\\onepiece.jpg"; // 輸出檔案路徑 private String FILE_OUTPUT_PATH = "C:\\Users\\onepiece\\Desktop\\"; // 限制16MB private Long FILE_SIZE_LIMIT = 16L * 1024L * 1024L; @Test public void saveFiles(){ byte[] fileContent = null; FileInputStream fis = null; try { File file = new File(FILE_PATH); long length = file.length(); // 校驗檔案大小,大於16MB返回。 這裡的操作邏輯依據你自己業務需求調整即可。 if (length >= FILE_SIZE_LIMIT) { System.out.println("檔案: " + file.getAbsolutePath() + " 超出單個檔案16MB的限制。" ); return; } fileContent = new byte[(int) file.length()]; fis = new FileInputStream(file); // 讀取整個檔案 fis.read(fileContent); // 把檔案內容以二進位制格式寫入到mongodb Document document = new Document(); // fileName欄位、content欄位自定義。 document.append("fileName", file.getName()); document.append("content", new Binary(fileContent)); Document insert = mongoTemplate.insert(document, IMAGE_COLLECTION); System.out.println("檔案 " + file.getName() + " 已存入mongodb,對應ID是: " + insert.get("_id").toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 測試讀取並寫入到指定路徑。 */ @Test public void readAndWriteFiles(){ // 這裡也是,預設查所有,需要條件自行增加。 簡單取1條驗證。 List<Document> result = mongoTemplate.find(new Query(), Document.class, IMAGE_COLLECTION); Document document = result.get(0); // 取出儲存的二進位制資料,這裡用binary.class處理。 Binary content = document.get("content", Binary.class); String fileName = document.get("fileName", String.class); try { String newFilePath = FILE_OUTPUT_PATH + fileName; // 寫入到指定路徑 FileOutputStream fos = new FileOutputStream(newFilePath); fos.write(content.getData()); } catch (IOException e) { e.printStackTrace(); } }
除了二進位制的格式,也可以直接把檔案用如Base64之類的編碼工具來轉碼儲存String。
@Test public void testBase64(){ saveFileWithBase64(FILE_PATH); getFileWithBase64(); } public void saveFileWithBase64(String filePath){ // 讀取檔案並編碼為 Base64 格式 File file = new File(filePath); byte[] fileContent = new byte[(int) file.length()]; try (FileInputStream inputStream = new FileInputStream(file)) { inputStream.read(fileContent); } catch (IOException e) { e.printStackTrace(); } // 把讀取到的流轉成base64 String encodedString = Base64.getEncoder().encodeToString(fileContent); // 將 Base64 編碼的檔案內容儲存到 MongoDB 文件中 Document document = new Document(); document.put("fileName", file.getName()); document.put("base64Content", encodedString); Document insert = mongoTemplate.insert(document, IMAGE_COLLECTION); System.out.println("檔案 " + file.getName() + " 已存入mongodb,對應ID是: " + insert.get("_id").toString()); } public void getFileWithBase64(){ Criteria criteria = Criteria.where("base64Content").exists(true); List<Document> result = mongoTemplate.find(new Query(criteria), Document.class, IMAGE_COLLECTION); Document document = result.get(0); String base64Content = document.get("base64Content", String.class); String fileName = document.get("fileName", String.class); byte[] decode = Base64.getDecoder().decode(base64Content); try { String newFilePath = FILE_OUTPUT_PATH + fileName; FileOutputStream fos = new FileOutputStream(newFilePath); fos.write(decode); System.out.println("檔案已讀取並複製到指定路徑,詳情為:" + newFilePath); } catch (IOException e) { e.printStackTrace(); } }
1.3 落庫後的效果
直接儲存二進位制資料,可以看到,使用BinData儲存,還會顯示位元組數(檔案大小)。
2. 大於16MB的檔案儲存,使用GridFS
2.1 gridFS簡介
GridFS is a specification for storing and retrieving files that exceed the BSON-document size limit of 16 MB.
字面直譯就是說GridFS是用來儲存大於BSON文件限制的16MB的檔案。
官方文件 https://www.mongodb.com/docs/manual/core/gridfs/
儲存原理:GridFS 會將大檔案物件分割成多個小的chunk(檔案片段), 一般為256k/個,每個chunk將作為MongoDB的一個文件(document)被儲存在chunks集合中。
每一個資料庫有一個GridFS區域,用來儲存。
需要透過先建立bucket(和OSS中一樣的概念)來儲存,一個bucket建立後,一旦有檔案存入,在collections中就會自動生成2個集合來儲存檔案的資料和資訊,一般是bucket名字+files和bucket名字+chunks。
每個檔案的實際內容被存在chunks(二進位制資料)中,和檔案有關的meta資料(filename,content_type,還有使用者自定義的屬性)將會被存在files集合中。
如下圖結構
2.2 實現程式碼
package com.onepiece.mongo; import com.mongodb.client.MongoDatabase; import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSBuckets; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.util.FileCopyUtils; import java.io.*; import java.util.List; /** * @author zaoyu * @description:使用GridFS儲存檔案並做讀取。 */ @SpringBootTest public class MongoGridFS { @Autowired private MongoTemplate mongoTemplate; // GridFS下的bucket,自行指定要把檔案儲存到哪個bucket。 private String BUCKET_NAME = "images"; // 原始檔,即要被儲存的檔案的絕對路徑 private String FILE_PATH = "D:\\temp\\onepiece.jpg"; // 儲存檔案後自動生成的儲存檔案資訊的collection,一般是xx.files。 private String COLLECTION_NAME = "images.files"; // 用於演示接收輸出檔案的路徑 private String FILE_OUTPUT_PATH = "C:\\Users\\onepiece\\Desktop\\"; @Test public void testGridFSSaveFiles() { saveToGridFS(); System.out.println("------------"); readFromGridFS(); } /** * 傳入bucketName得到指定bucket操作物件。 * * @param bucketName * @return */ public GridFSBucket createGridFSBucket(String bucketName) { MongoDatabase db = mongoTemplate.getDb(); return GridFSBuckets.create(db, bucketName); } /** * 儲存檔案到GridFS */ public void saveToGridFS() { // 先呼叫上面方法得到一個GridFSBucket的操作物件 GridFSBucket gridFSBucket = createGridFSBucket(BUCKET_NAME); File file = new File(FILE_PATH); FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } // 設定GridFS儲存配置,這裡是設定了每個chunk(塊)的大小為1024個位元組,也可以設定大一點。 MetaData是對檔案的說明,如果不需要可以不寫。 也是以鍵值對存在,BSON格式。 GridFSUploadOptions options = new GridFSUploadOptions().chunkSizeBytes(1024).metadata(new Document("user", "onepiece")); // 呼叫GridFSBucket中的uploadFromStream方法,把對應的檔案流傳遞進去,然後就會以binary(二進位制格式)儲存到GridFS中,並得到一個檔案在xx.files中的主鍵ID,後面可以用這個ID來查詢關聯的二進位制檔案資料。 ObjectId objectId = gridFSBucket.uploadFromStream(file.getName(), inputStream, options); System.out.println(file.getName() + "已存入mongodb gridFS, 對應id是:" + objectId); } /** * 從GridFS中讀取檔案 */ public void readFromGridFS() { // 這裡查詢條件我先不寫,預設查所有,取第一條做驗證演示。 用Document類接收。 List<Document> files = mongoTemplate.find(new Query(), Document.class, COLLECTION_NAME); Document file = files.get(0); // 得到主鍵ID,作為等下要查詢的檔案ID值。 ObjectId fileId = file.getObjectId("_id"); String filename = file.getString("filename"); // 先呼叫上面方法得到一個GridFSBucket的操作物件 GridFSBucket gridFSBucket = createGridFSBucket(BUCKET_NAME); // 呼叫openDownloadStream方法得到檔案IO流。 InputStream downloadStream = gridFSBucket.openDownloadStream(fileId); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(FILE_OUTPUT_PATH + filename); // 把IO流直接到指定路徑的輸出流物件實現輸出。 FileCopyUtils.copy(downloadStream, fileOutputStream); } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
2.3 落庫效果
bucket:
注意這裡的ID,就是files中的主鍵ID。
files collection (image.files):
chunks collection (image.chunks)
可以看到這裡的files_id就是對應image.files中的主鍵ID。檔案被拆成多個chunk塊。
三、小結
對於小檔案的,可以直接轉二進位制儲存,對於大於等於16MB的,使用GridFS儲存。
希望這篇文章能幫到大家,有錯漏之處,歡迎指正。
請多點贊、評論~
完。