1. 概述
在使用 MongoDB 存储大文件时,我们很快就会遇到单文档 16MB 的 BSON 大小限制。这时候,GridFS 就派上用场了 —— 它是 MongoDB 提供的一种将大文件拆分为多个小块(chunks)进行存储的规范。
Spring Data MongoDB 对 GridFS 提供了非常友好的封装,核心是 GridFsOperations
接口及其默认实现 GridFsTemplate
。通过它,我们可以像操作普通文件系统一样上传、查询、下载和删除文件,而无需关心底层分块和重组的细节。
✅ 适用场景:存储图片、视频、日志文件等超过 16MB 的二进制数据
❌ 不推荐:小文件(元数据开销大),或频繁随机读写的场景
2. 配置方式
2.1 XML 配置(已逐渐淘汰,了解即可)
如果你还在维护老项目,可能会看到如下 XML 配置:
<bean id="gridFsTemplate" class="org.springframework.data.mongodb.gridfs.GridFsTemplate">
<constructor-arg ref="mongoDbFactory" />
<constructor-arg ref="mongoConverter" />
</bean>
<mongo:db-factory id="mongoDbFactory" dbname="filestore" mongo-client-ref="mongoClient" />
<mongo:mapping-converter id="mongoConverter" base-package="com.example.domain">
<mongo:custom-converters base-package="com.example.converter"/>
</mongo:mapping-converter>
⚠️ 注意:mongoDbFactory
负责连接 MongoDB 实例,mongoConverter
负责 Java 对象与 MongoDB 文档之间的转换。
2.2 Java 配置(推荐)
现代 Spring Boot 项目普遍采用纯 Java 配置,简洁且类型安全:
@Configuration
@EnableMongoRepositories(basePackages = "com.example.repository")
public class MongoConfig extends AbstractMongoClientConfiguration {
@Autowired
private MappingMongoConverter mongoConverter;
@Bean
public GridFsTemplate gridFsTemplate() throws Exception {
return new GridFsTemplate(mongoDbFactory(), mongoConverter);
}
@Override
protected String getDatabaseName() {
return "filestore";
}
@Bean
public MongoClient mongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
}
✅ 小贴士:继承 AbstractMongoClientConfiguration
可自动获得 mongoDbFactory()
方法,减少样板代码。
3. GridFsTemplate 核心方法实战
3.1 store:上传文件
最常用的接口,用于将文件存入 GridFS:
InputStream inputStream = new FileInputStream("src/main/resources/avatar.png");
// 可选:添加自定义元数据
DBObject metaData = new BasicDBObject();
metaData.put("user", "zhangsan");
metaData.put("uploadTime", System.currentTimeMillis());
// 执行存储
ObjectId fileId = (ObjectId) gridFsTemplate.store(
inputStream,
"avatar.png",
"image/png",
metaData
);
📌 存储原理:
- 文件元数据(文件名、类型、长度、自定义 metadata 等)存入
fs.files
集合 - 文件内容被切分为 256KB 的块(默认),存入
fs.chunks
集合 - 两个集合通过
files_id
字段关联
执行 db['fs.files'].find()
查看元数据:
{
"_id" : ObjectId("5602de6e5d8bba0d6f2e45e4"),
"metadata" : {
"user" : "zhangsan",
"uploadTime" : 1712345678901
},
"filename" : "avatar.png",
"chunkSize" : NumberLong(261120),
"uploadDate" : ISODate("2024-04-05T10:20:30.000Z"),
"length" : NumberLong(855),
"contentType" : "image/png"
}
执行 db['fs.chunks'].find()
查看内容块(示例只展示第一个 chunk):
{
"_id" : ObjectId("chunk-001"),
"files_id" : ObjectId("5602de6e5d8bba0d6f2e45e4"),
"n" : 0,
"data" : { "$binary": "iVBORw0KGgoAAAANSUhEUg...", "$type": "00" }
}
⚠️ 踩坑提醒:store()
方法返回的是 ObjectId
,不是字符串!强制转 toString()
虽然能用,但建议保留原始类型用于后续操作。
3.2 findOne:精确查询单个文件
根据条件查询最多一个文件,常用于通过 ID 获取文件:
String id = "5602de6e5d8bba0d6f2e45e4";
GridFSFile file = gridFsTemplate.findOne(
new Query(Criteria.where("_id").is(new ObjectId(id)))
);
📌 行为说明:
- 如果匹配多个文件,只返回自然排序下的第一条
- 如果无匹配,返回
null
- 建议优先使用
_id
查询,性能最优
3.3 find:批量查询文件列表
用于获取满足条件的所有文件元数据:
// 查询所有文件
List<GridFSFile> allFiles = new ArrayList<>();
gridFsTemplate.find(new Query()).into(allFiles);
带条件查询示例:查找 metadata 中 user 为 "zhangsan" 的文件
List<GridFSFile> files = new ArrayList<>();
gridFsTemplate.find(
new Query(Criteria.where("metadata.user").is("zhangsan"))
).into(files);
✅ 灵活用法:支持 metadata.xxx
路径查询,可结合正则、范围等复杂条件。
3.4 delete:删除文件
删除操作会同时清除 fs.files
和 fs.chunks
中的记录:
String id = "5702deyu6d8bba0d6f2e45e4";
gridFsTemplate.delete(
new Query(Criteria.where("_id").is(new ObjectId(id)))
);
⚠️ 踩坑提醒:删除后无法恢复,建议在业务层做软删除标记(如加 deleted: true
到 metadata)。
删除后,数据库中对应文件的元数据和所有 chunk 都会被移除。
3.5 getResources:按文件名模式获取资源
这是一个非常实用的方法,用于通过通配符匹配文件名,返回 GridFsResource
数组,便于后续流式处理:
// 匹配 test 开头的所有文件
GridFsResource[] resources = gridFsTemplate.getResources("test*");
for (GridFsResource resource : resources) {
String filename = resource.getFilename();
InputStream inputStream = resource.getInputStream();
// 处理文件流...
}
📌 示例数据:
[
{ "_id": ObjectId("..."), "filename": "test.png", "metadata": { "user": "alex" } },
{ "_id": ObjectId("..."), "filename": "test_backup.png", "metadata": { "user": "david" } },
{ "_id": ObjectId("..."), "filename": "baeldung.png", "metadata": { "user": "eugen" } }
]
调用 getResources("test*")
将返回前两个文件的资源对象。
✅ 优势:返回的是 Spring 的 Resource
接口,天然集成到 Spring 的资源体系,适合搭配 ResourceHttpRequestHandler
做文件下载接口。
4. GridFSFile 核心方法
GridFSFile
是文件元数据的 Java 表示,常用方法一览:
方法 | 说明 |
---|---|
getFilename() |
获取文件名 |
getMetadata() |
获取自定义元数据(DBObject) |
getId() |
获取文件 ObjectId |
getUploadDate() |
获取上传时间 |
getLength() |
获取文件总大小(字节) |
getContentType() |
获取 MIME 类型 |
containsField(String) |
判断是否包含某字段 |
get(String) |
通用字段获取(返回 Object) |
keySet() |
获取所有字段名 |
使用示例:
GridFSFile file = gridFsTemplate.findOne(query);
if (file != null) {
System.out.println("文件名: " + file.getFilename());
System.out.println("大小: " + file.getLength() + " bytes");
DBObject meta = file.getMetadata();
if (meta != null) {
System.out.println("上传用户: " + meta.get("user"));
}
}
5. 总结
GridFS 是处理大文件存储的可靠方案,而 GridFsTemplate
极大地简化了开发工作。核心要点回顾:
✅ store:上传文件 + 元数据,自动分块
✅ findOne / find:精准或批量查询元数据
✅ delete:彻底删除文件及分块
✅ getResources:按名匹配,返回 Spring Resource,便于集成下载接口
📌 实战建议:
- 文件名设计要有唯一性或命名规范,避免冲突
- 元数据别浪费,记录上传者、业务类型等信息便于检索
- 大文件下载注意流式处理,避免 OOM
- 生产环境务必加索引(如
filename
,metadata.user
)
完整示例代码已整理至 GitHub:https://github.com/yourname/spring-mongo-gridfs-demo(mock 地址)