Spring Boot 集成 MongoDB:从入门到常用写法
MongoDB 是一种文档型 NoSQL 数据库数据以类似 JSON 的 BSON 格式存储。相比传统关系型数据库MongoDB 更适合存储结构灵活、字段变化频繁、读写吞吐较高的数据例如日志、内容、配置、用户行为、商品扩展属性等。本文介绍如何在 Spring Boot 中使用 MongoDB包括环境准备、依赖配置、实体映射、基础 CRUD、分页查询、自定义查询以及常见注意事项。1. 准备 MongoDB本地可以使用 Docker 快速启动 MongoDBdockerrun-d\--namemongo\-p27017:27017\-eMONGO_INITDB_ROOT_USERNAMEroot\-eMONGO_INITDB_ROOT_PASSWORD123456\mongo连接信息如下地址localhost:27017 用户名root 密码1234562. 引入依赖在pom.xml中添加 Spring Data MongoDB 依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-mongodb/artifactId/dependency如果是 Gradleimplementation org.springframework.boot:spring-boot-starter-data-mongodb3. 配置 MongoDB 连接在application.yml中配置连接地址spring:data:mongodb:uri:mongodb://root:123456localhost:27017/demo_db?authSourceadmin说明demo_db是业务数据库名authSourceadmin表示账号密码在admin数据库中认证如果没有账号密码可以使用mongodb://localhost:27017/demo_db4. 创建实体类MongoDB 中的集合类似 MySQL 中的表文档类似表中的一行数据。importlombok.Data;importorg.springframework.data.annotation.Id;importorg.springframework.data.mongodb.core.mapping.Document;importjava.time.LocalDateTime;DataDocument(collectionusers)publicclassUser{IdprivateStringid;privateStringusername;privateIntegerage;privateStringemail;privateLocalDateTimecreateTime;}常用注解说明注解说明Document标记当前类映射到 MongoDB 集合collection指定集合名称Id标记文档主键字段5. 使用 MongoRepositorySpring Data MongoDB 提供了 Repository 风格的接口可以快速完成基础 CRUD。importorg.springframework.data.mongodb.repository.MongoRepository;importjava.util.List;importjava.util.Optional;publicinterfaceUserRepositoryextendsMongoRepositoryUser,String{OptionalUserfindByUsername(Stringusername);ListUserfindByAgeGreaterThan(Integerage);ListUserfindByEmailContaining(Stringkeyword);}Spring Data 会根据方法名自动生成查询逻辑。6. 基础 CRUD 示例importlombok.RequiredArgsConstructor;importorg.springframework.stereotype.Service;importjava.time.LocalDateTime;importjava.util.List;ServiceRequiredArgsConstructorpublicclassUserService{privatefinalUserRepositoryuserRepository;publicUsercreateUser(Stringusername,Integerage,Stringemail){UserusernewUser();user.setUsername(username);user.setAge(age);user.setEmail(email);user.setCreateTime(LocalDateTime.now());returnuserRepository.save(user);}publicUserupdateUser(Stringid,Stringemail){UseruseruserRepository.findById(id).orElseThrow(()-newRuntimeException(用户不存在));user.setEmail(email);returnuserRepository.save(user);}publicUsergetUser(Stringid){returnuserRepository.findById(id).orElseThrow(()-newRuntimeException(用户不存在));}publicListUserlistUsers(){returnuserRepository.findAll();}publicvoiddeleteUser(Stringid){userRepository.deleteById(id);}}7. 提供 Controller 接口importlombok.RequiredArgsConstructor;importorg.springframework.web.bind.annotation.*;importjava.util.List;RestControllerRequestMapping(/users)RequiredArgsConstructorpublicclassUserController{privatefinalUserServiceuserService;PostMappingpublicUsercreate(RequestBodyCreateUserRequestrequest){returnuserService.createUser(request.getUsername(),request.getAge(),request.getEmail());}GetMapping(/{id})publicUserdetail(PathVariableStringid){returnuserService.getUser(id);}GetMappingpublicListUserlist(){returnuserService.listUsers();}PutMapping(/{id}/email)publicUserupdateEmail(PathVariableStringid,RequestParamStringemail){returnuserService.updateUser(id,email);}DeleteMapping(/{id})publicvoiddelete(PathVariableStringid){userService.deleteUser(id);}}请求对象importlombok.Data;DatapublicclassCreateUserRequest{privateStringusername;privateIntegerage;privateStringemail;}8. 分页查询MongoRepository 支持 Spring Data 的分页能力。importorg.springframework.data.domain.Page;importorg.springframework.data.domain.Pageable;importorg.springframework.data.mongodb.repository.MongoRepository;publicinterfaceUserRepositoryextendsMongoRepositoryUser,String{PageUserfindByAgeGreaterThan(Integerage,Pageablepageable);}Service 示例importorg.springframework.data.domain.Page;importorg.springframework.data.domain.PageRequest;importorg.springframework.stereotype.Service;publicPageUserpageUsers(Integerage,intpage,intsize){returnuserRepository.findByAgeGreaterThan(age,PageRequest.of(page,size));}Controller 示例GetMapping(/page)publicPageUserpage(RequestParamIntegerage,RequestParam(defaultValue0)intpage,RequestParam(defaultValue10)intsize){returnuserService.pageUsers(age,page,size);}注意Spring Data 的分页页码从0开始。9. 使用 Query 自定义查询当方法名查询不够灵活时可以使用Query。importorg.springframework.data.mongodb.repository.Query;importjava.util.List;publicinterfaceUserRepositoryextendsMongoRepositoryUser,String{Query({ username: ?0 })ListUserqueryByUsername(Stringusername);Query({ age: { $gte: ?0, $lte: ?1 } })ListUserqueryByAgeRange(IntegerminAge,IntegermaxAge);}字段投影示例只返回部分字段Query(value{ age: { $gte: ?0 } },fields{ username: 1, email: 1 })ListUserqueryUserBasicInfo(Integerage);10. 使用 MongoTemplateMongoRepository适合简单 CRUDMongoTemplate适合复杂查询、动态条件、聚合操作。importlombok.RequiredArgsConstructor;importorg.springframework.data.domain.Sort;importorg.springframework.data.mongodb.core.MongoTemplate;importorg.springframework.data.mongodb.core.query.Criteria;importorg.springframework.data.mongodb.core.query.Query;importorg.springframework.stereotype.Repository;importjava.util.List;RepositoryRequiredArgsConstructorpublicclassUserMongoTemplateRepository{privatefinalMongoTemplatemongoTemplate;publicListUsersearchUsers(Stringusername,IntegerminAge){QueryquerynewQuery();if(username!null!username.isBlank()){query.addCriteria(Criteria.where(username).regex(username));}if(minAge!null){query.addCriteria(Criteria.where(age).gte(minAge));}query.with(Sort.by(Sort.Direction.DESC,createTime));returnmongoTemplate.find(query,User.class);}}分页写法publicListUserpageUsers(Stringusername,intpage,intsize){QueryquerynewQuery();if(username!null!username.isBlank()){query.addCriteria(Criteria.where(username).regex(username));}query.skip((long)page*size);query.limit(size);returnmongoTemplate.find(query,User.class);}统计总数publiclongcountUsers(Stringusername){QueryquerynewQuery();if(username!null!username.isBlank()){query.addCriteria(Criteria.where(username).regex(username));}returnmongoTemplate.count(query,User.class);}11. 更新指定字段使用MongoTemplate可以只更新部分字段不需要整条文档覆盖保存。importorg.springframework.data.mongodb.core.query.Update;publicvoidupdateEmail(Stringid,Stringemail){QueryquerynewQuery(Criteria.where(_id).is(id));UpdateupdatenewUpdate().set(email,email).currentDate(updateTime);mongoTemplate.updateFirst(query,update,User.class);}常用更新操作newUpdate().set(email,testexample.com);newUpdate().inc(age,1);newUpdate().unset(email);newUpdate().push(tags,java);newUpdate().pull(tags,old);12. 删除数据根据条件删除publicvoiddeleteByUsername(Stringusername){QueryquerynewQuery(Criteria.where(username).is(username));mongoTemplate.remove(query,User.class);}13. 创建索引索引可以提升查询性能尤其是高频查询字段。importorg.springframework.data.mongodb.core.index.Indexed;DataDocument(collectionusers)publicclassUser{IdprivateStringid;Indexed(uniquetrue)privateStringusername;IndexedprivateIntegerage;privateStringemail;privateLocalDateTimecreateTime;}也可以使用复合索引importorg.springframework.data.mongodb.core.index.CompoundIndex;DataDocument(collectionusers)CompoundIndex(nameidx_age_create_time,def{age: 1, createTime: -1})publicclassUser{IdprivateStringid;privateStringusername;privateIntegerage;privateStringemail;privateLocalDateTimecreateTime;}14. 常见注意事项14.1 不要滥用模糊查询类似下面的正则查询如果数据量很大容易导致性能问题Criteria.where(username).regex(keyword)如果是高频搜索场景建议考虑增加合理索引使用前缀匹配使用 MongoDB Text Index复杂搜索交给 Elasticsearch、OpenSearch 等搜索引擎14.2 注意 save 的覆盖行为repository.save(user)如果对象中存在id会更新整条文档。如果只想更新某几个字段建议使用MongoTemplate的Update。14.3 字段命名要稳定MongoDB 的文档结构比较灵活但业务字段不应该随意变化。一旦接口、查询和索引都依赖某些字段字段变更仍然需要像关系型数据库一样谨慎。14.4 大集合必须重视索引MongoDB 没有索引时也能查询但数据量上来后性能会快速下降。生产环境中应重点关注查询条件字段是否有索引排序字段是否有索引组合查询是否需要复合索引是否存在慢查询14.5 事务不是 MongoDB 的首选使用方式MongoDB 支持事务但它的优势不是复杂事务模型。如果业务强依赖多表强一致事务关系型数据库可能更合适。15. Repository 和 MongoTemplate 怎么选场景推荐方式简单增删改查MongoRepository根据固定字段查询MongoRepository动态查询条件MongoTemplate只更新部分字段MongoTemplate复杂聚合统计MongoTemplate需要更强类型约束和简单代码MongoRepository实际项目中两者经常同时使用常规 CRUD 使用MongoRepository复杂查询和局部更新使用MongoTemplate16. 小结Spring Boot 集成 MongoDB 非常简单核心步骤是引入spring-boot-starter-data-mongodb配置 MongoDB 连接地址使用Document定义文档实体使用MongoRepository完成基础 CRUD使用MongoTemplate处理复杂查询和局部更新根据查询场景合理创建索引MongoDB 的优势在于文档结构灵活、读写性能好、扩展能力强但它并不是关系型数据库的完全替代品。实际选型时应根据业务数据结构、查询模式、一致性要求和团队维护成本综合判断。