CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
MongoDB 入门/课时/Spring Data MongoDB
RawBlame
课时目录12 节

MongoDB 入门

  1. 01MongoDB 是什么
  2. 02安装与 mongosh
  3. 03文档与 BSON
  4. 04CRUD 操作
  5. 05查询与投影
  6. 06索引
  7. 07聚合管道
  8. 08Spring Data MongoDB
  9. 09何时用 Mongo vs SQL
  10. 10Docker 运行 MongoDB
  11. 11Node.js 驱动与 Mongoose
  12. 12综合练习

Spring Data MongoDB

@Document、MongoRepository、与 JPA 对照。

40 分钟

Spring Data MongoDB

这一课你将能做到

  1. 在 Spring Boot 项目引入 spring-boot-starter-data-mongodb,用 spring.data.mongodb.uri 连接本地容器
  2. 编写 @Document 实体与 MongoRepository 接口,完成保存、派生查询与方法名查询
  3. 说清与 JPA/MySQL 共存时的边界:各管各库、单一权威源、禁止无对账双写

为什么要学

Java 主线在本站是 Spring Boot + MySQL + JPA。文档库要进后端,标准路径就是 Spring Data MongoDB:注解风格贴近 JPA(@Document 像 @Entity),Repository 方法名派生查询的体验几乎一致,心智切换成本极低。

工程上它最常见的落位:做题日志、操作审计、评论这类文档型集合的读写 API。本课要求你把 mongosh 里练过的操作翻译成 Java——并守住「MySQL 管权威、Mongo 管扩展」的架构边界。

人话:@Document 之于 Mongo 集合,就像 @Entity 之于 MySQL 表;MongoRepository 之于 Mongo,就像 JpaRepository 之于 MySQL。

先建立图像

同一个 Spring Boot 进程
  ├─ spring.datasource.*        → MySQL → JpaRepository     → cf_track / cf_lesson(权威)
  └─ spring.data.mongodb.uri    → Mongo → MongoRepository   → lesson_logs / comments(扩展)

概念对照表:

JPA / MySQLSpring Data MongoDB
@Entity@Document(collection = "…")
@Table(name)collection 属性
@Id@Id
JpaRepository<T, ID>MongoRepository<T, ID>
findBy… 派生查询同名习惯
EntityManager / JdbcTemplateMongoTemplate
@Transactional(MySQL)单文档原子;事务需副本集 + 事务管理器

跟着做

步骤 1:加依赖

Maven:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

步骤 2:只配 Mongo URI,别动 datasource

application-local.yml(密码走环境变量,勿提交真实凭据):

spring:
  data:
    mongodb:
      uri: mongodb://admin:secret@127.0.0.1:27017/coderoadmap_lab?authSource=admin

确认 spring.datasource.url 仍指向本机 MySQL 原样不动;启动前 Docker Mongo 已在跑(docker ps 验证)。

步骤 3:编写实体与 Repository

package com.example.lab.mongo;

import java.time.Instant;
import java.util.List;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.MongoRepository;

@Document(collection = "lesson_logs")
public class LessonLog {

    @Id
    private String id;              // 映射文档 _id(ObjectId 自动转 String)

    @Indexed                        // 启动时自动建索引(声明式)
    private String lessonSlug;

    private String userId;
    private String action;          // view | complete
    private Instant at;

    // 省略 getter/setter(或用 Lombok @Data)
}

interface LessonLogRepository extends MongoRepository<LessonLog, String> {
    List<LessonLog> findByUserIdOrderByAtDesc(String userId);
    long countByLessonSlugAndAction(String lessonSlug, String action);
}

步骤 4:跑一遍读写

在测试或 CommandLineRunner 里:

LessonLog log = new LessonLog();
log.setLessonSlug("mongo-spring");
log.setUserId("u_1001");
log.setAction("view");
log.setAt(Instant.now());
lessonLogRepository.save(log);

List<LessonLog> list =
    lessonLogRepository.findByUserIdOrderByAtDesc("u_1001");
// list 非空;mongosh 里 db.lesson_logs.find({ userId: "u_1001" }) 能对上

应看到:应用启动无 Mongo 报错;save 后 mongosh 可查到文档;lesson_logs 里多出 lessonSlug_1 索引。

步骤 5:方法名查询对照 mongosh

findByUserIdOrderByAtDesc("u_1001")
  ≈ db.lesson_logs.find({ userId: "u_1001" }).sort({ at: -1 })

countByLessonSlugAndAction("mongo-crud", "view")
  ≈ db.lesson_logs.countDocuments({ lessonSlug: "mongo-crud", action: "view" })

复杂聚合不在方法名里硬挤,交给 MongoTemplate:

// 服务层注入 MongoTemplate,实现聚合课的热度 Top5
Aggregation agg = Aggregation.newAggregation(
    Aggregation.match(Criteria.where("action").is("view")),
    Aggregation.group("lessonSlug")
        .count().as("views")
        .addToSet("userId").as("users"),
    Aggregation.project("views")
        .and("users").size().as("uniqueUsers"),
    Aggregation.sort(Sort.by(Sort.Direction.DESC, "views")),
    Aggregation.limit(5)
);
var results = mongoTemplate.aggregate(agg, "lesson_logs", HotCourseView.class);

步骤 6:画出双数据源边界

MySQL(JPA)  → 课程、课时、用户、做题进度(权威,schema.sql 管理)
MongoDB       → lesson_logs、comments、审计日志(扩展,集合约定管理)
禁止:同一实体(如 Track 标题)以两库为权威并双写
跨库联动:事件 / 消息队列 + 最终一致,可重建

讲清楚要点

  1. starter 自动装配 Mongo 客户端:URI 错误会在启动或首次访问时暴露;本地先用 mongosh 验证连通再改 Java 配置。
  2. @Document(collection = "…") 显式指定集合名:避免默认命名漂移,保证与 mongosh / Node 端共用同一集合时字段对得上。
  3. 方法名派生查询覆盖简单场景:findBy… / countBy… 直译成 MQL;统计报表用 MongoTemplate 聚合,别把方法名写成绕口令。
  4. @Indexed 是声明式建索引:相当于启动时替你执行 createIndex;生产更推荐迁移脚本显式管理,避免启动期大集合建索引的抖动。
  5. @Id 类型团队要统一:String(ObjectId 的十六进制)最常用;选中一种写进规范,避免 ObjectId 与 String 混用导致查询不命中。
  6. 与 JPA 共存 = 两套映射、两个库:不能跨库外键,关联靠应用层拼装或事件同步;权威数据的写路径只在 MySQL。
  7. 事务预期要摆正:单文档操作原子;跨文档事务需副本集 + MongoTransactionManager,本站实验库是单机容器——设计上优先用单文档原子性规避。

你可能会懵的地方

  • 实体能同时标 @Entity 和 @Document 吗? 绝不。那是把两个库的映射搅在一起,save 去哪都说不清。正确姿势:两个类、两个库、各自独立命名(如 Track 走 JPA,LessonLog 走 Mongo)。

  • JPA 的 @Transactional 对 Mongo 生效吗? 默认不生效。Mongo 单文档天然原子,无需事务注解;真要跨文档事务需注册 MongoTransactionManager 且连接的是副本集——实验环境单机容器跑不了。

  • Repository 扫描不到? 启动类包路径要覆盖接口所在包;跨包结构时显式 @EnableMongoRepositories(basePackages = "…")。

  • 字段名存进库后变了? 默认按 Java 字段名写库;与历史集合字段不一致时用 @Field("旧字段名") 显式映射,别改库里数据迁就代码。

  • 为什么日志类数据放 Mongo 而不是 MySQL 加一张表? 字段随业务演进频繁增减、写入吞吐高、按用户/课程范围查询即可满足——文档模型省去频繁 ALTER 与宽表 NULL 列;这是面试讲「技术选型」的现成素材。

错法对照

错法问题正解
Mongo URI 配进 spring.datasource.urlBoot 按 JDBC 连接直接失败spring.data.mongodb.uri
把 CfTrack 实体改造成 Mongo 权威主路径权威源被挪走JPA 管权威;Mongo 新增日志实体
两端字段名驼峰 / 下划线混乱跨端查询永远查不中统一契约 + @Document 显式集合名
生产密码写死在仓库 yml凭据泄露环境变量 / 配置中心
以为有 Repository 就不用建索引数据量上来全 COLLSCAN索引照建(@Indexed 或脚本),explain 验证
依赖 @Transactional 包住多文档写单机容器不支持,静默不回滚单文档原子建模;或升级副本集 + 事务管理器

卡点急救

  1. 现象:启动报 UnknownHostException 或连接超时 容器没起或 URI 主机端口错。先 db.runCommand({ ping: 1 }) 在 mongosh 里验通,再核对 yml 的 URI 逐字符一致。
  2. 现象:findBy… 查不到明明存在的数据 字段名不匹配(库里的 lesson_slug vs 实体的 lessonSlug)——加 @Field 映射;_id 类型不一致同理。
  3. 现象:集成测试跑一次第二次数据冲突 测试用独立库名(如 coderoadmap_lab_test)或 @DirtiesContext;更彻底用 Testcontainers 起临时 Mongo 容器,测试间完全隔离。

面试常问

  • @Document 与 @Entity 有什么区别? @Entity 映射 MySQL 表行,配合 JPA 事务与外键体系;@Document 映射 Mongo 集合文档,无外键、单文档原子、schema 由应用层约定。两者可共存于同一 Spring Boot 进程,但必须分属不同类、不同库、不同权威域。

  • Spring 里怎么实现「统计每个课时的浏览量 Top5」? MongoTemplate + 聚合:match(action=view) → group(lessonSlug).count() → sort(views desc) → limit(5);简单等值计数用 Repository 的 countByLessonSlugAndAction。答出「简单查询走 Repository、复杂管道走 Template」的分工即为完整。

  • 一个 Spring Boot 应用同时连 MySQL 和 MongoDB,要注意什么? 三点:① 配置键隔离(spring.datasource.* vs spring.data.mongodb.uri);② 权威域清晰,同一业务实体绝不双写双权威,跨库用事件最终一致;③ 各自的连接池、索引、事务模型独立评估(JPA 事务 vs Mongo 单文档原子/副本集事务)。

自测三问

  1. @Document 与 @Entity 落到存储上有何不同?能标在同一个类上吗?
  2. findByUserIdOrderByAtDesc 翻译成 mongosh 命令是什么?
  3. 为什么做题日志适合放 Mongo 而课程目录留在 MySQL?

小结一句话

@Document + MongoRepository 是 Spring 集成 MongoDB 的标准路径:简单查询走派生方法名、复杂统计走 MongoTemplate,与 JPA 共存时各管各库、权威数据仍在 MySQL。

延伸

  • Spring Data MongoDB 参考文档
  • Spring Boot 对 MongoDB 的支持
  • 下一课:何时用 Mongo vs SQL——把选型理由讲成面试答案。
加载本节练习…
← 上一课聚合管道下一课 →何时用 Mongo vs SQL