1. 为什么选择Spring Boot与MongoDB组合
在当今的互联网应用开发中,NoSQL数据库因其灵活的数据模型和高扩展性越来越受到开发者青睐。MongoDB作为文档型数据库的代表,在处理非结构化数据和快速迭代开发场景中展现出独特优势。而Spring Boot作为Java生态中最流行的微服务框架,其约定优于配置的理念大幅降低了项目初始化的复杂度。
我曾在多个电商和物联网项目中采用这种技术组合,最直观的感受是开发效率的提升。比如在商品评价系统中,MongoDB的嵌套文档结构能完美匹配评价+回复的树形关系,配合Spring Data MongoDB的Repository抽象,原本需要复杂SQL实现的查询现在用几行方法声明就能搞定。
2. 环境准备与基础配置
2.1 依赖引入
在pom.xml中添加关键依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.4.0</version>
</dependency>
注意:生产环境建议明确指定MongoDB驱动版本,避免Spring Boot自动管理的版本与集群不兼容
2.2 连接配置
application.yml典型配置:
yaml复制spring:
data:
mongodb:
uri: mongodb://user:password@host1:27017,host2:27017/database
auto-index-creation: true
authentication-database: admin
配置要点解析:
- 多节点连接使用逗号分隔,实现自动故障转移
- auto-index-creation开发环境建议开启,生产环境应通过迁移脚本控制
- 认证数据库通常与业务数据库分离
3. 核心CRUD实现
3.1 实体建模技巧
java复制@Document(collection = "products")
public class Product {
@Id
private String id;
@Indexed(unique = true)
private String sku;
@Field("product_name")
private String name;
@Transient
private BigDecimal discountPrice;
// 嵌套文档示例
private List<Specification> specs;
}
实体设计经验:
- 使用@Field控制字段命名,保持数据库字段风格统一
- @Transient标注不持久化的字段,避免无用字段存入数据库
- 嵌套文档不宜超过3层,否则考虑引用关系
3.2 Repository高级用法
基础接口:
java复制public interface ProductRepository extends MongoRepository<Product, String> {
// 方法名自动推导查询
List<Product> findByNameAndPriceLessThan(String name, BigDecimal price);
// 注解自定义查询
@Query("{'specs.color': ?0}")
Page<Product> findBySpecColor(String color, Pageable pageable);
}
复杂查询建议:
- 简单查询用方法名推导(支持AND/OR/NOT等逻辑)
- 中等复杂度用@Query注解
- 极复杂聚合使用MongoTemplate
4. 分页查询优化方案
4.1 基础分页实现
java复制PageRequest pageRequest = PageRequest.of(0, 10,
Sort.by("price").descending());
Page<Product> page = productRepository.findAll(pageRequest);
4.2 大分页性能陷阱
当处理第100页之后的数据时,skip()操作会导致性能急剧下降。实测数据:
| 页码 | 查询耗时(ms) |
|---|---|
| 1 | 32 |
| 100 | 245 |
| 1000 | 1800 |
优化方案:
- 使用范围查询替代skip:
java复制@Query("{'_id': {$gt: ?0}}")
List<Product> findByIdGreaterThan(String lastId, Pageable pageable);
- 配合索引使用:
java复制@Query(value = "{'createTime': {$lt: ?0}}",
fields = "{'_id':1, 'name':1}")
List<Product> findBriefBeforeDate(Date date, Pageable pageable);
5. 事务处理实战
5.1 事务配置要点
- MongoDB必须使用副本集(开发环境可用单节点伪集群)
- 添加事务管理器配置:
java复制@Bean
MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
5.2 事务使用模式
java复制@Transactional
public void placeOrder(Order order) {
// 扣减库存
mongoTemplate.update(Product.class)
.matching(where("sku").is(order.getSku()))
.apply(new Update().inc("stock", -order.getQuantity()))
.first();
// 创建订单
orderRepository.save(order);
// 记录流水
auditLogRepository.insert(new AuditLog(...));
}
事务限制说明:
- 单个事务内操作不能超过1000个文档
- 事务默认60秒超时,复杂事务需要调整
- 跨分片事务需要特殊配置
6. 索引设计与优化
6.1 常用索引类型
- 单字段索引:
java复制@CompoundIndex(def = "{'category': 1, 'price': -1}")
public class Product {...}
- 文本索引(支持全文搜索):
java复制@TextIndexed
private String description;
- TTL索引(自动过期):
java复制@Indexed(expireAfterSeconds = 86400)
private Date createTime;
6.2 索引优化案例
问题场景:商品列表页需要同时支持:
- 按分类筛选
- 按价格排序
- 按销量排序
优化方案:
java复制// 复合索引设计
@CompoundIndexes({
@CompoundIndex(name = "cate_price", def = "{'categoryId':1, 'price':1}"),
@CompoundIndex(name = "cate_sales", def = "{'categoryId':1, 'sales':-1}")
})
执行计划检查命令:
bash复制db.products.find({categoryId: "1001"}).sort({price: 1})
.explain("executionStats")
7. 生产环境注意事项
- 连接池配置:
yaml复制spring:
data:
mongodb:
uri: mongodb://host/db?maxPoolSize=50&waitQueueTimeoutMS=2000
- 读写分离配置:
java复制@Configuration
public class MongoConfig {
@Bean
@Primary
public MongoTemplate primaryTemplate(MongoDatabaseFactory factory) {
return new MongoTemplate(factory);
}
@Bean
public MongoTemplate secondaryTemplate(@Qualifier("secondaryFactory")
MongoDatabaseFactory factory) {
return new MongoTemplate(factory);
}
}
- 监控指标接入:
- 集成Micrometer暴露MongoDB指标
- 关键监控项:连接池使用率、慢查询、操作错误率
8. 常见问题排查
- 连接超时问题:
- 检查网络ACL规则
- 验证DNS解析
- 测试telnet到MongoDB端口
- 性能突然下降:
java复制// 在日志中开启慢查询记录
@Bean
public MongoClientSettings mongoSettings() {
return MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.serverSelectionTimeout(5000))
.applyToSocketSettings(builder -> builder.connectTimeout(2000))
.build();
}
- 事务冲突处理:
- 添加重试机制
- 使用乐观锁控制并发
- 考虑改用最终一致性方案
9. 扩展应用场景
- 地理空间查询:
java复制@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private double[] location;
// 查询5公里内的店铺
repository.findByLocationNear(point, new Distance(5, Metrics.KILOMETERS));
- 变更流监听(Change Stream):
java复制@Scheduled(fixedDelay = 1000)
public void watchChanges() {
mongoTemplate.changeStream(Product.class)
.listen()
.forEach(event -> {
// 处理变更事件
});
}
- 聚合管道高级应用:
java复制Aggregation agg = newAggregation(
match(where("category").is("electronics")),
group("brand").count().as("total"),
sort(Sort.Direction.DESC, "total")
);