1. 先说结论:为什么要把 MongoDB 接进 Spring Boot
最近在折腾一个数据增长比较快的业务模块,关系型数据库在几张表 join 之后越来越吃力,尤其是文档型数据的读写,字段结构还不固定。于是把目光放到了 MongoDB 上,顺手在 Spring Boot 项目里把它完整集成了一遍。这里不聊“MongoDB 和 MySQL 谁更好”的月经话题,只讲实际集成过程中踩过的坑、被忽略的细节,以及一套可以直接落到代码里的方案。
这套集成的核心价值在于:Spring Boot 本身已经提供了非常成熟的 MongoDB 支持,但真正用好它需要理解几个关键概念——数据库、集合、文档这三层结构到底对应 Java 里的什么;Spring Data MongoDB 又帮我们做了多少事情;以及那些网上教程一句话带过的配置和索引、事务、连接池细节,在真实项目里会怎么坑人。
如果你正在用 Spring Boot 写一个需要存储日志、用户行为、商品信息、配置快照这类半结构化数据的项目,或者单纯想把 MongoDB 引入现有工程试试水,这篇内容基本能覆盖从依赖引入到生产排查的完整路径。我默认你已经有 Spring Boot 的基础,MongoDB 至少装好并能启动,如果还没装,后面也会补充安装和验证的思路。
2. 项目准备与依赖引入
2.1 版本选型:Spring Boot 与 MongoDB 驱动的匹配
老生常谈的版本问题,但偏偏最致命。Spring Boot 不是越高越好,Spring Data MongoDB 和 MongoDB 服务端也有对应的兼容关系。我这次用的是 Spring Boot 2.7.18,配 MongoDB 6.0 社区版,驱动由 Spring Boot 自动管理,不手动指定版本。
先说一个我踩过的坑:最开始图新鲜上了 Spring Boot 3.2.x,结果发现 Jakarta 包名替换只是冰山一角,Spring Data MongoDB 4.x 对 MongoDB 服务端的最低版本要求、驱动 API 的弃用方法都变了,旧代码里好几个类直接编不过。如果你的项目还留在 Spring Boot 2.x,别急着升,先确认业务真的需要新特性。如果必须升 3.x,做好全面回归测试。
依赖只需要一个 starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>这个依赖会引入 spring-data-mongodb 和 mongodb-driver-sync,所有基础的 MongoClient、MongoTemplate、Repository 支持都在里面了。不需要额外加 mongodb-driver 的独立依赖,除非你要用响应式流(那是另一个 starter 的事)。
2.2 连接配置:不仅仅是“填个地址”那么简单
很多人写配置就三行:地址、库名、端口。真实项目里远远不够。先展示我用在 application.yml 里的一套基础配置:
spring: data: mongodb: uri: mongodb://admin:password@192.168.1.100:27017/mydb?authSource=admin&replicaSet=rs0 auto-index-creation: true uuid-representation: standard这里有几个被忽略的重点:
uri里如果开启了副本集,必须写上replicaSet=rs0,否则 Spring Data 默认情况下连接不启用副本集探测(虽然有serverSelectionTimeoutMS兜底,但一旦主节点切换,客户端不会优雅处理)。authSource=admin是指定认证库。很多人用户名密码都对,但认证库默认为当前库,导致鉴权失败。uuid-representation不设置时,Spring Data MongoDB 默认使用 legacy UUID,和 MongoDB 官方驱动写入的 UUID 字段没法兼容,尤其当你要用第三方工具(比如 NoSQLBooster 或其他语言客户端)读数据时会看到一堆乱码。建议统一为 standard。auto-index-creation建议开发环境开启,生产环境用迁移脚本管理索引,别靠这个属性自动创建。这个是后话,到索引章节细说。
如果你用的是多环境配置,建议把 uri 放到环境变量里,不要硬编码。数据库连接字符串里包含账号密码,一旦进入 Git 历史就很难彻底清除。
2.3 服务端安装与连通性验证
mongodb 安装失败是热搜里的高频词,macOS 用户用 brew 装容易遇到权限问题,Windows 用户经常卡在 service 启动。这里给一个最小验证路径:
- 确认 mongod 进程在运行,监听 27017 端口。
- 在命令行执行
mongosh --eval "db.runCommand({ ping: 1 })",返回ok: 1即服务正常。 - 再用项目配置的账号密码连接一次,确认 authSource 正确。
一个常见的坑:安装后没启动服务,Spring Boot 启动报Timed out after 30000 ms或ConnectException,然后误以为是连接参数的问题。先命令行,再代码排查,顺序别反。
3. 数据建模:Collection 和 Document 在 Java 里的映射方式
MongoDB 的数据结构是用 JSON 风格文档组成的,核心概念就三个:数据库、集合、文档。集合相当于关系数据库里的表,但它不强制字段一致;一个集合里的每篇文档字段可以不同,这就是它处理灵活业务数据时的最大优势。不过,自由的代价是,数据模型设计得不好,查询时就会极其痛苦。
3.1 实体类定义与注解的取舍
Spring Data MongoDB 的实体映射和 JPA 很像,但注解语义不同。看一个实际例子:
@Document(collection = "user_profile") public class UserProfile { @Id private String id; @Indexed(unique = true) private String userId; @Field("user_name") private String userName; private List<String> tags; private Address address; private Map<String, Object> extraAttrs; private Instant createdAt; }说明一下选择理由:
@Document里的 collection 名称建议显式用复数下划线命名,避免依赖类名转换规则,明确集合边界。@Id字段类型尽量用 String,让 MongoDB 生成 ObjectId 自动转换为字符串,避免在 JSON 序列化和前端传参时产生类型不一致。除非你要存储自定义业务主键,否则别用 Long 类型作 _id。@Field("user_name")用来映射驼峰字段和数据库下划线字段。有人图省事不映射,直接在 MongoDB 里存驼峰字段,其实也行,但团队协作时统一风格更好。Map<String, Object>适合存不确定的扩展属性,这是 MongoDB 相对关系型的一大优势。但注意,这种字段无法被索引,查询也只能全表扫描,如果这个字段将来要参与查询,尽早拆成具体字段。
3.2 嵌套文档与无限深度的陷阱
MongoDB 的文档可以嵌套,实体类里也能直接写嵌套对象。比如上面的 Address 就是一个普通 POJO,不需要 @Document 注解。嵌套对象的存储默认是内嵌文档,不是引用。
这里有一个重要决策:深嵌套还是扁平化。我的建议是嵌套不超过三层,超过三层就拆成独立集合或用引用关联。原因很直接:MongoDB 更新嵌套数组中的某个元素时,需要使用位置操作符和聚合管道,Spring Data 的便捷方法很难覆盖所有场景,最后还是要手写巨复杂的 Query。
反例见过不少,例如把整个订单商品快照、操作日志全塞在一个文档里,单个文档撑到几 MB,查询和写入性能都会受影响。MongoDB 单文档大小上限是 16MB,但业务上超过 10MB 的文档基本就是设计失误了。
4. 数据访问:MongoTemplate 与 Repository 到底选哪个
Spring Data MongoDB 提供两种主要访问方式:MongoRepository和MongoTemplate。新手容易纠结,老手其实都混着用。我的经验是:常规单集合 CRUD 用 Repository,复杂查询、聚合、字段裁剪更新用 MongoTemplate。比例大概 7:3。
4.1 基于 Repository 的常见操作
定义接口继承 MongoRepository,Spring 会自动实现方法名解析:
public interface UserProfileRepository extends MongoRepository<UserProfile, String> { Optional<UserProfile> findByUserId(String userId); List<UserProfile> findTop10ByTagsContainingOrderByCreatedAtDesc(String tag); long countByAddressCity(String city); }方法名解析规则其实很好理解,就是把字段名、查询关键字按驼峰拼起来。findByXxxAndYyy、findByXxxOrYyy、IsGreaterThan、Between等自动生成查询逻辑。这里想提醒的是,方法名过长会降低可读性,比如findAllByStatusAndTypeAndCreateTimeBetweenAndDeletedIsNull——这种查询我建议直接用 Query 注解或者模板查询,别硬凹命名。
自定义查询用@Query:
@Query("{ 'tags': { '$all': ?0 }, 'status': { '$ne': 'disabled' } }") List<UserProfile> findByAllTags(List<String> tags);JSON 查询串里的$符号在 Java 注解里不需要转义,因为是字符串。这个写法直观,但也容易写错,建议先在 MongoDB Compass 或 mongosh 里验证同样的语句能跑通,再粘到注解里。
4.2 基于 MongoTemplate 的高级查询
当查询条件动态拼接时,Repository 就很笨拙了。MongoTemplate 配合 Query 和 Criteria 非常顺手:
@Autowired private MongoTemplate mongoTemplate; public Page<UserProfile> search(String keyword, Integer minAge, int page, int size) { Query query = new Query(); if (StringUtils.hasText(keyword)) { query.addCriteria(Criteria.where("userName").regex(keyword, "i")); } if (minAge != null) { query.addCriteria(Criteria.where("age").gte(minAge)); } long total = mongoTemplate.count(query, UserProfile.class); query.with(Sort.by(Sort.Direction.DESC, "createdAt")) .skip((long) page * size) .limit(size); List<UserProfile> list = mongoTemplate.find(query, UserProfile.class); return PageableExecutionUtils.getPage(list, PageRequest.of(page, size), () -> total); }这里的skip + limit实现分页在数据量大了以后有性能问题,深分页建议用_id游标方式,下面排查章节会提。动态查询是 MongoTemplate 的核心场景,不建议为了省事把所有条件封装成万能方法,查询可读性同样重要。
4.3 写入策略:save 还是 insert
数据库操作里插入和更新有着微妙差别。MongoTemplate 的save方法会执行 upsert:如果_id不存在则插入,存在则整体替换。而insert只插入,如果_id冲突会抛 DuplicateKeyException。
我的使用习惯是:新增操作明确用insert,修改操作明确用updateFirst或findAndModify。原因很简单,save全量替换的语义容易覆盖并发下其他字段的修改,而且业务上无法区分新增和更新的场景,容易造成脏数据。如果希望部分字段更新,用 Update 对象指定$set:
Query query = Query.query(Criteria.where("userId").is("u123")); Update update = new Update().set("userName", "新名字").set("updatedAt", Instant.now()); mongoTemplate.updateFirst(query, update, UserProfile.class);一行代码解决字段级更新,不触碰其他数据。这个写法严格讲只更新第一个命中文档,如果你的条件可能命中多条,注意用updateMulti并仔细确认条件密度。
5. 索引设计与常见操作禁忌
5.1 索引是查询性能的生命线
MongoDB 单集合的数据增长是线性的,没有索引的查询就是全表扫,到了几百万文档就会出现几百毫秒甚至秒级的响应。做索引要建立在真实查询模式上,别一股脑给所有字段加索引。我一般在设计阶段就把常用查询条件定下来,然后优先为以下三类字段建索引:
- 等值查询字段:比如
userId、status。 - 排序字段:比如
createdAt降序。 - 范围查询字段:比如
age、price。
组合索引顺序有讲究,基本原则是“等值在前,排序其次,范围最后”。举个反例:如果查询条件是status = 1 AND age > 20 ORDER BY createdAt DESC,建一个(status, createdAt, age)比(age, status, createdAt)更合理,因为 age 的范围条件限制了后续字段无法有效走索引。
注解方式建索引适合开发期,但生产环境推荐用 MongoDB 的迁移脚本或者在应用启动时用 Java 代码校验索引。Spring Data 的@Indexed注解配auto-index-creation: true确实省事,但问题是生产环境如果已有存量数据,自动建索引会阻塞集合操作,别在高峰期开启新索引。
5.2 索引冲突与唯一索引的坑
唯一索引在 Spring Data 里用@Indexed(unique = true)声明,但要注意它的行为:如果集合里已经有重复数据,创建索引会直接失败,应用启动时可能会因为索引创建失败而抛异常。真实的场景是,历史脏数据导致上线失败,而不是代码问题。
排查思路很简单:到 MongoDB 控制台手动执行创建索引,观察错误提示,先清重再建索引。另外,唯一索引对缺失字段的处理是:多个文档都不含该字段,则索引不会因为 null 而冲突,这个行为和 MySQL 的唯一约束不太一样,有人会在这里被误导。
5.3 删除操作别犯的错
热搜里“文档数据在 MongoDB 中的查询和删除”出现得很频繁,很多人踩过的坑是误删。MongoDB 的删除是不可逆的,没有事务能救回已提交的数据。分享一个安全策略:
Query query = Query.query(Criteria.where("userId").is("u456")); UserProfile deleted = mongoTemplate.findAndRemove(query, UserProfile.class);findAndRemove会在删除前把被删文档返回来,至少保留一份证据。如果要批量删除,先用 count 确认数量,再执行deleteMulti,别直接用remove(Query)闷头删。更保险的做法是逻辑删除:加一个deleted字段,查询时统一过滤,默认 SQL 风格。MongoDB 物理删除的优势是释放空间,但如果业务上存在审计需求,逻辑删除是更通用的方案。
6. Spring Boot 中 MongoDB 的典型功能扩展
6.1 自定义自动配置:读取外部配置并组装 MongoClient
“Spring Boot 自定义自动配置”经常被搜索引擎收录,但应用场景到底在哪?我这里举例:多套 MongoDB 环境,或者希望在应用启动时对 MongoDB 客户端统一设置查询超时、连接池大小,自动配置就派上用场。
不用重复造轮子,Spring Boot 已经提供了MongoClientSettings的可定制入口。通过AbstractMongoClientConfiguration重写方法:
@Configuration public class MongoConfig extends AbstractMongoClientConfiguration { @Value("${spring.data.mongodb.uri}") private String uri; @Override public MongoClient mongoClient() { MongoClientSettings settings = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(uri)) .applyToConnectionPoolSettings(builder -> builder.maxSize(50).minSize(5).maxWaitTime(5, TimeUnit.SECONDS)) .applyToSocketSettings(builder -> builder.connectTimeout(5, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS)) .build(); return MongoClients.create(settings); } @Override protected String getDatabaseName() { return new ConnectionString(uri).getDatabase(); } }这里面applyToConnectionPoolSettings是值得注意的,默认连接池 maxSize 是 100,但在高并发尖峰场景如果不设置最大等待时间,客户端可能因为线程全部卡在等待连接上而雪崩。给连接和读取设超时是生产环境的基本要求,否则一条慢查询能把整个服务的线程池拖死。
6.2 集成测试内嵌 MongoDB
测试是个容易被略过但价值极高的模块。真实项目里我使用de.flapdoodle.embed.mongo来做集成测试,它会在 JVM 进程内启动一个真实的 MongoDB 实例。依赖是:
<dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.mongo</artifactId> <scope>test</scope> </dependency>版本匹配仍需注意,Spring Boot 2.7 对应的 embed mongo 版本已经能正常支持 MongoDB 4.x 模拟。测试类长这样:
@DataMongoTest class UserProfileRepositoryTest { @Autowired private UserProfileRepository repository; @Test void shouldFindByUserId() { // 准备数据,执行断言 } }@DataMongoTest只加载 MongoDB 相关配置,不会把整个应用上下文全拉起来,测试速度比较理想。不要把生产数据库拿来跑测试,这是底线,后果不用多说。
6.3 聚合查询:把 group by 搬到 MongoTemplate
当报表统计需要按字段分组、计算总数、平均值时,MongoTemplate 的聚合能力很香。下面是一次按城市统计用户数量并筛选超过 10 人的聚合:
Aggregation agg = Aggregation.newAggregation( Aggregation.match(Criteria.where("status").is("ACTIVE")), Aggregation.group("address.city").count().as("total"), Aggregation.match(Aggregation.group("total").greaterThan(10L).toDocument()), Aggregation.sort(Sort.by(Sort.Direction.DESC, "total")) ); List<CityStat> result = mongoTemplate.aggregate(agg, "user_profile", CityStat.class).getMappedResults();聚合操作里 Pipeline 的顺序决定结果,$match放在最前面能尽早过滤数据,减少后续阶段处理量。熟练使用聚合还能避免把大量数据拉到 Java 内存再手工统计,这是性能的巨大差异。
7. 实战案例:用户行为日志存储与分析
为了把前面的内容串起来,这里用一个相对完整的场景:用户行为日志服务,接收前端上报的事件数据,存储到 MongoDB,同时支持按时间和事件类型分析。这个案例在真实业务中非常典型,也是 MongoDB 比关系型数据库更合适的领域。
7.1 文档结构与集合设计
日志数据字段高度动态,不同事件携带不同参数,所以用 Map 存扩展字段很适合。文档结构如下:
@Document(collection = "user_behavior_log") public class BehaviorLog { @Id private String id; private String userId; private String eventType; private Map<String, Object> payload; private Instant happenedAt; }索引设计上,最核心的查询模式是“按用户查最近行为”和“按事件类型和时间范围统计”,因此建立组合索引(userId, happenedAt)和(eventType, happenedAt)。注意happenedAt用 Instant,Spring Data 会保存为日期类型,查询时直接传入 Instant 范围即可。
这里有个容易踩的坑:如果需要分析时间范围,尽量在查询条件和索引里使用 UTC 时间,不要使用带时区的 LocalDateTime 直接比较,否则夏令时、时区偏移会造成数据偏差。
7.2 入库接口实现
使用 MongoTemplate 批量插入以提高写入性能:
public void saveLogs(List<BehaviorLog> logs) { mongoTemplate.insert(logs, BehaviorLog.class); }批量插入的性能优势很明显,但如果业务上要求逐条确认写入结果,就退回到循环单条插入。日志场景通常可以容忍一定程度的数据丢失,因此批量插入是主流选择。如果写入量大,还可以在配置中调大 socket read timeout,以免大批量插入时连接提前关闭。
7.3 统计分析:事件趋势图
运营要看每个事件类型每天的数量,聚合查询天生适合这种场景。利用$dateToString格式化时间字段后分组:
Aggregation agg = Aggregation.newAggregation( Aggregation.match(Criteria.where("eventType").in(Arrays.asList("click", "view"))), Aggregation.project() .andExpression("dateToString('%Y-%m-%d', happenedAt)").as("date"), Aggregation.group("date", "eventType").count().as("cnt"), Aggregation.sort(Sort.by(Sort.Direction.ASC, "date")) );mongoTemplate.aggregate 返回的映射结果里_id是一个复合对象,需要自定义一个 DTO 接收:
public class DailyEventStat { private String date; private String eventType; private long cnt; }Spring Data 能把这个扁平结构映射到 DTO,得益于聚合结果里不是嵌套的_id文档,而是我们用 group 的多个键自动生成的复合 id,处理时需要注意字段名映射,必要时使用.as(...)别名来对齐。
8. 事务与一致性注意事项
8.1 MongoDB 支持事务,但别滥用
MongoDB 4.0 开始支持副本集的多文档事务,4.2 之后分片集群也能用。如果你部署的是单机模式而不是副本集,事务会直接报错。Spring Data MongoDB 里很容易用@Transactional开启事务:
@Transactional public void transferData(String sourceId, String targetId) { // 更新 source 集合,更新 target 集合 }但这里必须提醒:默认事务不开启,必须确认 MongoClient 连接的是副本集或者分片集群,否则运行时抛异常。事务会给操作带来性能开销,日志、计数类场景完全没必要用。只有真正涉及资金、订单状态、需要原子性更新多处文档时才使用。
8.2 写入关注级别
MongoDB 的写入关注级别(Write Concern)决定写入操作得到多少副本确认。Spring Data 中默认不设置时使用驱动默认值acknowledged,即主节点确认即可。如果要求更严格的持久性,可以在MongoClientSettings里设置:
.applyToClusterSettings(builder -> builder.mode(ClusterConnectionMode.MULTIPLE))或者更直观地设置WriteConcern.MAJORITY:
MongoClientSettings settings = MongoClientSettings.builder() .writeConcern(WriteConcern.MAJORITY) .build();这个选项意味着必须等大多数从节点确认写入才算成功,数据可靠性更高,但延迟也更高。要在一致性和性能之间找到平衡点,不是越安全越好。
9. 高可用与连接池参数调优
9.1 连接池设置的实际参考
连接池参数很多人不去碰,等性能问题出现才去排查。Spring Data MongoDB 默认的maxSize是 100,这在大多数场景够用,但要结合线程池和业务压力来调整。我有一次压测,线程池 200,单请求持有 Mongo 连接的时间 50ms,结果 100 个连接全部被占满,加上maxWaitTime没设置,大量线程阻塞在获取连接上,整体响应时间雪崩。
一个可行的参考配置:
- maxSize:根据 Tomcat 最大线程数 / 单请求预计数据库耗时来算。假设最大线程 200,期望单请求数据库耗时 10ms 以内,理论上 20 连接就够,但留有缓冲,取 50。
- minSize:建议 5,保持基础连接,避免冷启动时逐个创建。
- maxWaitTime:5 秒,超过则抛异常,不会无限等待。
- connectTimeout:5 秒,网络异常时快速失败。
- readTimeout:10 秒,避免慢查询拖死线程。
这些参数放在MongoClientSettings里统一配置,不要散落在各个业务代码中。
9.2 慢查询排查指南
遇到 MongoDB 响应慢,先做两件事:
- 在 mongosh 里
db.currentOp()查看是否有长时间运行的操作。 - 开启 profiling 查看慢查询日志:
db.setProfilingLevel(1, 200)200表示超过 200ms 的操作记录到 system.profile 集合。然后通过db.system.profile.find().sort({ts:-1}).limit(20)查看耗时最大的语句。分析慢查询时重点看规划执行计划explain()里的winningPlan是否走了索引,如果出现COLLSCAN,基本可以断定索引缺失或者查询条件写得不合适。
分享一个真实案例:一个列表接口开始很快,数据量到 80 万后突然要 2 秒。explain 后发现查询条件createTime范围太大,导致优化器认为走全表扫描比走索引更划算。解决方案是把查询时间范围限制到最近 30 天,并且让组合索引的第一个字段使用等值条件,果然恢复正常。
10. 实用技巧与个人体会
最后分享几个零散但特别实用的点,算是压箱底的经验。
10.1 不要在生产环境直接用 Pagination skip
数据量超过百万后,skip深分页的性能会严重退化。我常用的优化是“基于 _id 或时间游标分页”。前端传 lastId,后端用_id > lastId AND 其他条件查询,限制条数。这种翻页方式不支持跳页,适合信息流场景。如果必须跳页,直接用聚合加索引也勉强能撑,但底层还是要做成本考量。
10.2 使用 Java 时间类型保持一致性
MongoDB 默认存储 UTC 时间,Spring Data 处理Instant、LocalDateTime有时会出现时区偏移。优先使用Instant,存储的是不可变时间点,在序列化时统一转换为用户时区显示,数据库层不掺杂时区逻辑。这个原则能避免很多莫名其妙的 8 小时差异问题。
10.3 善用 bean validation 和自定义校验
虽然 MongoDB 是 Schema-free,但业务上绝大多数文档还是需要结构约束。实体类上加@NotBlank、@Size之类校验注解,在 Controller 层生效,比数据库层约束更容易维护。这里不是说要求每个字段都非空,而是对核心字段必须设置底线。自由是有边界的,否则下游各种导出、分析、报表代码每天都在处理 null。
10.4 别忽略数据库日志审计
MongoDB 里删除或更新文档时,建议通过 TTL 索引做日志过期清理,而不是每次手动删。TTL 索引的使用也很简单,给@Indexed(expireAfterSeconds = 2592000)的字段插入过期时间即可。比如用户行为日志只保留 30 天,让数据库自己清理,避免应用层写定时任务批量删除,给数据库带来重复压力。
11. 一次集成遇到过的真实问题回顾
11.1 认证失败的坑:authSource 与用户名密码都对,还是登录不了
一次环境部署,代码里明明用的是管理员账号,启动却报Authentication failed。排查了半天,最终发现数据库创建了多个库,而用户是在admin库下创建的,连接串里的库名是业务库mydb,没写authSource=admin。原来 MongoDB 默认用业务库做认证,用户不存在于该库,自然登录失败。把authSource=admin加上后立刻恢复。这个错误特别隐蔽,因为本地环境用户名也许恰好存在于业务库,但生产环境多库共存时就会罢工。
11.2 索引创建导致启动缓慢
生产上线时执行了新的 @Indexed 注解,auto-index-creation开了,应用启动后 MongoDB 在后台建索引,结果大集合建索引耗时十几分钟,期间读写均受影响。虽然没有直接导致宕机,但服务的首次请求响应时间飙升到一个不可接受的区间。从此规定:大集合索引变更必须走脚本在低峰期手动执行,应用启动不自动建索引。
11.3 版本太高引发的不兼容问题
热搜里的“springboot版本太高”也让我撞上过。Spring Boot 3.2 下spring-boot-starter-data-mongodb的默认驱动版本已经到 4.11+,如果你用 MongoDB 4.0 服务端,驱动虽然能连接,但部分服务端命令已经不被支持,比如某些聚合性能分析命令。更关键的是,Spring Data MongoDB 4.x 默认要求 MongoDB 服务端至少 3.6,但早版本实例里的功能缺陷不会自动修复。结论就是别贪新,版本匹配表要查清楚再动手。
12. 后续扩展方向
集成只是第一步,真正把 MongoDB 在 Spring Boot 里用好,后面还有很多值得深入的内容。比如响应式编程,如果项目本身就是 WebFlux,那spring-boot-starter-data-mongodb-reactive是顺势的选择。再比如需要全文检索时用 MongoDB Atlas Search,跟事务性能如何取舍。还有数据迁移工具mongomirror或者自研同步脚本,这些都是在生产环境积累业务量后不得不面对的问题。
我个人在实际项目中体会最深的是:Spring Data MongoDB 封装度太高,容易让人忽略底层驱动的行为。一旦遇到诡异问题,记得绕过 Spring 的封装,直接用MongoClient在命令行重放操作,往往瞬间定位问题根因。遇到问题先回到 MongoDB原生视角,再用 Spring 的方式落地解决,这是排查效率最高的路径。
最后再分享一个小技巧:在应用启动时打印 MongoDB 版本和驱动版本,写日志或者/actuator/env里看一眼,很多兼容性问题在开发期就能暴露。这个习惯救了我好几次,也希望你们用得上。