Springboot整合MongoDB儲存檔案、讀取檔案

harlan_op發表於2023-04-14

一、前言和開發環境及配置

可以轉載,但請註明出處。  

之前自己寫的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儲存。 

 

希望這篇文章能幫到大家,有錯漏之處,歡迎指正。

請多點贊、評論~ 

完。

 

相關文章