CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
Spring Data MongoDB
这一课你将能做到
在 Spring Boot 项目引入 spring-boot-starter-data-mongodb,用 spring.data.mongodb.uri 连接本地容器
编写 @Document 实体与 MongoRepository 接口,完成保存、派生查询与方法名查询
说清与 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 / MySQL Spring Data MongoDB @Entity@Document(collection = "…")@Table(name)collection 属性@Id@IdJpaRepository<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 标题)以两库为权威并双写
跨库联动:事件 / 消息队列 + 最终一致,可重建
讲清楚要点
starter 自动装配 Mongo 客户端 :URI 错误会在启动或首次访问时暴露;本地先用 mongosh 验证连通再改 Java 配置。
@Document(collection = "…") 显式指定集合名 :避免默认命名漂移,保证与 mongosh / Node 端共用同一集合时字段对得上。
方法名派生查询覆盖简单场景 :findBy… / countBy… 直译成 MQL;统计报表用 MongoTemplate 聚合,别把方法名写成绕口令。
@Indexed 是声明式建索引 :相当于启动时替你执行 createIndex;生产更推荐迁移脚本显式管理,避免启动期大集合建索引的抖动。
@Id 类型团队要统一 :String(ObjectId 的十六进制)最常用;选中一种写进规范,避免 ObjectId 与 String 混用导致查询不命中。
与 JPA 共存 = 两套映射、两个库 :不能跨库外键,关联靠应用层拼装或事件同步;权威数据的写路径只在 MySQL。
事务预期要摆正 :单文档操作原子;跨文档事务需副本集 + 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.url Boot 按 JDBC 连接直接失败 spring.data.mongodb.uri把 CfTrack 实体改造成 Mongo 权威 主路径权威源被挪走 JPA 管权威;Mongo 新增日志实体 两端字段名驼峰 / 下划线混乱 跨端查询永远查不中 统一契约 + @Document 显式集合名 生产密码写死在仓库 yml 凭据泄露 环境变量 / 配置中心 以为有 Repository 就不用建索引 数据量上来全 COLLSCAN 索引照建(@Indexed 或脚本),explain 验证 依赖 @Transactional 包住多文档写 单机容器不支持,静默不回滚 单文档原子建模;或升级副本集 + 事务管理器
卡点急救
现象:启动报 UnknownHostException 或连接超时
容器没起或 URI 主机端口错。先 db.runCommand({ ping: 1 }) 在 mongosh 里验通,再核对 yml 的 URI 逐字符一致。
现象:findBy… 查不到明明存在的数据
字段名不匹配(库里的 lesson_slug vs 实体的 lessonSlug)——加 @Field 映射;_id 类型不一致同理。
现象:集成测试跑一次第二次数据冲突
测试用独立库名(如 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 单文档原子/副本集事务)。
自测三问
@Document 与 @Entity 落到存储上有何不同?能标在同一个类上吗?
findByUserIdOrderByAtDesc 翻译成 mongosh 命令是什么?
为什么做题日志适合放 Mongo 而课程目录留在 MySQL?
小结一句话 @Document + MongoRepository 是 Spring 集成 MongoDB 的标准路径:简单查询走派生方法名、复杂统计走 MongoTemplate,与 JPA 共存时各管各库、权威数据仍在 MySQL。
延伸