大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
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 | @Id |
JpaRepository<T, ID> | MongoRepository<T, ID> |
findBy… 派生查询 | 同名习惯 |
EntityManager / JdbcTemplate | MongoTemplate |
@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_slugvs 实体的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.*vsspring.data.mongodb.uri);② 权威域清晰,同一业务实体绝不双写双权威,跨库用事件最终一致;③ 各自的连接池、索引、事务模型独立评估(JPA 事务 vs Mongo 单文档原子/副本集事务)。
自测三问
@Document与@Entity落到存储上有何不同?能标在同一个类上吗?findByUserIdOrderByAtDesc翻译成 mongosh 命令是什么?- 为什么做题日志适合放 Mongo 而课程目录留在 MySQL?
小结一句话
@Document + MongoRepository 是 Spring 集成 MongoDB 的标准路径:简单查询走派生方法名、复杂统计走 MongoTemplate,与 JPA 共存时各管各库、权威数据仍在 MySQL。
延伸
- Spring Data MongoDB 参考文档
- Spring Boot 对 MongoDB 的支持
- 下一课:何时用 Mongo vs SQL——把选型理由讲成面试答案。