Compare commits
2 Commits
33c9563611
...
6d7504a4f0
Author | SHA1 | Date |
---|---|---|
|
6d7504a4f0 | |
|
404d30bee9 |
|
@ -0,0 +1,167 @@
|
|||
# MongoDB联查测试项目
|
||||
|
||||
一个简单的MongoDB多表联查测试项目,演示如何使用Spring Boot + MongoDB进行复杂的聚合查询。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **5表联查**:购物记录、用户、地址、产品、分类的复杂关联查询
|
||||
- **动态条件**:支持多种查询条件的动态组合
|
||||
- **分页查询**:使用MongoDB Facet操作实现高效分页
|
||||
- **灵活排序**:支持多字段排序
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Spring Boot 2.7.18
|
||||
- Spring Data MongoDB
|
||||
- Java 21
|
||||
- Lombok
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 启动MongoDB
|
||||
```bash
|
||||
# 使用Docker
|
||||
docker run -d -p 27017:27017 --name mongodb mongo:latest
|
||||
|
||||
# 或启动本地MongoDB
|
||||
mongod --dbpath /path/to/your/db
|
||||
```
|
||||
|
||||
### 2. 运行项目
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 3. 测试接口
|
||||
```bash
|
||||
# 基本查询
|
||||
curl "http://localhost:8080/test/search?phase=双十一&user=alice&page=0&size=10"
|
||||
|
||||
# 价格范围查询
|
||||
curl "http://localhost:8080/test/search?minPrice=100&maxPrice=1000&page=0&size=10"
|
||||
|
||||
# 城市筛选
|
||||
curl "http://localhost:8080/test/search?city=北京&category=电子产品&page=0&size=10"
|
||||
```
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 核心集合
|
||||
- **shopping_record**: 购物记录(主表)
|
||||
- **users**: 用户信息
|
||||
- **address**: 地址信息
|
||||
- **product**: 产品信息
|
||||
- **categories**: 分类信息
|
||||
- **shopping_phase**: 购物阶段
|
||||
|
||||
### 关联关系
|
||||
```
|
||||
shopping_record
|
||||
├── userId -> users._id
|
||||
├── productId -> product._id
|
||||
├── shoppingPhaseId -> shopping_phase._id
|
||||
└── users.addressId -> address._id
|
||||
└── product.categoryId -> categories._id
|
||||
```
|
||||
|
||||
## API接口
|
||||
|
||||
### 搜索购物记录
|
||||
`GET /test/search`
|
||||
|
||||
**参数**:
|
||||
- `phase`: 购物阶段名称
|
||||
- `user`: 用户名模糊匹配
|
||||
- `city`: 城市名称
|
||||
- `category`: 分类名称
|
||||
- `minPrice`: 最低价格
|
||||
- `maxPrice`: 最高价格
|
||||
- `page`: 页码(从0开始)
|
||||
- `size`: 每页大小
|
||||
- `sortField`: 排序字段
|
||||
- `ascending`: 是否升序
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"username": "alice",
|
||||
"email": "alice@example.com",
|
||||
"productName": "iPhone 15",
|
||||
"price": 7999.00,
|
||||
"categoryName": "电子产品"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 0,
|
||||
"size": 10,
|
||||
"total": 1,
|
||||
"totalPages": 1,
|
||||
"hasNext": false,
|
||||
"hasPrevious": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 核心实现
|
||||
|
||||
### 聚合查询构建器
|
||||
使用自定义的`MongoAggregationBuilder`构建复杂的聚合管道:
|
||||
|
||||
<augment_code_snippet path="src/main/java/org/example/mongosimple/service/ShoppingAggregationService.java" mode="EXCERPT">
|
||||
````java
|
||||
PageResult<ShoppingResult> result = MongoAggregationBuilder.fromWithCompatibility(mongoTemplate, "shopping_record")
|
||||
// 关联所有需要的表
|
||||
.lookupAndUnwind("shopping_phase", "shoppingPhaseId", "_id", "phase")
|
||||
.lookupAndUnwind("users", "userId", "_id", "user")
|
||||
.lookupAndUnwind("address", "user.addressId", "_id", "address")
|
||||
.lookupAndUnwind("product", "productId", "_id", "product")
|
||||
.lookupAndUnwind("categories", "product.categoryId", "_id", "category")
|
||||
|
||||
// 动态条件构建
|
||||
.matchDynamic(criteria -> criteria
|
||||
.whenNotEmpty(params.getPhaseName(), "phase.name")
|
||||
.whenNotEmptyLike(params.getUsernamePattern(), "user.username")
|
||||
.whenNotEmpty(params.getCity(), "address.city")
|
||||
.whenRange(params.getMinPrice(), params.getMaxPrice(), "product.price")
|
||||
)
|
||||
|
||||
// 投影和分页
|
||||
.project(project -> project
|
||||
.and("user.username").as("username")
|
||||
.and("product.name").as("productName")
|
||||
.and("product.price").as("price")
|
||||
)
|
||||
.sort(params.getSortField(), params.isAscending())
|
||||
.executeWithPagination(ShoppingResult.class, params.getPage(), params.getSize());
|
||||
````
|
||||
</augment_code_snippet>
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/main/java/org/example/mongosimple/
|
||||
├── controller/ # REST控制器
|
||||
├── service/ # 业务逻辑层
|
||||
├── mongoObject/ # MongoDB文档实体
|
||||
├── entity/ # 查询结果实体
|
||||
└── prepareEntity/ # 查询构建工具类
|
||||
```
|
||||
|
||||
## 开发说明
|
||||
|
||||
这是一个专注于MongoDB联查功能的测试项目,去除了复杂的缓存、监控、导出等功能,专注于核心的聚合查询实现。
|
||||
|
||||
适合用于:
|
||||
- 学习MongoDB聚合查询
|
||||
- 测试复杂的多表关联查询
|
||||
- 验证查询性能和结果准确性
|
||||
|
||||
|
||||
| 查询条件 | 是否命中复合索引 |
|
||||
| -------------------------------------- | -------- |
|
||||
| `{ user_id: ? }` | ✅(最左前缀) |
|
||||
| `{ user_id: ?, shopping_phase_id: ? }` | ✅(完整索引) |
|
||||
| `{ shopping_phase_id: ? }` | ❌(缺少最左) |
|
|
@ -3,21 +3,16 @@ package org.example.mongosimple.controller;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.example.mongosimple.entity.ShoppingResult;
|
||||
import org.example.mongosimple.prepareEntity.MongoAggregationBuilder;
|
||||
import org.example.mongosimple.prepareEntity.PageResult;
|
||||
import org.example.mongosimple.prepareEntity.ShoppingSearchParams;
|
||||
import org.example.mongosimple.service.ShoppingAggregationService;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,13 +28,13 @@ public class DataInitializer implements CommandLineRunner {
|
|||
// System.out.println("开始初始化测试数据...");
|
||||
|
||||
// 初始化用户数据
|
||||
// initUsers();
|
||||
// init();
|
||||
|
||||
|
||||
// System.out.println("测试数据初始化完成!");
|
||||
}
|
||||
|
||||
private void initUsers() {
|
||||
private void init() {
|
||||
// System.out.println("初始化用户数据...");
|
||||
|
||||
Address address = new Address();
|
||||
|
@ -79,7 +79,7 @@ public class DataInitializer implements CommandLineRunner {
|
|||
mongoTemplate.save(product1);
|
||||
mongoTemplate.save(product2);
|
||||
|
||||
// 来自的dezhou的用户 在618期间买的 价格位于 5000 - 6000 的电子产品z
|
||||
// 来自的dezhou的用户 在618期间买的 价格位于 5000 - 6000 的电子产品
|
||||
|
||||
ShoppingRecord shoppingRecord = new ShoppingRecord("68c4fabe669d40228340eaa9", "68c4fcd9f53fc172ab23f4ac", "68c4fb3a370a3428ec124244");
|
||||
ShoppingRecord shoppingRecord1 = new ShoppingRecord("68c4fabe669d40228340eaab", "68c4fcd9f53fc172ab23f4ac", "68c4fb3a370a3428ec124244");
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.example.mongosimple.mongoObject;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@CompoundIndex(def = "{'shopping_phase_id': 1, 'user_id': 1}")
|
||||
@Document(collection = "shopping_record")
|
||||
public class ShoppingRecord {
|
||||
@Id
|
||||
|
@ -11,7 +14,8 @@ public class ShoppingRecord {
|
|||
private String userId;
|
||||
|
||||
private String productId;
|
||||
|
||||
// unique = false(默认) 普通索引,允许重复值
|
||||
@Indexed
|
||||
private String shoppingPhaseId;
|
||||
|
||||
public ShoppingRecord(String userId, String productId, String shoppingPhaseId) {
|
||||
|
|
|
@ -529,15 +529,7 @@ public class MongoAggregationBuilder {
|
|||
* 自动添加字段展平投影
|
||||
*/
|
||||
public static MongoAggregationBuilder fromWithCompatibility(MongoTemplate mongoTemplate, String sourceCollection) {
|
||||
MongoAggregationBuilder builder = new MongoAggregationBuilder(mongoTemplate, sourceCollection);
|
||||
|
||||
// 检查是否需要兼容性处理
|
||||
if (!SpringBootCompatibilityHelper.supportsNestedFieldReferences()) {
|
||||
System.out.println("检测到Spring Boot 2环境,启用兼容性模式");
|
||||
// 在后续的lookup操作后自动添加字段展平
|
||||
}
|
||||
|
||||
return builder;
|
||||
return new MongoAggregationBuilder(mongoTemplate, sourceCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue