ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot集成MongoDB全指南:配置、建模、索引与调优实战

Spring Boot集成MongoDB全指南:配置、建模、索引与调优实战 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做好全面回归测试。依赖只需要一个 starterdependency groupIdorg.springframework.boot/groupId artifactIdspring-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:password192.168.1.100:27017/mydb?authSourceadminreplicaSetrs0 auto-index-creation: true uuid-representation: standard这里有几个被忽略的重点uri里如果开启了副本集必须写上replicaSetrs0否则 Spring Data 默认情况下连接不启用副本集探测虽然有serverSelectionTimeoutMS兜底但一旦主节点切换客户端不会优雅处理。authSourceadmin是指定认证库。很多人用户名密码都对但认证库默认为当前库导致鉴权失败。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 ListString tags; private Address address; private MapString, Object extraAttrs; private Instant createdAt; }说明一下选择理由Document里的 collection 名称建议显式用复数下划线命名避免依赖类名转换规则明确集合边界。Id字段类型尽量用 String让 MongoDB 生成 ObjectId 自动转换为字符串避免在 JSON 序列化和前端传参时产生类型不一致。除非你要存储自定义业务主键否则别用 Long 类型作 _id。Field(user_name)用来映射驼峰字段和数据库下划线字段。有人图省事不映射直接在 MongoDB 里存驼峰字段其实也行但团队协作时统一风格更好。MapString, 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 的常见操作定义接口继承 MongoRepositorySpring 会自动实现方法名解析public interface UserProfileRepository extends MongoRepositoryUserProfile, String { OptionalUserProfile findByUserId(String userId); ListUserProfile findTop10ByTagsContainingOrderByCreatedAtDesc(String tag); long countByAddressCity(String city); }方法名解析规则其实很好理解就是把字段名、查询关键字按驼峰拼起来。findByXxxAndYyy、findByXxxOrYyy、IsGreaterThan、Between等自动生成查询逻辑。这里想提醒的是方法名过长会降低可读性比如findAllByStatusAndTypeAndCreateTimeBetweenAndDeletedIsNull——这种查询我建议直接用 Query 注解或者模板查询别硬凹命名。自定义查询用QueryQuery({ tags: { $all: ?0 }, status: { $ne: disabled } }) ListUserProfile findByAllTags(ListString tags);JSON 查询串里的$符号在 Java 注解里不需要转义因为是字符串。这个写法直观但也容易写错建议先在 MongoDB Compass 或 mongosh 里验证同样的语句能跑通再粘到注解里。4.2 基于 MongoTemplate 的高级查询当查询条件动态拼接时Repository 就很笨拙了。MongoTemplate 配合 Query 和 Criteria 非常顺手Autowired private MongoTemplate mongoTemplate; public PageUserProfile 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); ListUserProfile 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 对象指定$setQuery 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 groupIdde.flapdoodle.embed/groupId artifactIdde.flapdoodle.embed.mongo/artifactId scopetest/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)) ); ListCityStat 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 MapString, Object payload; private Instant happenedAt; }索引设计上最核心的查询模式是“按用户查最近行为”和“按事件类型和时间范围统计”因此建立组合索引(userId, happenedAt)和(eventType, happenedAt)。注意happenedAt用 InstantSpring Data 会保存为日期类型查询时直接传入 Instant 范围即可。这里有个容易踩的坑如果需要分析时间范围尽量在查询条件和索引里使用 UTC 时间不要使用带时区的 LocalDateTime 直接比较否则夏令时、时区偏移会造成数据偏差。7.2 入库接口实现使用 MongoTemplate 批量插入以提高写入性能public void saveLogs(ListBehaviorLog 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.MAJORITYMongoClientSettings 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保持基础连接避免冷启动时逐个创建。maxWaitTime5 秒超过则抛异常不会无限等待。connectTimeout5 秒网络异常时快速失败。readTimeout10 秒避免慢查询拖死线程。这些参数放在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没写authSourceadmin。原来 MongoDB 默认用业务库做认证用户不存在于该库自然登录失败。把authSourceadmin加上后立刻恢复。这个错误特别隐蔽因为本地环境用户名也许恰好存在于业务库但生产环境多库共存时就会罢工。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里看一眼很多兼容性问题在开发期就能暴露。这个习惯救了我好几次也希望你们用得上。
返回列表