随着互联网应用变得越来越复杂,数据格式越来越灵活,我们常常会遇到这样的困境:
-
需求频繁变更:增加一个字段就要改表结构(ALTER TABLE),在海量数据上这简直是噩梦。
-
数据结构复杂:想存储一个嵌套的JSON对象,得拆分成十几张表,查询时要写复杂的JOIN,性能堪忧。
正是为了解决这些问题,NoSQL数据库应运而生,而MongoDB便是其中之一。
一、什么是NoSQL?
NoSQL 的全称是 Not Only SQL,意为“不仅仅是SQL”。它并不是要取代SQL,而是为其提供一种补充,适用于不同的场景。
NoSQL数据库种类繁多,主要分为以下几类:
-
文档型数据库:如 MongoDB(以灵活的JSON-like文档存储数据)
-
键值型数据库:如 Redis(简单的键值对,速度极快)
-
列族数据库:如 Cassandra(为大规模数据分析设计)
-
图数据库:如 Neo4j(专注于实体之间的关系)
它们的共同特点是:灵活、易扩展、高性能。
二、什么是MongoDB?
关于MongoDB的介绍、安装,具体内容详见下述文章:
MongoDB与Reids的差异:
特性 | MongoDB | Redis |
---|---|---|
核心定位 | 文档型数据库 | 内存型键值数据库 |
数据模型 | 丰富的 文档模型 (类似 JSON的BSON) | 简单的 键值对,值支持多种数据结构 |
存储介质 | 磁盘 (内存用于缓存加速) | 内存 (可持久化到磁盘) |
主要优势 | 灵活性、查询能力、海量数据存储 | 速度 (极低延迟)、高并发、原子操作 |
典型用例 | 主数据库、内容管理、用户配置、实时分析 | 缓存、消息队列、会话存储、实时排行榜 |
三、与Mysql对比进行MongoDB的理解
概念对比
概念 | MongoDB (文档数据库) | MySQL (关系型数据库) |
---|---|---|
数据格式 | 类似JSON的文档(灵活嵌套) | 表格(严格的行和列) |
例子 | {_id: 1, name: "Alice", hobbies: ["唱歌", "游泳"] } | id: 1, name: "Alice" (hobbies需拆到另一张表) |
适合场景 | 数据结构多变、嵌套复杂(如用户画像、日志) | 结构固定、需要强一致性(如订单、账户) |
术语对比
MySQL 术语 | MongoDB 对应术语 | 区别说明 |
---|---|---|
数据库 (Database) | 数据库 (Database) | 概念相同 |
表 (Table) | 集合 (Collection) | MySQL 表有固定结构,MongoDB 集合无固定结构 |
行 (Row) | 文档 (Document) | MongoDB 的文档可以嵌套其他文档或数组 |
列 (Column) | 字段 (Field) | MongoDB 的字段可以动态增减 |
主键 (Primary Key) | _id 字段 | MongoDB 自动生成唯一 _id |
外键 (Foreign Key) | 无原生支持 | MongoDB 通过手动引用实现关联 |
JOIN 操作 | $lookup(有限支持) | MongoDB 的关联查询性能较低,推荐数据嵌套 |
查询语法对比
查询类型 | MySQL 查询语句 | MongoDB 查询语句 |
---|---|---|
查询所有用户 | SELECT * FROM users; | db.users.find(); |
条件查询(年龄>18) | SELECT * FROM users WHERE age > 18; | db.users.find([ age: { $gt: 18 } ]); |
关联查询(用户+订单) | SELECT u.name, o.order_id FROM users u JOIN orders o ON u.id = o.user_id; | db.users.aggregate([ { $lookup: { from: "orders", localField: "_id", foreignField: "user_id", as: "orders" } } ]); |
四、MongoDB + Spring Boot 实现文件存储
(一)两种存储方式
MongoDB 提供两种文件存储方式:
1. GridFS
- 适合大文件(>16MB,MongoDB 单个文档限制)
- 自动分块存储,支持流式读写
2. 直接存储小文件(Base64/Binary)
- 适合小文件(<16MB)
- 简单快捷,无需额外配置
以下采用GridFS大文件存储的方式,进行文件存储。
(二)添加依赖与配置文件
<!-- mongodb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
# application.yml
spring:
data:
mongodb:
host: localhost
port: 27017
database: test
# username: your_user
# password: your_pass
(三)代码实践
需在controller类中添加
import org.springframework.data.mongodb.core.MongoTemplate;
@Autowired
private MongoTemplate mongoTemplate;
// 对mongo数据库进行连接使用
文件上传
@PostMapping("upload")
public String uploadFile(MultipartFile file) {
try (InputStream inputStream = file.getInputStream()){
// 1、 创建桶(数据库名称 ,桶名 )
GridFSBucket bucket = GridFSBuckets.create(mongoTemplate.getDb(),"file");
//2、 设置分片上传的条件
GridFSUploadOptions options = new GridFSUploadOptions()
.chunkSizeBytes(1024)
.metadata(new Document("filename",file.getOriginalFilename())); // 附加的记录信息
// 3、 上传文件
// objectId 是 文件的唯一标识 ( mongodb集合中的主键 “_id”)
ObjectId objectId = bucket.uploadFromStream(file.getOriginalFilename(), inputStream,options);
// 转化成String
return objectId.toHexString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
文件预览/下载
@GetMapping("preview/{id}")
public void previewFile(@PathVariable("id") String id, HttpServletResponse response) {
// 1、 创建桶
GridFSBucket bucket = GridFSBuckets.create(mongoTemplate.getDb(),"file");
// 2、 获取文件主键信息
ObjectId objectId = new ObjectId(id);
// 3、 查找文件信息
// 查找条件 new BsonDocument("_id", new BsonObjectId(objectId))
GridFSFile file = bucket
.find(new BsonDocument("_id", new BsonObjectId(objectId)))
.first();
if (file == null) {
// throw new RuntimeException("文件不存在");
try {
response.sendError(40404,"文件不存在");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 文件存在 ,获取文件流
// 4、 获取文件流
// 设置响应头(根据文件类型调整 Content-Type)
//response.setContentType(file.getMetadata().get("_contentType", "application/octet-stream"));
response.setHeader("Content-Disposition", "inline; filename=\"" + file.getFilename() + "\"");
// 5 、将文件流写入响应
try (GridFSDownloadStream downloadStream = bucket.openDownloadStream(file.getObjectId())) {
try {
IOUtils.copy(downloadStream, response.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
文件删除
@DeleteMapping("/delete/{id}")
public void delete(@PathVariable String id) {
GridFSBucket bucket = GridFSBuckets.create(mongoTemplate.getDb(),"file");
bucket.delete(new ObjectId(id));
}
存在判断
@GetMapping("/{id}")
public String get(@PathVariable String id) {
// 1、 创建桶
GridFSBucket bucket = GridFSBuckets.create(mongoTemplate.getDb(),"file");
// 2、 查找文件信息 查找条件 new BsonDocument("_id", new BsonObjectId(objectId))
GridFSFile file = bucket
.find(new BsonDocument("_id", new BsonObjectId(new ObjectId(id))))
.first();
if (file != null) {
return "文件存在";
} else {
return "文件不存在";
}
}