大家好,我是小悟。
一、需求描述
业务场景:开发一个博客文章管理系统
- 实现文章的基本CRUD操作
- 支持文章的评论功能
- 支持根据标题和内容模糊搜索
- 记录文章的创建和更新时间
- 支持文章标签管理
技术需求:
- 使用Spring Boot 2.x
- 使用MongoDB存储数据
- 提供RESTful API接口
- 使用MongoTemplate和MongoRepository两种方式操作数据
二、详细步骤
1. 环境准备
安装MongoDB
# MacOS brew install mongodb-community brew services start mongodb-community # 验证安装 mongo --version
2. 创建Spring Boot项目
pom.xml依赖配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> </parent> <groupId>com.example</groupId> <artifactId>mongodb-demo</artifactId> <version>1.0.0</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Data MongoDB --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 测试依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3. 配置文件
application.yml
spring: data: mongodb: host: localhost port: 27017 database: blogdb # 如果需要认证 # username: admin # password: admin123 # authentication-database: admin server: port: 8080 logging: level: org.springframework.data.mongodb: DEBUG
4. 实体类设计
Article.java
package com.example.mongodb.entity; import lombok.Data; import lombok.Builder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; 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.core.mapping.Field; import java.time.LocalDateTime; import java.util.List; @Data @Builder @NoArgsConstructor @AllArgsConstructor @Document(collection = "articles") // 指定集合名称 public class Article { @Id private String id; // MongoDB默认使用ObjectId,这里用String接收 @Indexed(unique = true) // 创建唯一索引 private String title; @Field("content") // 指定字段名 private String content; private String author; private List<String> tags; private Integer viewCount; private List<Comment> comments; @Field("createTime") private LocalDateTime createTime; @Field("updateTime") private LocalDateTime updateTime; private Boolean published; @Data @Builder @NoArgsConstructor @AllArgsConstructor public static class Comment { private String username; private String content; private LocalDateTime commentTime; private Integer likes; } }
5. Repository层
使用MongoRepository方式
package com.example.mongodb.repository; import com.example.mongodb.entity.Article; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface ArticleRepository extends MongoRepository<Article, String> { // 根据作者查询 List<Article> findByAuthor(String author); // 根据标签查询 List<Article> findByTagsIn(List<String> tags); // 根据标题模糊查询 List<Article> findByTitleLike(String title); // 根据发布时间范围查询 List<Article> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end); // 使用@Query注解自定义查询 @Query("{'title': {$regex: ?0}, 'published': true}") List<Article> findPublishedArticlesByTitle(String title); // 复杂的嵌套查询 @Query("{'comments.username': ?0}") List<Article> findByCommentUsername(String username); }
使用MongoTemplate方式
package com.example.mongodb.repository; import com.example.mongodb.entity.Article; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class ArticleTemplateRepository { @Autowired private MongoTemplate mongoTemplate; // 条件查询 public List<Article> findByCondition(String keyword, String author) { Query query = new Query(); // 添加查询条件 if (keyword != null && !keyword.isEmpty()) { query.addCriteria(Criteria.where("title").regex(keyword) .orOperator(Criteria.where("content").regex(keyword))); } if (author != null && !author.isEmpty()) { query.addCriteria(Criteria.where("author").is(author)); } // 只查询已发布的文章 query.addCriteria(Criteria.where("published").is(true)); // 排序 query.with(Sort.by(Sort.Direction.DESC, "createTime")); return mongoTemplate.find(query, Article.class); } // 更新评论点赞数 public void updateCommentLikes(String articleId, String commentUsername, int increment) { Query query = new Query(Criteria.where("id").is(articleId) .and("comments.username").is(commentUsername)); Update update = new Update().inc("comments.$.likes", increment); mongoTemplate.updateFirst(query, update, Article.class); } // 增加文章浏览量 public void incrementViewCount(String articleId) { Query query = new Query(Criteria.where("id").is(articleId)); Update update = new Update().inc("viewCount", 1); mongoTemplate.updateFirst(query, update, Article.class); } // 批量更新 public void updateArticlesStatusByAuthor(String author, Boolean status) { Query query = new Query(Criteria.where("author").is(author)); Update update = new Update().set("published", status); mongoTemplate.updateMulti(query, update, Article.class); } }
6. Service层
package com.example.mongodb.service; import com.example.mongodb.entity.Article; import com.example.mongodb.repository.ArticleRepository; import com.example.mongodb.repository.ArticleTemplateRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; @Autowired private ArticleTemplateRepository articleTemplateRepository; // 创建文章 public Article createArticle(Article article) { article.setId(null); // 确保是新文章 article.setCreateTime(LocalDateTime.now()); article.setUpdateTime(LocalDateTime.now()); article.setViewCount(0); return articleRepository.save(article); } // 更新文章 public Article updateArticle(String id, Article article) { return articleRepository.findById(id) .map(existingArticle -> { article.setId(id); article.setCreateTime(existingArticle.getCreateTime()); article.setUpdateTime(LocalDateTime.now()); return articleRepository.save(article); }) .orElseThrow(() -> new RuntimeException("文章不存在")); } // 删除文章 public void deleteArticle(String id) { articleRepository.deleteById(id); } // 查询单篇文章 public Article getArticle(String id) { return articleRepository.findById(id) .map(article -> { // 阅读量+1 articleTemplateRepository.incrementViewCount(id); article.setViewCount(article.getViewCount() + 1); return article; }) .orElseThrow(() -> new RuntimeException("文章不存在")); } // 查询所有文章 public List<Article> getAllArticles() { return articleRepository.findAll(); } // 根据作者查询 public List<Article> getArticlesByAuthor(String author) { return articleRepository.findByAuthor(author); } // 条件搜索 public List<Article> searchArticles(String keyword, String author) { return articleTemplateRepository.findByCondition(keyword, author); } // 添加评论 public Article addComment(String articleId, Article.Comment comment) { return articleRepository.findById(articleId) .map(article -> { comment.setCommentTime(LocalDateTime.now()); comment.setLikes(0); article.getComments().add(comment); article.setUpdateTime(LocalDateTime.now()); return articleRepository.save(article); }) .orElseThrow(() -> new RuntimeException("文章不存在")); } // 点赞评论 public void likeComment(String articleId, String username) { articleTemplateRepository.updateCommentLikes(articleId, username, 1); } }
7. Controller层
package com.example.mongodb.controller; import com.example.mongodb.entity.Article; import com.example.mongodb.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/articles") public class ArticleController { @Autowired private ArticleService articleService; @PostMapping public ResponseEntity<Article> createArticle(@RequestBody Article article) { Article created = articleService.createArticle(article); return new ResponseEntity<>(created, HttpStatus.CREATED); } @PutMapping("/{id}") public ResponseEntity<Article> updateArticle(@PathVariable String id, @RequestBody Article article) { Article updated = articleService.updateArticle(id, article); return ResponseEntity.ok(updated); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteArticle(@PathVariable String id) { articleService.deleteArticle(id); return ResponseEntity.noContent().build(); } @GetMapping("/{id}") public ResponseEntity<Article> getArticle(@PathVariable String id) { Article article = articleService.getArticle(id); return ResponseEntity.ok(article); } @GetMapping public ResponseEntity<List<Article>> getAllArticles() { List<Article> articles = articleService.getAllArticles(); return ResponseEntity.ok(articles); } @GetMapping("/search") public ResponseEntity<List<Article>> searchArticles( @RequestParam(required = false) String keyword, @RequestParam(required = false) String author) { List<Article> articles = articleService.searchArticles(keyword, author); return ResponseEntity.ok(articles); } @PostMapping("/{id}/comments") public ResponseEntity<Article> addComment( @PathVariable String id, @RequestBody Article.Comment comment) { Article article = articleService.addComment(id, comment); return ResponseEntity.ok(article); } @PostMapping("/{id}/comments/{username}/like") public ResponseEntity<Void> likeComment( @PathVariable String id, @PathVariable String username) { articleService.likeComment(id, username); return ResponseEntity.ok().build(); } }
8. 启动类
package com.example.mongodb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.mongodb.config.EnableMongoAuditing; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication @EnableMongoRepositories // 启用MongoDB仓库 @EnableMongoAuditing // 启用MongoDB审计功能 public class MongodbApplication { public static void main(String[] args) { SpringApplication.run(MongodbApplication.class, args); } }
9. 测试数据初始化
package com.example.mongodb.config; import com.example.mongodb.entity.Article; import com.example.mongodb.repository.ArticleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.Arrays; @Component public class DataInitializer implements CommandLineRunner { @Autowired private ArticleRepository articleRepository; @Override public void run(String... args) throws Exception { // 清空数据 articleRepository.deleteAll(); // 创建测试文章 Article article1 = Article.builder() .title("Spring Boot 入门教程") .content("本文将介绍Spring Boot的基本使用方法...") .author("张三") .tags(Arrays.asList("Java", "Spring Boot", "教程")) .viewCount(0) .published(true) .createTime(LocalDateTime.now()) .updateTime(LocalDateTime.now()) .comments(Arrays.asList( Article.Comment.builder() .username("李四") .content("写的很好,学习了!") .commentTime(LocalDateTime.now()) .likes(5) .build() )) .build(); Article article2 = Article.builder() .title("MongoDB 实战技巧") .content("分享MongoDB在实际项目中的应用经验...") .author("王五") .tags(Arrays.asList("数据库", "MongoDB", "实战")) .viewCount(0) .published(true) .createTime(LocalDateTime.now()) .updateTime(LocalDateTime.now()) .comments(Arrays.asList()) .build(); articleRepository.saveAll(Arrays.asList(article1, article2)); System.out.println("测试数据初始化完成!"); } }
三、测试接口
使用curl测试API
# 创建文章 curl -X POST http://localhost:8080/api/articles \ -H "Content-Type: application/json" \ -d '{ "title": "MongoDB高级特性", "content": "本文将介绍MongoDB的高级特性...", "author": "赵六", "tags": ["MongoDB", "数据库", "高级"], "published": true }' # 查询所有文章 curl http://localhost:8080/api/articles # 搜索文章 curl "http://localhost:8080/api/articles/search?keyword=Spring&author=张三" # 添加评论 curl -X POST http://localhost:8080/api/articles/{id}/comments \ -H "Content-Type: application/json" \ -d '{ "username": "读者A", "content": "非常实用的教程!" }' # 点赞评论 curl -X POST http://localhost:8080/api/articles/{id}/comments/读者A/like
四、详细总结
1. MongoDB与关系型数据库的对比
| 特性 | MongoDB | MySQL |
| 数据结构 | 文档型(BSON/JSON) | 表格(行/列) |
| 关联查询 | 内嵌文档或DBRef | JOIN操作 |
| Schema | 动态schema | 固定schema |
| 扩展性 | 原生支持水平扩展 | 主从复制/分库分表 |
| 事务支持 | 4.0后支持多文档事务 | 完整ACID支持 |
2. Spring Data MongoDB的核心注解
| 注解 | 作用 |
| @Document | 映射MongoDB的集合 |
| @Id | 标识文档ID字段 |
| @Field | 指定字段名 |
| @Indexed | 声明索引 |
| @CompoundIndex | 复合索引 |
| @DBRef | 引用其他集合(谨慎使用) |
| @Transactional | 支持事务操作 |
3. 操作方式对比
MongoRepository的优点:
- 代码简洁,继承接口即可获得基础CRUD
- 支持方法名解析查询
- 与Spring Data JPA使用方式相似
MongoTemplate的优点:
- 更灵活的查询条件构造
- 支持批量操作和聚合查询
- 可以执行原生MongoDB命令
4. 最佳实践建议
1. 索引设计
- 为常用查询字段创建索引
- 复合索引要注意字段顺序
- 使用@Indexed注解或mongoTemplate创建索引
2. 性能优化
// 分页查询示例 Pageable pageable = PageRequest.of(0, 10, Sort.by("createTime").descending()); Page<Article> page = articleRepository.findAll(pageable); // 只返回需要的字段 Query query = new Query(); query.fields().include("title").include("author");
3. 数据一致性
@Transactional public void updateWithTransaction(String articleId, Comment comment) { // 确保两个操作原子性 addComment(articleId, comment); updateArticleStats(articleId); }
4. 异常处理
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(DuplicateKeyException.class) public ResponseEntity<String> handleDuplicateKey() { return ResponseEntity.status(HttpStatus.CONFLICT) .body("文章标题已存在"); } }
5. 常见问题解决
1. 连接认证问题
spring: data: mongodb: uri: mongodb://username:password@localhost:27017/dbname?authSource=admin
2. 大数据量处理
// 使用流式处理 @Autowired private MongoTemplate mongoTemplate; public void processLargeData() { Query query = new Query(); query.cursorBatchSize(1000); // 设置批处理大小 mongoTemplate.stream(query, Article.class) .forEach(article -> { // 处理每条记录 }); }
3. 审计功能
@EntityListeners(AuditingEntityListener.class) public class Article { @CreatedDate private LocalDateTime createTime; @LastModifiedDate private LocalDateTime updateTime; }
6. 项目结构建议
src/main/java/com/example/mongodb/ ├── config/ # 配置类 ├── entity/ # 实体类 ├── repository/ # 数据访问层 ├── service/ # 业务逻辑层 ├── controller/ # REST控制器 ├── dto/ # 数据传输对象 ├── exception/ # 异常处理 └── util/ # 工具类
五、总结
通过本实战项目,我们完整地实现了Spring Boot与MongoDB的整合,涵盖了:
- 基础配置:Maven依赖、YAML配置
- 数据建模:文档设计、嵌套文档使用
- 数据访问:Repository和Template两种方式
- 业务实现:CRUD操作、复杂查询、评论功能
- 性能优化:索引、分页、批量处理
- 最佳实践:事务、审计、异常处理
MongoDB作为NoSQL数据库的代表,特别适合以下场景:
- 数据结构不固定,需要频繁变更
- 需要快速迭代开发
- 读写并发高,需要水平扩展
- 存储复杂嵌套结构的JSON数据
Spring Data MongoDB提供了简洁的API,让开发者能够专注于业务逻辑,快速构建高性能的应用。通过合理使用MongoDB的特性,可以充分发挥其文档数据库的优势。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海