预见猿份
主题
首页面试题模拟面试SQL练习在线工具关于我们老苗一对一私教学员评价
实战项目
零基础学习路线项目前置基础创新WMS项目Java微服务框架与实战云岚到家项目闪聚支付项目学成在线项目青橙电商项目JVM原理与实战调优分布式事务专题Java高频面试题MySQL从入门到精通达梦数据库从入门到实战MongoDB从入门到实战PostgreSQL从入门到实战TiDB从入门到实战Java数据结构与算法Java 并发编程(JUC)实战课程老苗一对一私教学员评价
blog我的会员
  • MongoDB从入门到实战|Java 程序员的文档数据库课程

    • 课程介绍
    • 第1章 认识MongoDB与环境搭建
    • 第2章 文档CRUD与Java Driver实战
    • 第3章 数据建模与Schema设计
    • 第4章 索引与性能优化
    • 第5章 聚合管道
    • 第6章 事务、并发与一致性
    • 第7章 Spring Data MongoDB实战
    • 第8章 副本集、分片与运维安全







----- 到底线了 -----

第1章 认识 MongoDB 与环境搭建 ​

🎯 本章学习目标 ​

学完本章,你应当能够:

  1. 说清 MongoDB 的定位,以及文档模型 vs 关系模型各自擅长什么、什么时候该换 MongoDB。
  2. 背下 MongoDB 与 MySQL 的概念对照表:数据库/集合/文档/字段 ↔ 库/表/行/列,_id/ObjectId ↔ 主键。
  3. 理解 BSON 文档结构、_id 唯一索引、无固定 schema 的含义。
  4. 在 Windows(MSI)或 Docker 上安装 MongoDB 8.x,用 mongosh 和 Compass 验证。
  5. 用 Java Driver 跑通第一次连接,登记后续章节要用的连接信息。

1.1 MongoDB 是什么:给 MySQL 同学的三分钟背景 ​

MongoDB 是一款开源的文档型 非关系型( NoSQL) 数据库,它把数据存成类似 JSON 的“文档”,而不是像关系型数据库那样存成行列分明的二维表。官方手册的原话是:MongoDB 将数据存储在类似 JSON 的灵活文档中,从而可以像应用程序代码使用数据一样轻松地对数据进行建模。

  • 主流版本:MongoDB 8.x(2024 起的主流发布线),学习用 Community 版免费;云上可直接用 MongoDB Atlas 免费 M0 集群,零安装。
  • 核心差异:MySQL 存「行」,MongoDB 存「文档」——一个文档就是一个自包含的 JSON 对象,把相关数据聚在一起。
  • 典型场景:商品目录(属性各异)、内容管理(文章+评论)、用户画像(标签集合)、订单(主单+多明细)、IoT 时序、移动/游戏后端。
  • 扩展性:原生分片(sharding)水平扩容,是它相对 MySQL 单机架构的杀手锏。

一句话认知

把 MongoDB 想成「用 JSON 存数据、按文档整块读写、天生好扩展的数据库」。你有 MySQL 的底子,学习重点不是「语法」,而是建模思维从「拆表」转向「聚合文档」——这是第 3 章的核心。

1.2 概念对照:MySQL 旧知识如何在 MongoDB「对号入座」 ​

这是 MySQL 程序员转 MongoDB 最重要的一张表,贯穿全程,建议贴在显示器边。

MySQLMongoDB差异要点(踩坑预警)
Database 数据库Database 数据库概念相同;库和集合都首次写入时自动创建,不用 CREATE DATABASE
Table 表Collection 集合集合无固定 schema:同一个集合里每行字段可以不一样
Row 行Document 文档文档是 BSON 对象,一行 = 一个自包含 JSON,可内嵌数组/子文档
Column 列Field 字段字段可有可无、类型可变(一个文档 age 是数字,另一个可以是字符串——不建议但允许)
主键 PRIMARY KEY_id 字段每个文档必有一个唯一的 _id,默认自动生成 ObjectId;_id 上自动建唯一索引
AUTO_INCREMENTObjectId / 自增序列ObjectId 是 12 字节全局唯一 ID(含时间戳),无需中心化发号,天然适合分布式
SQL SELECT * FROM t WHERE ...MQL db.collection.find(...)MongoDB 用「方法调用 + 查询对象」表达,不是 SQL 文本
JOIN 联表$lookup / 内嵌文档MongoDB 弱关联建模;能内嵌就别 JOIN(第 3、5 章)
information_schemalistCollections / $jsonSchema 校验schema 灵活,元数据概念不同
默认端口 3306默认端口 27017连接串、防火墙要记牢
root 用户无内置 root;自建 admin 用户鉴权默认关闭(本地开发),生产要开 auth(第 8 章)
binlogoplog副本集同步靠 oplog,思路类似 binlog(第 8 章)
InnoDB 引擎WiredTiger(默认,唯一生产引擎)不用选引擎,默认即 WiredTiger
mysqldumpmongodump / mongorestore逻辑备份,第 8 章
B+Tree 索引B-Tree 索引索引思想高度相似(第 4 章),经验可大量复用
事务(强)单文档原子 + 多文档事务(4.0+,副本集)事务能力有,但心智不同(第 6 章)

面试爱问:MongoDB 到底是不是「没有表结构」?

不是「没有结构」,而是「结构不强制、可变」。集合默认不校验字段,任意文档可增删字段;但你可以用 $jsonSchema 给集合加校验规则(第 3 章),把 MySQL 的「强 schema」找回来。答题时点出「灵活 schema ≠ 无 schema」是加分项。

1.2.1 一个文档长什么样 ​

对照 MySQL「一行」,看 MongoDB「一个文档」(这里是一条商品):

json
{
  "_id": ObjectId("665f1a2b9c1d4e5f6a7b8c9d"),
  "title": "预见猿份 Java 实战课程",
  "price": 199.00,
  "tags": ["Java", "面试", "实战"],
  "stock": { "total": 100, "locked": 3 },
  "skus": [
    { "skuId": "A-001", "spec": "电子版", "price": 199.00 },
    { "skuId": "A-002", "spec": "纸质版", "price": 249.00 }
  ],
  "createTime": ISODate("2026-01-01T10:00:00Z")
}
1
2
3
4
5
6
7
8
9
10
11
12

MySQL 要表达这条数据,得拆成 商品表 + 标签表 + SKU表 + 库存表 四张表 JOIN;MongoDB 一个文档全装下——这就是「文档」二字的分量,也是建模的出发点。

这条文档怎么写进库里?mongosh 与 Java 两种外壳对照(后续所有章节的示例都会这样成对给出):

javascript
// mongosh:一条命令直接插入
const product = {
  title: "预见猿份 Java 实战课程",
  price: 199.00,
  tags: ["Java", "面试", "实战"],
  stock: { total: 100, locked: 3 },
  skus: [
    { skuId: "A-001", spec: "电子版", price: 199.00 },
    { skuId: "A-002", spec: "纸质版", price: 249.00 }
  ],
  createTime: new Date("2026-01-01T10:00:00Z")
};
db.t_product.insertOne(product);
1
2
3
4
5
6
7
8
9
10
11
12
13
java
// Java Driver:Document 就是可嵌套的 Map,结构一一对应
Document product = new Document("title", "预见猿份 Java 实战课程")
        .append("price", 199.00)
        .append("tags", List.of("Java", "面试", "实战"))
        .append("stock", new Document("total", 100).append("locked", 3))
        .append("skus", List.of(
                new Document("skuId", "A-001").append("spec", "电子版").append("price", 199.00),
                new Document("skuId", "A-002").append("spec", "纸质版").append("price", 249.00)))
        .append("createTime", new Date());
db.getCollection("t_product").insertOne(product);
1
2
3
4
5
6
7
8
9
10

仔细观察:mongosh 里的 {键: 值} 对象,与 Java 里的 new Document(键, 值),装的是同一个 BSON 结构——这正是本书「一种操作、两种写法」互译的基础。

1.2.2 BSON 类型速览(对照 MySQL 列类型) ​

BSON 类型说明MySQL 对应
Double / Int32 / Int64 / Decimal128数值DECIMAL / INT / BIGINT(金额推荐 Decimal128)
StringUTF-8 字符串VARCHAR/TEXT
Object / Array子文档 / 数组无对应,正是文档模型的杀手锏
Boolean布尔TINYINT(1)
Date (ISODate)日期时间(毫秒)DATETIME/TIMESTAMP
ObjectId12 字节唯一 ID自增主键的分布式替代
Null / Undefined空 / 未定义NULL

1.3 安装 MongoDB 8.x ​

1.3.1 Windows 安装(MSI,学习推荐) ​

注意:MongoDB 新版仅支持 64 位 Windows Server 2022 / Windows 11,且不支持 WSL 跑 mongod。老系统建议改用下面的 Docker 方式。

  1. 从 MongoDB Download Center 选 Community Server 8.x + Windows x64 MSI 下载。
  2. 运行 MSI,安装类型选 Complete,安装到默认 C:\Program Files\MongoDB\Server\8.x\。
  3. 配置 Service 页勾选 "Run as Network Service"(或指定数据/日志目录),安装器会自动把 mongod 注册成 Windows 服务并开机自启。
  4. 数据默认在 C:\Program Files\MongoDB\Server\8.x\data\db,日志在 logs\mongod.log。
  5. mongosh(命令行)与 Compass(图形)需单独下载安装,不随 Server 自带。

验证服务:sc query MongoDB 或在「服务」里看 MongoDB 是否 Running。

1.3.2 Docker 安装(跨平台/CI 首选,你本机是 Windows 也能用) ​

bash
# 启动一个 MongoDB 容器(Community 官方镜像),映射 27017
docker run --name mongodb -p 27017:27017 -d mongodb/mongodb-community-server:latest

# 验证容器在跑
docker container ls

# 用 mongosh 连进去(宿主机装了 mongosh 就直接连 localhost:27017;或在容器里执行)
mongosh --port 27017

# 后续第6、8章要做「副本集事务/复制」实验时,用这个单节点副本集起法
docker run --name mongo-rs -p 27017:27017 -d mongodb/mongodb-community-server:latest --replSet rs0
1
2
3
4
5
6
7
8
9
10
11

MongoDB 5.0+ 的 Docker 镜像要求 CPU 支持 AVX 指令集(近十年 CPU 基本都支持)。

1.3.3 MongoDB Atlas(免安装,云端 5 分钟) ​

如果只想练 API、不想折腾本地环境:注册 Atlas 云账号 → 创建 M0 Free Tier 集群 → 「Connect → Java」拿到形如 mongodb+srv://user:pass@cluster0.xxxx.mongodb.net/ 的连接串,直接跳到 1.5 用 Java 连。

1.4 mongosh 与 Compass:两个必会工具 ​

装完先记住对照:mongosh ≈ mysql 命令行客户端,Compass ≈ Navicat/DBeaver。

1.4.1 mongosh 三板斧 ​

javascript
// 连接:mongosh "mongodb://localhost:27017"  进去后:
show dbs                          // 看所有库(对照 MySQL SHOW DATABASES)
use yjoffer                       // 切库/建库(库不存在则首次写入时创建)
db.showCollections()              // 看集合(对照 SHOW TABLES)

// 插入一条
db.t_user.insertOne({ _id: 1, username: "tom", age: 20 })
db.t_user.find({ age: { $gte: 18 } })           // 查询(对照 SELECT ... WHERE age>=18)
db.t_user.updateOne({ _id: 1 }, { $set: { age: 21 } })
db.t_user.drop()                                 // 删集合(对照 DROP TABLE)

// 元信息
db.version()                       // 服务端版本
db.runCommand({ serverStatus: 1 }) // 服务状态(对照 SHOW STATUS)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

上面「三板斧」里的每一步,用 Java Driver 写出来是完全对应的形状(Filter 对应查询对象、$set 更新文档原样照搬):

java
MongoClient client = MongoClients.create("mongodb://localhost:27017");
MongoDatabase db = client.getDatabase("yjoffer");                    // 对应 use yjoffer
MongoCollection<Document> users = db.getCollection("t_user");        // 对应 db.t_user

// mongosh: db.t_user.insertOne({ _id: 1, username: "tom", age: 20 })
users.insertOne(new Document("_id", 1).append("username", "tom").append("age", 20));

// mongosh: db.t_user.find({ age: { $gte: 18 } })
users.find(Filters.gte("age", 18)).into(new ArrayList<>());

// mongosh: db.t_user.updateOne({ _id: 1 }, { $set: { age: 21 } })
users.updateOne(Filters.eq("_id", 1), new Document("$set", new Document("age", 21)));

// mongosh: db.t_user.drop()
users.drop();

// mongosh: show dbs
db.listCollectionNames();                                            // 对应 db.showCollections()
for (String name : client.listDatabaseNames()) System.out.println(name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

从 mysql 客户端切过来要改的两个手感

  1. MongoDB 默认不需要先 CREATE:use 新库 + 往新集合 insertOne 一条,库和集合就都建好了。
  2. 查询不是写 SQL 字符串,而是调用 find(filterDoc, projectionDoc) 方法,参数是 JS 对象——这套对象语法和 Java Driver 里的 Filters/Projections 完全一一对应,第 2 章会打通。

1.4.2 Compass 图形化 ​

用连接串 mongodb://localhost:27017 连接后,左侧选库/集合,可视地浏览文档、执行查询、看索引与聚合管道预览、看执行计划(第 4 章会用到它的 EXPLAIN 视图)。学习阶段强烈建议开着它——把抽象的文档/索引变成看得见的界面。

1.5 Java 首次连接:跑通再登记信息 ​

引入官方同步驱动(Maven 中央仓库可直接拉取,版本以最新 5.x 为准):

xml
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>5.2.0</version>
</dependency>
1
2
3
4
5
java
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class FirstConnect {
    public static void main(String[] args) {
        // 连接串:本地默认 mongodb://localhost:27017,Atlas 用 mongodb+srv://...
        try (MongoClient client = MongoClients.create("mongodb://localhost:27017")) {
            MongoDatabase db = client.getDatabase("yjoffer");   // 库不存在会自动延迟创建

            // 插入一条,证明连接通了
            db.getCollection("t_health").insertOne(new Document("ping", 1));

            // 读回来
            Document one = db.getCollection("t_health").find().first();
            System.out.println("连接成功,读到: " + one.toJson());

            // 打印服务端信息,确认版本
            System.out.println("当前库: " + db.getName());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

验证连接是否真的写进去了?回到 mongosh 读一遍(两个工具操作的永远是同一份数据,互为验证工具):

javascript
use yjoffer
db.t_health.find()          // 应能看到 { ping: 1 } 那条文档
db.t_health.drop()          // 体检完成,清理掉
1
2
3

跑通后登记第 2~7 章要用的信息:

  • 连接串:mongodb://localhost:27017(本地)或 Atlas 的 mongodb+srv://...
  • 练习库名:yjoffer
  • 驱动:mongodb-driver-sync 5.x,核心入口类 MongoClients / MongoClient / MongoDatabase / MongoCollection

线程模型提醒(对照 MySQL 连接池)

MongoClient 本身是线程安全、内含连接池的,一个应用通常只创建一个实例全程复用——别像每次 new Connection 那样每次 new client。这点在 Spring 集成(第 7 章)里由框架自动管理。

1.6 本章小结 ​

  1. MongoDB 用文档存数据,一个文档自包含一整块业务数据;建模从「拆表」转向「聚合」(第 3 章)。
  2. 概念对照记死:库/集合/文档/字段 ↔ 库/表/行/列;_id(ObjectId) ↔ 主键;27017 ↔ 3306;oplog ↔ binlog;WiredTiger ↔ InnoDB。
  3. 环境搭建三条路:Windows MSI / Docker / Atlas 免费集群;工具两件套 mongosh + Compass。
  4. Java 侧一个 MongoClient 全局复用,getDatabase → getCollection 就能读写。

✍️ 课后练习 ​

  1. 用 1.2 的对照表,把你在 MySQL 里最熟的一张表「口述」成 MongoDB 文档,指出哪些字段可以内嵌成数组/子文档。
  2. 在 mongosh 里:新建库 yjoffer、集合 t_student,插入 3 个字段不完全相同的文档,再 find() 全部查出来,观察「同一集合不同字段」的表现。
  3. 用 Docker 起一个带 --replSet rs0 的容器(为第 6 章事务做准备),并用 Java 程序连上它写入一条数据。
  4. (思考题)为什么 MongoDB 能在「无固定 schema」的前提下仍然保证每个文档有唯一主键?_id 起了什么作用?

📖 参考答案 ​

第 1 题:把 MySQL 表「口述」成 MongoDB 文档

拿最常见的「用户三件套」演示,第 1 步列出输入(MySQL 里的一行主表 + 两行从表):

sql
-- t_user:      (id=1, username='tom', pwd='***', status=1)
-- t_profile:   (user_id=1, city='杭州', bio='Java 工程师')
-- t_address:   (user_id=1, addr='西湖区A号'), (user_id=1, addr='滨江区B号')
1
2
3

第 2 步问自己:这三张表是不是「总是一起读」?(用户详情页一次全加载)——是 → 合并成一个文档:

javascript
// users 集合里的一个文档(输出形状)
{
  _id: ObjectId("68f3..."),            // ← t_user.id 换成 ObjectId,或直接用业务主键 1
  username: "tom", pwd: "***", status: 1,
  profile: { city: "杭州", bio: "Java 工程师" },          // 1:1 → 子文档内嵌
  addresses: [ "西湖区A号", "滨江区B号" ]                 // 1:N 少 → 数组内嵌
}
1
2
3
4
5
6
7

第 3 步标出反例:如果某个从表「独立高频写」或「无界增长」(如用户登录日志),就不能内嵌,要拆独立集合存 userId 引用——判据到第 3 章展开。

第 2 题:mongosh 建库建集合 + 异构字段文档

第 1 步:连上本地服务并切库(库不存在不用 CREATE,首次写入自动实体化):

javascript
use yjoffer
1

第 2 步:插 3 个字段不完全相同的文档:

javascript
db.t_student.insertOne({ name: "amy", age: 20, major: "CS" })
db.t_student.insertOne({ name: "bob", age: 22 })                    // 没有 major
db.t_student.insertOne({ name: "coco", major: "数学", extra: { gpa: 3.8 } })  // 没有 age,多了嵌套字段
1
2
3

第 3 步:查出来观察「schema 自由」:

javascript
db.t_student.find().pretty()
// 输出:三条文档字段各不相同,缺的字段不是 NULL,是「根本没这个键」
// 对照 MySQL:同表所有行必须同列,缺值只能 NULL——这就是「无固定 schema」的第一现场
1
2
3

第 4 步(加验):db.t_student.find({ age: { $exists: false } }) 能精确查出 bob——这个手感第 2 章会反复用到。

第 3 题:Docker 起副本集容器 + Java 写入

第 1 步:启动单节点副本集(与 standalone 只差一个参数):

bash
docker run -d --name mongo-rs -p 27017:27017 mongo:8.0 mongod --replSet rs0
1

第 2 步:初始化副本集配置(只做一次,不初始化服务不接受读写):

bash
docker exec mongo-rs mongosh --eval "rs.initiate({_id:'rs0', members:[{_id:0, host:'localhost:27017'}]})"
# 提示符会从 PRIMARY> 出现,说明本节点已当选主节点
1
2

第 3 步:Java 连接写入并读回(代码与 1.4.1 体检程序一致,连接串不用改):

java
try (MongoClient client = MongoClients.create("mongodb://localhost:27017/yjoffer?directConnection=true")) {
    MongoCollection<Document> coll = client.getDatabase("yjoffer").getCollection("t_rs_test");
    coll.insertOne(new Document("from", "java").append("ok", 1));
    System.out.println(coll.find().first().toJson());
}
// 预期输出:{"_id": ObjectId("68f3..."), "from": "java", "ok": 1}
1
2
3
4
5
6

单机副本集用 directConnection=true 避开驱动拓扑探测;第 6 章事务环境就用这个容器,不用再折腾。

第 4 步:mongosh 交叉验证 db.t_rs_test.find() 能看到同一条数据,然后 drop 清理。

第 4 题(思考题)参考思路

要点三条:① 「无 schema」只约束业务字段,_id 是系统级硬性字段——每个文档必须有且集合自带 _id 唯一索引(不可删);② 插入时不写 _id,驱动/服务端自动生成 ObjectId(12 字节 = 4s 时间戳 + 5 随机值 + 3 自增计数器),同机器多进程也不碰撞;③ 本质上它是「主键约束 + 自动生成器」的结合,MySQL 里这两件事靠 AUTO_INCREMENT + PRIMARY KEY 声明完成,MongoDB 把它内置进了类型系统。

下一章我们进入正题:用 Java Driver 完成文档的增删改查,并把 MySQL 的 WHERE / 投影 / 排序 / 分页逐条翻译过来。

← 课程介绍第2章 文档CRUD与Java Driver实战 →








如果发现文档内容有错误或排版错乱,请及时联系站长老苗修改,不胜感激。联系我们
关于我们 | 隐私政策 | 豫ICP备2026003386号-4 | 豫公网安备41010202004008号
目录

本页无章节