MongoDB 快速实战与基本原理

目录

    • [一、MongoDB 简介](#一、MongoDB 简介)
      • [1.1 什么是 MongoDB](#1.1 什么是 MongoDB)
      • [1.2 MongoDB vs 关系型数据库对比](#1.2 MongoDB vs 关系型数据库对比)
      • [1.3 MongoDB 六大核心优势](#1.3 MongoDB 六大核心优势)
      • [1.4 典型应用场景](#1.4 典型应用场景)
    • [二、MongoDB 安装与配置](#二、MongoDB 安装与配置)
      • [2.1 Linux 安装 MongoDB(CentOS 7)](#2.1 Linux 安装 MongoDB(CentOS 7))
      • [2.2 Docker 安装 MongoDB](#2.2 Docker 安装 MongoDB)
      • [2.3 mongosh 使用](#2.3 mongosh 使用)
      • [2.4 安全认证](#2.4 安全认证)
      • [2.5 GUI 管理工具推荐](#2.5 GUI 管理工具推荐)
    • [三、MongoDB 文档操作(CRUD)](#三、MongoDB 文档操作(CRUD))
      • [3.1 插入文档](#3.1 插入文档)
      • [3.2 查询文档](#3.2 查询文档)
      • [3.3 更新文档](#3.3 更新文档)
      • [3.4 删除文档](#3.4 删除文档)
      • [3.5 批量写操作 bulkWrite](#3.5 批量写操作 bulkWrite)
    • [四、MongoDB 高级数据类型](#四、MongoDB 高级数据类型)
      • [4.1 BSON 协议与数据类型](#4.1 BSON 协议与数据类型)
      • [4.2 日期类型](#4.2 日期类型)
      • [4.3 ObjectId](#4.3 ObjectId)
      • [4.4 内嵌文档与数组](#4.4 内嵌文档与数组)
      • [4.5 固定集合(Capped Collection)](#4.5 固定集合(Capped Collection))
    • [五、SpringBoot 整合 MongoDB](#五、SpringBoot 整合 MongoDB)
      • [5.1 准备工作](#5.1 准备工作)
      • [5.2 文档操作注解](#5.2 文档操作注解)
      • [5.3 CRUD 操作代码](#5.3 CRUD 操作代码)
      • [5.4 小技巧:去除 `_class` 字段](#5.4 小技巧:去除 _class 字段)
    • 六、学习总结

一、MongoDB 简介

1.1 什么是 MongoDB

MongoDB 是一个文档数据库(以 JSON 为数据模型),由 C++ 编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

文档来自于"JSON Document",数据库中的一条记录就像是一个 PDF、WORD 文档一样。

定位:MongoDB 是一个介于关系型数据库和非关系型数据库之间的产品,是非关系型数据库当中功能最丰富、最像关系型数据库的。

  • 存储格式:BSON(Binary JSON,JSON 的二进制扩展格式),支持嵌套文档和数组
  • 查询能力:支持的查询语句非常强大,基本可以实现关系型数据库单表查询的绝大部分功能
  • 事务支持:支持对文档进行原子性操作(ACID 事务)

MongoDB 在数据库排行榜中位列前5,详情参考:https://db-engines.com/en/ranking

1.2 MongoDB vs 关系型数据库对比

关系型数据库 MongoDB 说明
database database 数据库
table collection 集合(表),集合中可存放不同结构的文档
row document 文档(行),由多个不同字段组成
column field 字段(列)
index index 索引,概念一致
primary key _id 每个文档都拥有唯一的 _id 字段
view view 视图,MongoDB 3.4 开始提供,通过聚合管道实现
JOIN $lookup 聚合操作中实现关联查询(表连接)

⚠️ 重要区别:MongoDB 没有外键约束,文档内的字段不要求相同------这正是其灵活半结构化的特点。

1.3 MongoDB 六大核心优势

优势 说明
🔗 直观 从复杂的关系模型到一目了然的对象模型
⚡ 快速 简单可靠的快速开发方式
🔄 灵活 动态模式意味着更容易应对业务变化
🚀 高可用 副本集提供 99.999% 高可用
📈 可扩展 分片架构支持海量数据和无限扩展
🌐 JSON 友好 JSON 结构和对象模型接近,开发效率高

1.4 典型应用场景

场景 说明
🎮 游戏 存储游戏用户信息、装备、积分等,直接以内嵌文档形式存储,便于查询
🛒 物流 存储订单信息,订单状态在运送过程中不断更新,MongoDB 以嵌套数组方式存储
👥 社交 存储用户信息及用户发表的朋友圈信息,通过地理位置索引实现附近功能
📡 物联网 存储所有接入的智能设备信息及设备汇报的日志信息
🎬 视频直播 存储用户信息、点赞、弹幕等
🔬 大数据应用 使用云数据库 MongoDB 作为大数据的云存储系统,随时进行数据提取分析

二、MongoDB 安装与配置

2.1 Linux 安装 MongoDB(CentOS 7)

前置准备:CentOS 7 系统

下载 MongoDB Community Server

下载地址:https://www.mongodb.com/try/download/community

bash 复制代码
# 下载 MongoDB
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-6.0.5.tgz
tar -zxvf mongodb-linux-x86_64-rhel70-6.0.5.tgz

启动 MongoDB Server

bash 复制代码
# 创建 dbpath 和 logpath
mkdir -p /mongodb/data /mongodb/log

# 进入 mongodb 目录,启动 mongodb 服务
bin/mongod --port=27017 --dbpath=/mongodb/data --logpath=/mongodb/log/mongodb.log \
  --bind_ip=0.0.0.0 --fork

常用启动参数说明

参数 说明
--dbpath 指定数据文件存放的目录
--logpath 指定日志文件路径(注意:是文件非目录)
--logappend 使用追加的方式记录日志
--port 指定端口,默认为 27017
--bind_ip 默认只监听 localhost
--fork 后台运行
--auth 开启认证模式

配置环境变量

bash 复制代码
# 修改 /etc/profile 添加环境变量
export MONGODB_HOME=/usr/local/soft/mongodb
PATH=$PATH:$MONGODB_HOME/bin

# 然后执行
source /etc/profile

配置文件方式启动(推荐):

yaml 复制代码
# /mongodb/conf/mongo.conf(YAML 格式)
systemLog:
  destination: file
  path: /mongodb/log/mongod.log
  logAppend: true
storage:
  dbPath: /mongodb/data
  engine: wiredTiger
  journal:
    enabled: true
net:
  bindIp: 0.0.0.0
  port: 27017
processManagement:
  fork: true
bash 复制代码
# 使用配置文件启动
mongod -f /mongodb/conf/mongo.conf

# 关闭 MongoDB(方式1)
mongod --port=27017 --dbpath=/mongodb/data --shutdown

# 关闭 MongoDB(方式2:进入 mongosh)
use admin
db.shutdownServer()

2.2 Docker 安装 MongoDB

bash 复制代码
# 拉取 mongo 镜像
docker pull mongo:6.0.5

# 运行 mongo 容器
docker run --name mongo-server -p 29017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=fox \
  -e MONGO_INITDB_ROOT_PASSWORD=fox \
  -d mongo:6.0.5 --wiredTigerCacheSizeGB 1

# 远程连接
mongosh ip:29017 -u fox -p fox

💡 --wiredTigerCacheSizeGB 1 用于限制内存使用,否则 Mongo 会将 wiredTigerCacheSizeGB 设置为可用内存的较高比例。

2.3 mongosh 使用

mongosh 是 MongoDB 的交互式 JavaScript Shell,MongoDB 6.0 已移除旧版 mongo,改用 mongosh

bash 复制代码
# 安装 mongosh(CentOS 7)
wget https://downloads.mongodb.com/compass/mongodb-mongosh-1.8.0.x86_64.rpm
yum install -y mongodb-mongosh-1.8.0.x86_64.rpm

# 连接 MongoDB
mongosh --host=192.168.65.206 --port=27017
mongosh 192.168.65.206:27017
# 指定 URI 格式连接
mongosh mongodb://192.168.65.206:27017/test

常用 mongosh 命令

命令 说明
show dbs 显示数据库列表
use <数据库名> 切换/创建数据库
db.dropDatabase() 删除当前数据库
show collections 显示当前数据库的集合列表
db.<集合>.stats() 查看集合信息
db.<集合>.drop() 删除集合
show users 显示当前数据库的用户列表
db.version() 查看数据库版本
db.help() 查询当前数据库支持的方法
`exit quit`
javascript 复制代码
// 数据库操作
show dbs          // 查看所有库
use test          // 切换到指定数据库,不存在则创建
db.dropDatabase() // 删除当前数据库

// 集合操作
show collections                  // 查看集合
db.createCollection("emp")        // 创建集合
db.emp.drop()                     // 删除集合

createCollection 选项

字段 类型 说明
capped Boolean 是否创建固定集合(超出大小时自动覆盖最早文档)
size 数值 固定集合时指定最大字节数(capped=true 时必填)
max 数值 固定集合时指定包含的最大文档数

2.4 安全认证

MongoDB 的安全认证通过用户名/密码实现,分两步:先建超级管理员用户,再建普通应用用户。

创建超级管理员

javascript 复制代码
use admin
// 创建管理员用户
db.createUser({user: "fox", pwd: "fox", roles: ["root"]})
// 查看用户
show users
// 删除用户
db.dropUser("fox")

内置权限角色

角色 说明
read 允许用户读取指定数据库
readWrite 允许用户读写指定数据库
dbAdmin 执行管理函数(索引创建、删除、统计等)
dbOwner 数据库最高权限(增删改查+用户管理)
userAdmin 在指定数据库中创建、删除和管理用户
clusterAdmin 分片和复制集的管理权限(仅 admin 库可用)
root 超级管理员,拥有所有权限
readAnyDatabase 所有数据库的读权限(仅 admin 库)
readWriteAnyDatabase 所有数据库的读写权限(仅 admin 库)

开启认证并连接

bash 复制代码
# 启动时开启认证
mongod -f /mongodb/conf/mongo.conf --auth

# 带认证连接
mongosh 192.168.65.206:27017 -u fox -p fox --authenticationDatabase=admin

2.5 GUI 管理工具推荐

工具 说明 地址
Compass 官方 GUI(免费) https://www.mongodb.com/zh-cn/products/compass
Robo 3T 开源免费 https://robomongo.org/
Studio 3T 收费(30天试用) https://studio3t.com/download/

MongoDB Database Tools

工具 说明
mongostat 数据库性能监控工具
mongotop 热点集合监控
mongodump 数据库逻辑备份
mongorestore 数据库逻辑恢复
mongoexport 数据导出工具
mongoimport 数据导入工具
bsondump BSON 格式转换工具
mongofiles GridFS 文件管理

三、MongoDB 文档操作(CRUD)

参考文档:https://www.mongodb.com/docs/manual/reference/sql-comparison/

3.1 插入文档

MongoDB 提供以下方法将文档插入集合:

  • db.collection.insertOne() :将单个文档插入到集合中
  • db.collection.insertMany() :将多个文档插入到集合中

插入单个文档 insertOne

javascript 复制代码
// 基础语法
db.collection.insertOne(
  <document>,
  { writeConcern: <document> }
)

// 示例(包含 writeConcern)
db.emps.insertOne(
  { name: "fox", age: 35 },
  {
    writeConcern: { w: "majority", j: true, wtimeout: 5000 }
  }
)

writeConcern 参数说明

参数 说明
w 指定写确认级别:数字=等待完成写操作的节点数;majority=多数节点
j 是否需要日志持久化后才返回(true=日志落盘后返回,false=不等待)
wtimeout 等待写操作完成的超时时间(毫秒),默认 0(不超时)

插入多个文档 insertMany

javascript 复制代码
db.collection.insertMany(
  [ <document1>, <document2>, ... ],
  {
    writeConcern: <document>,
    ordered: <boolean>   // 是否顺序写入,默认 true
  }
)

实战:插入 50 本书的数据

javascript 复制代码
// 编辑脚本 book.js
var tags = ["nosql", "mongodb", "document", "developer", "popular"];
var types = ["technology", "sociality", "travel", "novel", "literature"];
var books = [];
for (var i = 0; i < 50; i++) {
  var typeIdx = Math.floor(Math.random() * types.length);
  var tagIdx = Math.floor(Math.random() * tags.length);
  var favCount = Math.floor(Math.random() * 100);
  var book = {
    title: "book-" + i,
    type: types[typeIdx],
    tag: tags[tagIdx],
    favCount: favCount,
    author: "xxx" + i
  };
  books.push(book);
}
db.books.insertMany(books);

// 在 mongosh 中执行脚本
load("books.js")

3.2 查询文档

查询所有文档

javascript 复制代码
// 语法
db.collection.find(query, projection)
// query:查询条件(可选)
// projection:返回字段(可选)

// 查询所有
db.books.find()

// 格式化输出
db.books.find().pretty()

// 查询第一条文档
db.collection.findOne(query, projection)

⚠️ projection 字段:_id 为 1 时总是返回;其他字段要么全为 1(包含),要么全为 0(排除);但 _id 字段可以单独设 0 排除。

比较查询运算符

SQL MQL
a = 1 {a: 1}
a <> 1 {a: {$ne: 1}}
a > 1 {a: {$gt: 1}}
a >= 1 {a: {$gte: 1}}
a < 1 {a: {$lt: 1}}
a <= 1 {a: {$lte: 1}}

逻辑查询运算符

SQL MQL
a = 1 AND b = 1 {a: 1, b: 1}{$and: [{a:1},{b:1}]}
a = 1 OR b = 1 {$or: [{a: 1}, {b: 1}]}
a IS NULL {a: {$exists: false}}
a IN (1, 2, 3) {a: {$in: [1, 2, 3]}}

查询示例

javascript 复制代码
// 查询带有 nosql 标签的 book 文档
db.books.find({tag: "nosql"})

// 根据 id 查询单个 book 文档
db.books.find({_id: ObjectId("61caa09ee0782536660494d9")})

// 查询分类为"travel"并且收藏数超过 60 的 book 文档
db.books.find({type: "travel", favCount: {$gt: 60}})

// 正则表达式匹配查询(查询 type 中包含 so 字符的 book)
db.books.find({type: {$regex: "so"}})
// 简写形式
db.books.find({type: /so/})

排序

javascript 复制代码
// 指定按收藏数(favCount)降序返回
db.books.find({type: "travel"}).sort({favCount: -1})
// 1 表示升序,-1 表示降序

分页查询

javascript 复制代码
// skip 指定跳过的记录数,limit 限制返回结果数
// 查询第 3 页(每页 8 条)
db.books.find().skip(16).limit(8)

🔔 大数据量分页最佳实践 :生产环境中应避免使用 skip/limit 方式,推荐使用游标+唯一索引

javascript 复制代码
// 第一页
db.books.find({}).sort({_id: 1}).limit(10)
// 第二页(基于上页最后一个 _id)
db.books.find({_id: {$gt: <上一页最后一个_id>}}).sort({_id: 1}).limit(10)

同时,不要在分页中使用 count(),count 需要扫描所有匹配记录,在大数据量下极慢。

3.3 更新文档

MongoDB 提供以下更新方法:

  • db.collection.updateOne() :更新单个文档
  • db.collection.updateMany() :更新多个文档

常用更新操作符

操作符 语法 说明
$set {$set: {field: value}} 指定字段设值,不存在则创建
$unset {$unset: {field: 1}} 删除字段
$inc {$inc: {field: value}} 字段值增减(正数增,负数减)
$rename {$rename: {old_name: new_name}} 修改字段名
$push {$push: {field: value}} 向数组末尾追加元素
$pushAll {$pushAll: {field: value_array}} 向数组追加多个值
$pull {$pull: {field: value}} 从数组删除指定元素
$addToSet {$addToSet: {field: value}} 向数组添加元素(去重)
$pop {$pop: {field: 1}} 删除数组第一个(-1)或最后一个(1)元素

更新示例

javascript 复制代码
// 将所有 type 为"novel"的文档添加 publishedDate 字段
db.books.updateMany({type: "novel"}, {$set: {publishedDate: new Date()}})

// findAndModify:查询并修改,返回操作前的文档
db.books.findAndModify({
  query: {_id: ObjectId("642ec31813bdda928a1ea2a8")},
  update: {$inc: {favCount: 1}}
})

// new: true 表示返回修改后的文档
db.books.findAndModify({
  query: {_id: ObjectId("642ec31813bdda928a1ea2a8")},
  update: {$inc: {favCount: 1}},
  new: true
})

updateOne 完整语法

javascript 复制代码
db.collection.updateOne(
  <filter>,            // 筛选条件
  <update>,            // 更新操作
  {
    upsert: <boolean>, // true=不存在时插入,默认 false
    writeConcern: <document>,
    collation: <document>,
    arrayFilters: [ <filterdocument1>, ... ],
    hint: <document|string>  // MongoDB 4.2.1+
  }
)

3.4 删除文档

javascript 复制代码
// deleteOne:删除第一个匹配文档
db.books.deleteOne({type: "novel"})

// deleteMany:删除所有匹配文档
db.books.deleteMany({type: "novel"})

// deleteMany({}):删除集合中全部文档(比 remove 效率高,但不如 drop)
db.books.deleteMany({})

// findOneAndDelete:删除并返回被删除的文档
db.books.findOneAndDelete({type: "novel"})

// 按指定排序删除第一条(实现队列先进先出)
db.books.findOneAndDelete({type: "novel"}, {sort: {favCount: 1}})

⚠️ 如果要删除整个集合,使用 drop() 更高效,而不是 deleteMany({})

3.5 批量写操作 bulkWrite

bulkWrite() 提供执行多种插入、更新和删除操作的能力:

javascript 复制代码
db.pizzas.bulkWrite([
  { insertOne: { document: { _id: 3, type: "beef", size: "medium", price: 6 } } },
  { insertOne: { document: { _id: 4, type: "sausage", size: "large", price: 10 } } },
  { updateOne: {
      filter: { type: "cheese" },
      update: { $set: { price: 8 } }
  } },
  { deleteOne: { filter: { type: "pepperoni" } } },
  { replaceOne: {
      filter: { type: "vegan" },
      replacement: { type: "tofu", size: "small", price: 4 }
  } }
])

bulkWrite() 支持的操作类型:insertOneupdateOneupdateManyreplaceOnedeleteOnedeleteMany


四、MongoDB 高级数据类型

4.1 BSON 协议与数据类型

为什么使用 BSON 而不是 JSON?

JSON 虽通用,但存在以下局限:

  • 只支持 6 种基本类型(string、number、object、array、true/false、null)
  • 基于文本,解析效率较低
  • 不支持日期、二进制等特殊类型

BSON(Binary JSON)是 MongoDB 使用的存储/传输格式,相对 JSON 有以下优势:

优势 说明
🔤 类型更丰富 支持日期、二进制、ObjectId 等 JSON 不支持的类型
⚡ 高效编解码 二进制格式,记录每个元素长度,可直接 seek 读取
📦 支持嵌套 同样支持嵌套文档和数组

MongoDB 中,单个 BSON 文档最大为 16M,文档嵌套层数不超过 100。

常用 BSON 数据类型

类型 编号 别名 说明
Double 1 "double" 双精度浮点数
String 2 "string" 字符串
Object 3 "object" 对象(内嵌文档)
Array 4 "array" 数组
Binary data 5 "binData" 二进制数据
ObjectId 7 "objectId" 对象ID(自动生成)
Boolean 8 "bool" 布尔值
Date 9 "date" 日期
Null 10 "null" 空值
Regular Expression 11 "regex" 正则表达式
32-bit integer 16 "int" 32位整数
Timestamp 17 "timestamp" 时间戳
64-bit integer 18 "long" 64位整数
Decimal128 19 "decimal" 高精度浮点(3.4+)

$type 操作符

javascript 复制代码
// 查询 title 字段为 string 类型的文档
db.books.find({"title": {$type: 2}})
// 等价于
db.books.find({"title": {$type: "string"}})

4.2 日期类型

javascript 复制代码
// 插入三种日期格式
db.dates.insertMany([
  {data1: Date()},       // 返回字符串
  {data2: new Date()},   // 返回 ISODate 对象(UTC 时间)
  {data3: ISODate()}     // 同 new Date()
])
db.dates.find().pretty()

⚠️ MongoDB 使用 UTC(+0 时区) 存储日期,查询时注意时区换算(中国时区 +8)。

4.3 ObjectId

每个集合文档都有唯一的 _id 字段,默认使用 ObjectId 类型(12 字节,十六进制表示)。

ObjectId 结构(防重复设计):

复制代码
[ 4字节 Unix时间戳(秒) ] [ 5字节 随机数(进程+机器唯一标识) ] [ 3字节 递增计数器 ]
方法 说明
ObjectId.str 返回对象的十六进制字符串表示
ObjectId.getTimestamp() 返回创建时间(日期对象)
ObjectId.toString() ObjectId(...) 形式返回字符串
ObjectId.valueOf() 以十六进制字符串 str 形式返回
javascript 复制代码
// 创建一个新的 ObjectId
x = ObjectId()

4.4 内嵌文档与数组

内嵌文档

javascript 复制代码
// 插入带作者信息的书籍文档(内嵌)
db.books.insert({
  title: "大卫科波菲尔",
  author: {
    name: "狄更斯",
    gender: "男",
    hometown: "英国"
  }
})

// 查询狄更斯的作品
db.books.find({"author.name": "狄更斯"})

// 修改作者的家乡字段(点表示法访问嵌套字段)
db.books.updateOne(
  {"author.name": "狄更斯"},
  {$set: {"author.hometown": "英国/台湾"}}
)

数组操作

javascript 复制代码
// 设置 tags 数组
db.books.updateOne(
  {"author.name": "狄更斯"},
  {$set: {tags: ["政治", "历史", "散文", "小说", "文学"]}}
)

// 查询数组中的元素
db.books.find({"author.name": "狄更斯"}, {title: 1, tags: 1})

// $slice 取最后一个 tag
db.books.find({"author.name": "狄更斯"}, {title: 1, tags: {$slice: -1}})

// 追加末尾元素
db.books.updateOne({"author.name": "狄更斯"}, {$push: {tags: "古典"}})

// 同时追加多个元素($each)
db.books.updateOne(
  {"author.name": "狄更斯"},
  {$push: {tags: {$each: ["人权", "最美文学"]}}}
)

// $slice 保留最后 N 个元素
db.books.updateOne(
  {"author.name": "狄更斯"},
  {$push: {tags: {$each: ["人权", "最美文学"], $slice: -3}}}
)

// 数组元素查询
db.books.find({tags: "人权"})                               // 包含指定元素
db.books.find({tags: {$all: ["人权", "最美文学"]}})          // 同时包含多个元素

嵌套文档型数组(多值属性)

javascript 复制代码
// 商品多维标签(颜色、尺码、风格)
db.goods.insertMany([{
  name: "短袖衫",
  tags: [
    {tagKey: "size", tagValue: ["M", "L", "XL", "XXL", "XXXL"]},
    {tagKey: "color", tagValue: ["红色", "白色"]},
    {tagKey: "style", tagValue: "简约"}
  ]
}])

// 使用 $elemMatch 筛选 color=红色 的商品
db.goods.find({
  tags: {
    $elemMatch: {tagKey: "color", tagValue: "红色"}
  }
})

// 同时匹配 color=红色 AND size=XL
db.goods.find({
  tags: {
    $all: [
      {$elemMatch: {tagKey: "color", tagValue: "红色"}},
      {$elemMatch: {tagKey: "size", tagValue: "XL"}}
    ]
  }
})

4.5 固定集合(Capped Collection)

固定集合是一种限制大小的集合,遵循 FIFO 原则,当达到大小上限时自动覆盖最早的文档。

javascript 复制代码
// 创建固定集合(最多 10 条,4KB)
db.createCollection("logs", {capped: true, size: 4096, max: 10})

// 查看集合状态
db.logs.stats()

// 将普通集合转换为固定集合
db.runCommand({"convertToCapped": "mycoll", size: 100000})

适用场景

场景 说明
📝 系统日志 只需固定空间,旧日志自动淘汰
📊 TopN 数据 存储最新的 N 条数据(最新消息、最近浏览等)
📈 实时股票价格 存储价格变动信息,配合 tailable cursor 实现实时推送

实战:固定集合实现股票价格实时推送

javascript 复制代码
// 1. 创建消息队列(10MB)
db.createCollection("stock_queue", {capped: true, size: 10485760})

// 2. 创建时间索引(便于快速检索)
db.stock_queue.createIndex({timestamped: 1})

// 3. 生产者:模拟股票实时变动
function pushEvent() {
  while (true) {
    db.stock_queue.insert({
      timestamped: new Date(),
      stock: "MongoDB Inc",
      price: 100 * Math.random(1000)
    });
    print("publish stock changed");
    sleep(1000);
  }
}
pushEvent()

// 4. 消费者:监听实时股票变动(tailable cursor)
function listen() {
  var cursor = db.stock_queue.find({timestamped: {$gte: new Date()}}).tailable();
  while (true) {
    if (cursor.hasNext()) {
      print(JSON.stringify(cursor.next(), null, 2));
    }
    sleep(1000);
  }
}
listen()

💡 .tailable() 类似 Linux 的 tail -f,游标在获取完所有数据后不会关闭,而是持续等待新数据。


五、SpringBoot 整合 MongoDB

5.1 准备工作

1. 添加依赖

xml 复制代码
<!--spring data mongodb-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

2. 配置 yml

yaml 复制代码
spring:
  data:
    mongodb:
      uri: mongodb://fox:fox@192.168.65.174:27017/test?authSource=admin
      # 也可以分开配置:
      # database: test
      # host: 192.168.65.174
      # port: 27017
      # username: fox
      # password: fox
      # authentication-database: admin

3. 注入 MongoTemplate

java 复制代码
@Autowired
MongoTemplate mongoTemplate;

4. 集合操作示例

java 复制代码
@Test
public void testCollection() {
  boolean exists = mongoTemplate.collectionExists("emp");
  if (exists) {
    mongoTemplate.dropCollection("emp");  // 删除集合
  }
  mongoTemplate.createCollection("emp"); // 创建集合
}

5.2 文档操作注解

注解 作用域 说明
@Document 标注类为 MongoDB 文档,collection 属性指定集合名
@Id 成员变量 将字段值映射为文档的 _id
@Field 成员变量 指定字段映射的 key 名(默认为成员变量名)
@Transient 成员变量 标注字段不参与文档的序列化/反序列化

实体类示例

java 复制代码
@Document("emp")  // 对应 emp 集合中的一个文档
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

  @Id                      // 映射文档中的 _id
  private Integer id;
  @Field("username")       // 映射为 username 字段
  private String name;
  @Field
  private int age;
  @Field
  private Double salary;
  @Field
  private Date entryDay;
}

5.3 CRUD 操作代码

插入文档

java 复制代码
@Test
public void testInsert() {
  Employee employee = new Employee(1, "小明", 30, 10000.00, new Date());
  // save: _id 存在时更新,不存在时插入
  // mongoTemplate.save(employee);
  // insert: _id 存在时抛异常,支持批量插入
  mongoTemplate.insert(employee);

  List<Employee> list = Arrays.asList(
    new Employee(2, "张三", 21, 5000.00, new Date()),
    new Employee(3, "李四", 26, 8000.00, new Date()),
    new Employee(4, "王五", 22, 8000.00, new Date())
  );
  mongoTemplate.insert(list, Employee.class);
}

📌 insert vs save 区别

  • insert:重复 _id 时抛 DuplicateKeyException;支持一次批量插入(效率高)
  • save:已存在的文档会更新;多条数据需逐条调用(效率低)

查询文档

java 复制代码
@Test
public void testFind() {
  // 查询全部
  List<Employee> list = mongoTemplate.findAll(Employee.class);

  // 根据 _id 查询
  Employee e = mongoTemplate.findById(1, Employee.class);

  // 条件查询(薪资 > 8000)
  Query query = new Query(Criteria.where("salary").gte(8000));

  // 范围查询(薪资 4000 ~ 10000)
  // Query query = new Query(Criteria.where("salary").gt(4000).lt(10000));

  // 模糊查询
  // Query query = new Query(Criteria.where("name").regex("三"));

  // AND/OR 组合查询
  Criteria criteria = new Criteria();
  // AND:年龄>25 且 薪资>8000
  // criteria.andOperator(Criteria.where("age").gt(25), Criteria.where("salary").gt(8000));
  // OR:名字叫张三 或 薪资>5000
  criteria.orOperator(Criteria.where("name").is("张三"), Criteria.where("salary").gt(5000));
  Query query = new Query(criteria);

  // 排序 + 分页
  query.with(Sort.by(Sort.Order.desc("salary")))
       .skip(0)   // 跳过的记录数
       .limit(4); // 每页显示条数

  List<Employee> employees = mongoTemplate.find(query, Employee.class);
  employees.forEach(System.out::println);
}

// 使用 JSON 字符串查询
@Test
public void testFindByJson() {
  // 条件查询:年龄>25 或 薪资>=8000
  String json = "{$or:[{age:{$gt:25}},{salary:{$gte:8000}}]}";
  Query query = new BasicQuery(json);
  List<Employee> employees = mongoTemplate.find(query, Employee.class);
}

更新文档

java 复制代码
@Test
public void testUpdate() {
  Query query = new Query(Criteria.where("salary").gte(15000));

  Update update = new Update();
  update.set("salary", 13000);

  // updateFirst:只更新第一条匹配记录
  // mongoTemplate.updateFirst(query, update, Employee.class);

  // updateMulti:更新所有匹配记录
  // mongoTemplate.updateMulti(query, update, Employee.class);

  // upsert:没有匹配记录时新建
  // update.setOnInsert("id", 11);  // 指定 _id
  UpdateResult updateResult = mongoTemplate.upsert(query, update, Employee.class);
  System.out.println(updateResult.getModifiedCount());  // 修改的记录数
}

删除文档

java 复制代码
@Test
public void testDelete() {
  // 删除所有文档
  // mongoTemplate.remove(new Query(), Employee.class);

  // 条件删除(薪资 >= 10000)
  Query query = new Query(Criteria.where("salary").gte(10000));
  mongoTemplate.remove(query, Employee.class);
}

5.4 小技巧:去除 _class 字段

Spring Data MongoDB 默认会在文档中存储 _class 字段(Java 类全限定名)用于反序列化,如需去除:

java 复制代码
@Configuration
public class MongoConfig {

  /**
   * 配置 TypeMapper 去除 _class 字段
   */
  @Bean
  MappingMongoConverter mappingMongoConverter(
      MongoDatabaseFactory mongoDatabaseFactory,
      MongoMappingContext context,
      MongoCustomConversions conversions) {

    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
    MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, context);
    mappingMongoConverter.setCustomConversions(conversions);

    // 设置 DefaultMongoTypeMapper,将 typeKey 设置为 null(即不存储 _class)
    mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));

    return mappingMongoConverter;
  }
}

六、学习总结

核心知识点回顾

复制代码
MongoDB
├── 基础概念
│   ├── 文档数据库(BSON/JSON 模型)
│   ├── Database → Collection → Document → Field
│   └── 灵活半结构化,无强制 Schema
│
├── 安装部署
│   ├── Linux(CentOS 7)直接安装
│   ├── Docker 快速部署(推荐)
│   └── 安全认证(--auth + 用户权限体系)
│
├── CRUD 操作
│   ├── 插入:insertOne / insertMany
│   ├── 查询:find / findOne + 丰富操作符
│   ├── 更新:updateOne / updateMany / findAndModify
│   └── 删除:deleteOne / deleteMany / findOneAndDelete
│
├── 高级数据类型
│   ├── BSON(Binary JSON,比 JSON 类型更丰富)
│   ├── ObjectId(自带时间戳的唯一 ID)
│   ├── Date(UTC 存储)
│   ├── 内嵌文档 & 数组
│   └── 固定集合(capped collection)
│
└── SpringBoot 整合
    ├── spring-boot-starter-data-mongodb
    ├── @Document / @Id / @Field 注解
    ├── MongoTemplate API
    └── Criteria 条件构造

重要注意事项

  1. 🔑 安全 :生产环境务必开启 --auth,并为不同应用创建最小权限用户
  2. 📏 文档大小:单文档最大 16MB,嵌套层数不超过 100 层
  3. 时区:MongoDB 存储 UTC 时间,应用层需做 +8 处理
  4. 📄 分页:大数据量使用游标分页而非 skip/limit,避免性能问题
  5. 🗑️ 删除 :删除整个集合用 drop(),比 deleteMany({}) 更高效
  6. 🏷️ _class :SpringBoot 默认存储 _class 字段,可通过配置去除

📖 参考资料


学习目标:掌握 MongoDB 的核心概念、安装配置、CRUD 操作、数据类型及 SpringBoot 整合开发

参考资料:MongoDB 官方文档 https://www.mongodb.com/docs/manual/