大家好,我是小悟。
一、需求描述
业务场景:开发一个博客文章管理系统
- 实现文章的基本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的特性,可以充分发挥其文档数据库的优势。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海