一、前言和開發環境及配置
可以轉載,但請注明出處,
之前自己寫的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存盤,
希望這篇文章能幫到大家,有錯漏之處,歡迎指正,
請多點贊、評論~
完,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550100.html
標籤:其他
