Spring Data MongoDB 教程

GridFS 支持

GridFS 是 MongoDB 中存储和查询超过 BSON 文件大小限制(16M)的规范,不像 BSON 文件那样在一个单独的文档中存储文件,GridFS 将文件分成多个块,每个块作为一个单独的文档。默认情况下,每个 GridFS 块是 255kB,意味着除了最后一个块之外(根据剩余的文件大小),文档被分成多个 255kB 大小的块存储。

GridFS 使用两个集合保存数据,一个集合存储文件块,另外一个存储文件元数据。

当从 GridFS 中获取文件时,MongoDB 的驱动程序负责将多个块组装成完整文件,你可以通过 GridFS 进行范围查询,可以访问文件的任意部分(例如跳到视频文件或者音频文件的任意位置)。

无论是超过 16M 的文件和其他文件,只要存在访问时不想加载整个文件的场景存在,GridFS 对你就有帮助。

应用场景

在 MongoDB 中,使用 GridFS 存储超过 16M 的文件(BSON 文件不能超过 16M)。在某些情况下,MongoDB 存储大文件会比操作系统的文件系统更高效:

(1)如果你的文件系统限制目录下文件的个数,可以使用 MongoDB 在目录下存储任意多的文件。

(2)访问大数据文件时,不想一次加载而是分段访问。

(3)在多个系统间实现文件和元数据同步。

对文件进行原子更新时,MongoDB 不适合,不能支持对文件多个块更新操作的原子性;如果确有需要,也可以通过在元数据中指定当前版本来变通实现。

如果你的文件都小于 16M,应该考虑使用每个文件存一个独立文档的方式来取代 GridFS,可以使用 BinData 类型来存储二进制数据(也可以使用 GridFS,需要修改 chunk 大小,避免小文件被拆分,需要进行测试和比较性能)。

mongofiles 工具

mongofiles 是一个 MongoDB 内建工具,可以用它在 GridFS 中上传、下载、查看、搜索和删除文件。注意,在 MongoDB 5+ 版本后 mongofiles 命令被单独出来,没有放在 MongoDB 安装包的 bin 目录中,工具下载地址 https://www.mongodb.com/try/download/database-tools(mongosh-1.9.0-win32-x64.zip)。

下面将举例说明 mongofiles 工具的用法,如下:

上传文件

上传一个文件到 GridFS 文件系统中,命令格式 “mongofiles -d 数据库名字 -l "要上传的文件的完整路径名" put "上传后的文件名"”。如下:

# 将 D:\mongodb-database-tools-windows-x86_64-100.7.0\bin\README.md 文件上传到 GridFS,重命名为 readme.md
D:\mongodb-database-tools-windows-x86_64-100.7.0\bin> mongofiles -d test -l D:\mongodb-database-tools-windows-x86_64-100.7.0\bin\README.md put readme.md
2023-05-18T16:05:59.754+0800    connected to: mongodb://localhost/
2023-05-18T16:05:59.802+0800    adding gridFile: readme.md

2023-05-18T16:05:59.861+0800    added gridFile: readme.md

然后,在 MongoDB 数据库中就会多出 2 个集合,它们存储了 GridFS 文件系统的所有文件信息,查询这两个集合就能看到上传的那个文件的一些信息,如下图:

下载文件

从 GridFS 文件系统中下载一个文件到本地,命令格式 “mongofiles -d 数据库名字 -l "将文件保存在本地的完整路径名" get "GridFS文件系统中的文件名"”,如果不写 -l 以及后面的路径参数,则保存到当前位置。例如:

# 将 readme.md 下载到 D:\readme.md
D:\mongodb-database-tools-windows-x86_64-100.7.0\bin> mongofiles -d test -l D:\readme.md get readme.md
2023-05-18T16:11:12.023+0800    connected to: mongodb://localhost/
2023-05-18T16:11:12.072+0800    finished writing to D:\readme.md

# 将 readme.md 下载到当前目录
D:\mongodb-database-tools-windows-x86_64-100.7.0\bin> mongofiles -d test get readme.md
2023-05-18T16:11:39.710+0800    connected to: mongodb://localhost/
2023-05-18T16:11:39.761+0800    finished writing to readme.md

查看文件

查看 GridFS 文件系统中所有文件,命令格式 “mongofiles -d 数据库名字 list”,如下:

# 查看 test 数据库下面的所有文件
D:\mongodb-database-tools-windows-x86_64-100.7.0\bin> mongofiles -d test list
2023-05-18T16:12:15.790+0800    connected to: mongodb://localhost/
readme.md       3120

删除文件

删除 GridFS 文件系统中的某个文件,命令格式 “mongofiles -d 数据库名字 delete "文件名"”,如下:

# 删除名为 readme.md 的文件
D:\mongodb-database-tools-windows-x86_64-100.7.0\bin> mongofiles -d test delete readme.md
2023-05-18T16:12:45.188+0800    connected to: mongodb://localhost/
2023-05-18T16:12:45.251+0800    successfully deleted all instances of 'readme.md' from GridFS

Spring Data MongoDB 对 GridFS 支持

前面介绍了 MongoDB 支持在其文件系统 GridFS 中存储二进制文件。Spring Data MongoDB 提供了一个 GridFsOperations 接口以及相应的实现,即 GridFsTemplate,让你与文件系统互动。

你可以通过给它一个 MongoDatabaseFactory 以及一个 MongoConverter 来设置一个 GridFsTemplate 实例,如下例所示:

public class GridFsConfiguration extends AbstractMongoClientConfiguration {

   // ...further configuration omitted
   @Bean
   public GridFsTemplate gridFsTemplate() {
       return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
   }

}

基于 XML 配置文件实例化 GridFsTemplate,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mongo="http://www.springframework.org/schema/data/mongo"
 xsi:schemaLocation="http://www.springframework.org/schema/data/mongo
                     https://www.springframework.org/schema/data/mongo/spring-mongo.xsd
                     http://www.springframework.org/schema/beans
                     https://www.springframework.org/schema/beans/spring-beans.xsd">

   <mongo:db-factory id="mongoDbFactory" dbname="database" />
   <mongo:mapping-converter id="converter" />

   <bean class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
       <constructor-arg ref="mongoDbFactory" />
       <constructor-arg ref="converter" />
   </bean>

</beans>

现在,GridFsTemplate 模板则可以被注入,并用于执行存储和检索操作,例如使用 GridFsTemplate 来存储文件,代码如下:

public class GridFsClient {

   @Autowired
   private GridFsOperations operations;

   @Test
   public void storeFileToGridFs() {
       FileMetadata metadata = new FileMetadata();
       // populate metadata
       Resource file = ... // lookup File or Resource

       operations.store(file.getInputStream(), "filename.txt", metadata);
   }

}

store(...) 操作接收一个 InputStream、一个文件名和(可选择的)关于要存储的文件的元数据信息。元数据可以是一个任意的对象,它将被配置在 GridFsTemplate 中的MongoConverter 处理。另外,你也可以提供一个 Document。

你可以通过 find(...) 或 getResources(...) 方法从文件系统中读取文件。让我们先看一下 find(...) 方法,你可以找到一个文件,也可以找到符合查询条件的多个文件。你可以使用 GridFsCriteria 辅助类来定义查询。它提供了静态的工厂方法来封装默认的元数据字段(比如 whereFilename() 和 whereContentType()),或者通过whereMetaData() 封装一个自定义的元数据。下面的例子显示了如何使用 GridFsTemplate 来查询文件:

public class GridFsClient {

   @Autowired
   private GridFsOperations operations;

   @Test
   public void findFilesInGridFs() {
       GridFSFindIterable result = operations.find(query(whereFilename().is("filename.txt")))
   }

}

注意:目前,MongoDB 不支持在从 GridFS 检索文件时定义排序标准。出于这个原因,任何定义在查询实例上的排序标准都会被忽略,而这些排序标准会被交给 find(...) 方法来处理。

从 GridFs 读取文件的另一个选择是使用 ResourcePatternResolver 接口引入的方法。它们允许在方法中输入 Ant 路径,因此可以检索到与给定模式相匹配的文件。下面的例子显示了如何使用 GridFsTemplate 来读取文件:

public class GridFsClient {

   @Autowired
   private GridFsOperations operations;

   @Test
   public void readFilesFromGridFs() {
       GridFsResources[] txtFiles = operations.getResources("*.txt");
   }

}

注意:GridFsOperations 扩展了 ResourcePatternResolver,并允许将 GridFsTemplate(实例)插入 ApplicationContext,以从 MongoDB 数据库读取 Spring 配置文件。

完整示例

(1)application.properties 配置文件

# Log
logging.level.root=info

# MongoDB
spring.data.mongodb.uri=mongodb://localhost:27017/test

(2)配置类,配置 GridFsTemplate 实例

package com.hxstrive.springdata.mongodb.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;

/**
* MongoDB 配置
* @author hxstrive.com 2022/12/23
*/
@Slf4j
@Configuration
public class AppConfig {

   @Bean
   public GridFsTemplate gridFsTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
       return new GridFsTemplate(mongoDatabaseFactory, mongoConverter);
   }

}

(3)客户端代码

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.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsOperations;
import org.springframework.data.mongodb.gridfs.GridFsResource;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

@SpringBootTest
public class GridfsDemo {

   @Autowired
   private GridFsOperations operations;

   /**
    * 存入文件到 GridFS
    */
   @Test
   void store() throws Exception {
       // 文件
       FileInputStream inputStream = new FileInputStream(new File("D:\\readme.txt"));
       ObjectId id = operations.store(inputStream, "myFile.txt");
       System.out.println(id);
       // 结果:
       // 646707cc9d59344f3fa58c21
   }

   /**
    * 根据文件名获取文件信息
    */
   @Test
   void getResource() throws Exception {
       GridFsResource resource = operations.getResource("myFile.txt");
       System.out.println("ID:" + resource.getId());
       System.out.println("文件名:" + resource.getFilename());
       System.out.println("大小:" + resource.contentLength());
       System.out.println("正文:");

       InputStream inputStream = resource.getContent();
       ByteArrayOutputStream output = new ByteArrayOutputStream();
       byte[] bytes = new byte[1024];
       int len;
       while((len = inputStream.read(bytes)) != -1) {
           output.write(bytes, 0, len);
       }
       inputStream.close();
       System.out.println(new String(output.toByteArray()));
       // 结果:
       //ID:BsonObjectId{value=646707cc9d59344f3fa58c21}
       //文件名:myFile.txt
       //大小:21
       //正文:
       //hello mongodb
       //gridfs
   }

   /**
    * 根据模式检索文件
    */
   @Test
   void getResources() {
       GridFsResource[] resources = operations.getResources("*.txt");
       for(GridFsResource resource : resources) {
           System.out.println(resource.getFileId() + "  " + resource.getFilename());
       }
       // 结果:
       // 646707cc9d59344f3fa58c21  myFile.txt
   }

   /**
    * 根据文件名删除文件
    */
   @Test
   void delete() {
       operations.delete(Query.query(Criteria.where("filename").is("myFile.txt")));
   }

}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号