MongoDB高级查询全攻略:使用MongoTemplate实现分组、排序、分页与连表查询

一、前言

在实际的企业级应用开发中,对MongoDB数据进行复杂查询是常见的需求。Spring Data MongoDB提供的MongoTemplate是一个功能强大的工具,能够帮助我们执行各种复杂的查询操作。本文将详细讲解如何使用MongoTemplate实现MongoDB的分组、排序、分页和连表查询。

二、环境准备

2.1 依赖配置

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 实体类定义

假设我们有两个集合:用户(users)和订单(orders)。

java 复制代码
@Data
@Document(collection = "users")
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    private String id;
    private String username;
    private String email;
    private Integer age;
    private String city;
    private Date createTime;
}

@Data
@Document(collection = "orders")
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    @Id
    private String id;
    private String userId;
    private String orderNo;
    private Double amount;
    private Integer status; // 1:待支付 2:已支付 3:已完成 4:已取消
    private Date createTime;
    private List<OrderItem> items;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class OrderItem {
        private String productId;
        private String productName;
        private Integer quantity;
        private Double price;
    }
}

三、基础查询操作

3.1 简单查询

java 复制代码
@Service
public class MongoQueryService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    // 查询所有用户
    public List<User> findAllUsers() {
        return mongoTemplate.findAll(User.class);
    }
    
    // 条件查询
    public List<User> findUsersByCity(String city) {
        Query query = Query.query(Criteria.where("city").is(city));
        return mongoTemplate.find(query, User.class);
    }
    
    // 范围查询
    public List<User> findUsersByAgeRange(int minAge, int maxAge) {
        Query query = Query.query(
            Criteria.where("age").gte(minAge).lte(maxAge)
        );
        return mongoTemplate.find(query, User.class);
    }
}

四、分组查询(Aggregation)

4.1 基础分组统计

java 复制代码
@Service
public class AggregationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 按城市分组统计用户数量
     */
    public Map<String, Long> groupUsersByCity() {
        // 创建聚合操作
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group("city").count().as("count"),
            Aggregation.project("count").and("city").previousOperation(),
            Aggregation.sort(Sort.Direction.DESC, "count")
        );
        
        // 执行聚合查询
        AggregationResults<Document> results = 
            mongoTemplate.aggregate(aggregation, "users", Document.class);
        
        // 转换为Map
        return results.getMappedResults().stream()
            .collect(Collectors.toMap(
                doc -> doc.getString("city"),
                doc -> doc.getLong("count")
            ));
    }
    
    /**
     * 按年龄段分组统计
     */
    public List<Document> groupUsersByAgeGroup() {
        // 定义年龄阶段
        AggregationOperation ageGroup = context -> new Document("$addFields",
            new Document("ageGroup",
                new Document("$switch",
                    new Document("branches", Arrays.asList(
                        new Document("case", 
                            new Document("$lt", Arrays.asList("$age", 18)))
                            .append("then", "未成年"),
                        new Document("case", 
                            new Document("$and", Arrays.asList(
                                new Document("$gte", Arrays.asList("$age", 18)),
                                new Document("$lte", Arrays.asList("$age", 30))
                            )))
                            .append("then", "青年"),
                        new Document("case", 
                            new Document("$and", Arrays.asList(
                                new Document("$gt", Arrays.asList("$age", 30)),
                                new Document("$lte", Arrays.asList("$age", 50))
                            )))
                            .append("then", "中年"),
                        new Document("case", 
                            new Document("$gt", Arrays.asList("$age", 50)))
                            .append("then", "老年")
                    ))
                    .append("default", "未知")
                )
            )
        );
        
        Aggregation aggregation = Aggregation.newAggregation(
            ageGroup,
            Aggregation.group("ageGroup")
                .count().as("count")
                .avg("age").as("avgAge"),
            Aggregation.sort(Sort.Direction.DESC, "count")
        );
        
        return mongoTemplate.aggregate(aggregation, "users", Document.class)
            .getMappedResults();
    }
}

4.2 多字段分组与复杂统计

java 复制代码
@Service
public class ComplexAggregationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 按城市和状态分组统计订单
     */
    public List<OrderStats> groupOrdersByCityAndStatus() {
        // 使用$lookup进行关联查询
        LookupOperation lookup = LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userInfo");
        
        // 展开关联的用户信息
        UnwindOperation unwind = Aggregation.unwind("userInfo");
        
        Aggregation aggregation = Aggregation.newAggregation(
            lookup,
            unwind,
            Aggregation.group("userInfo.city", "status")
                .count().as("orderCount")
                .sum("amount").as("totalAmount")
                .avg("amount").as("avgAmount"),
            Aggregation.project()
                .and("_id.city").as("city")
                .and("_id.status").as("status")
                .and("orderCount").as("orderCount")
                .and("totalAmount").as("totalAmount")
                .and("avgAmount").as("avgAmount"),
            Aggregation.sort(Sort.Direction.DESC, "totalAmount")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", OrderStats.class)
            .getMappedResults();
    }
    
    /**
     * 统计DTO
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class OrderStats {
        private String city;
        private Integer status;
        private Long orderCount;
        private Double totalAmount;
        private Double avgAmount;
    }
    
    /**
     * 使用日期分组统计
     */
    public List<Document> groupOrdersByDate() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.project()
                .and("amount").as("amount")
                .and(DateOperators.dateOf("createTime")
                    .toString("%Y-%m-%d")).as("date"),
            Aggregation.group("date")
                .count().as("count")
                .sum("amount").as("totalAmount")
                .push("amount").as("amounts"),
            Aggregation.project()
                .and("_id").as("date")
                .and("count").as("count")
                .and("totalAmount").as("totalAmount")
                .and("amounts").as("amounts")
                .and(ArrayOperators.arrayOf("amounts")
                    .max()).as("maxAmount")
                .and(ArrayOperators.arrayOf("amounts")
                    .min()).as("minAmount"),
            Aggregation.sort(Sort.Direction.ASC, "date")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", Document.class)
            .getMappedResults();
    }
}

五、排序查询

5.1 单字段排序

java 复制代码
@Service
public class SortService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 单字段排序
     */
    public List<User> findUsersSorted(String field, boolean ascending) {
        Sort.Direction direction = ascending ? 
            Sort.Direction.ASC : Sort.Direction.DESC;
        Query query = new Query()
            .with(Sort.by(direction, field));
        
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 多字段排序
     */
    public List<User> findUsersMultiSorted() {
        Query query = new Query()
            .with(Sort.by(
                Sort.Order.desc("age"),
                Sort.Order.asc("createTime")
            ));
        
        return mongoTemplate.find(query, User.class);
    }
}

5.2 聚合查询中的排序

java 复制代码
@Service
public class AggregationSortService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 聚合结果排序
     */
    public List<Document> findTopCitiesByOrderAmount(int limit) {
        LookupOperation lookup = LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userInfo");
        
        Aggregation aggregation = Aggregation.newAggregation(
            lookup,
            Aggregation.unwind("userInfo"),
            Aggregation.group("userInfo.city")
                .sum("amount").as("totalAmount")
                .count().as("orderCount"),
            Aggregation.project()
                .and("_id").as("city")
                .and("totalAmount").as("totalAmount")
                .and("orderCount").as("orderCount")
                .andExpression("totalAmount / orderCount").as("avgAmount"),
            Aggregation.sort(Sort.Direction.DESC, "totalAmount"),
            Aggregation.limit(limit)
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", Document.class)
            .getMappedResults();
    }
}

六、分页查询

6.1 基础分页

java 复制代码
@Service
public class PaginationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 基础分页查询
     */
    public Page<User> findUsersByPage(int page, int size, String sortField, boolean ascending) {
        // 构建查询条件
        Query query = new Query();
        
        // 排序
        Sort.Direction direction = ascending ? 
            Sort.Direction.ASC : Sort.Direction.DESC;
        query.with(Sort.by(direction, sortField));
        
        // 计算总数
        long total = mongoTemplate.count(query, User.class);
        
        // 分页
        query.skip((long) (page - 1) * size).limit(size);
        
        // 查询数据
        List<User> content = mongoTemplate.find(query, User.class);
        
        return new PageImpl<>(content, PageRequest.of(page - 1, size), total);
    }
    
    /**
     * 分页查询DTO
     */
    @Data
    @AllArgsConstructor
    public static class PageResult<T> {
        private List<T> content;
        private int currentPage;
        private int pageSize;
        private long totalElements;
        private int totalPages;
        
        public PageResult(Page<T> page) {
            this.content = page.getContent();
            this.currentPage = page.getNumber() + 1;
            this.pageSize = page.getSize();
            this.totalElements = page.getTotalElements();
            this.totalPages = page.getTotalPages();
        }
    }
}

6.2 聚合分页查询

java 复制代码
@Service
public class AggregationPaginationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 聚合查询分页
     */
    public PageResult<Document> aggregateWithPagination(
            int page, int size, String city) {
        
        // 构建聚合管道
        List<AggregationOperation> operations = new ArrayList<>();
        
        // 条件过滤
        if (StringUtils.isNotBlank(city)) {
            operations.add(Aggregation.match(Criteria.where("city").is(city)));
        }
        
        // 添加分页操作前先获取总数
        Aggregation countAggregation = Aggregation.newAggregation(
            Aggregation.group().count().as("total")
        );
        
        // 添加其他聚合操作
        operations.addAll(Arrays.asList(
            Aggregation.group("age")
                .count().as("count")
                .avg("age").as("avgAge"),
            Aggregation.sort(Sort.Direction.DESC, "count"),
            Aggregation.skip((long) (page - 1) * size),
            Aggregation.limit(size)
        ));
        
        // 执行数据查询
        Aggregation dataAggregation = Aggregation.newAggregation(operations);
        List<Document> content = mongoTemplate
            .aggregate(dataAggregation, "users", Document.class)
            .getMappedResults();
        
        // 执行计数查询
        Document countResult = mongoTemplate
            .aggregate(countAggregation, "users", Document.class)
            .getUniqueMappedResult();
        long total = countResult != null ? countResult.getLong("total") : 0;
        
        // 计算总页数
        int totalPages = (int) Math.ceil((double) total / size);
        
        return new PageResult<>(content, page, size, total, totalPages);
    }
    
    /**
     * 使用facet实现高效分页(MongoDB 3.4+)
     */
    public Map<String, Object> facetPagination(int page, int size) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.facet(
                // 分页数据
                Aggregation.skip((long) (page - 1) * size),
                Aggregation.limit(size)
            ).as("data")
            .and(
                // 统计信息
                Aggregation.count().as("total")
            ).as("metadata")
        );
        
        return mongoTemplate.aggregate(aggregation, "users", Document.class)
            .getUniqueMappedResult();
    }
}

七、连表查询($lookup)

7.1 基础连表查询

java 复制代码
@Service
public class LookupService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 一对一关联查询
     */
    public List<OrderWithUser> findOrdersWithUserInfo() {
        LookupOperation lookup = LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userInfo");
        
        // 由于是一对一,使用$unwind展开数组
        UnwindOperation unwind = Aggregation.unwind("userInfo", true);
        
        Aggregation aggregation = Aggregation.newAggregation(
            lookup,
            unwind,
            Aggregation.project()
                .and("id").as("orderId")
                .and("orderNo").as("orderNo")
                .and("amount").as("amount")
                .and("status").as("status")
                .and("userInfo.username").as("username")
                .and("userInfo.email").as("email")
                .and("userInfo.city").as("city")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", OrderWithUser.class)
            .getMappedResults();
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class OrderWithUser {
        private String orderId;
        private String orderNo;
        private Double amount;
        private Integer status;
        private String username;
        private String email;
        private String city;
    }
}

7.2 多表关联与复杂查询

java 复制代码
@Service  
public class ComplexLookupService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 多条件关联查询
     */
    public List<Document> findOrdersWithUserDetails(String city, Date startDate) {
        // 定义匹配条件
        Criteria userCriteria = Criteria.where("city").is(city);
        Criteria orderCriteria = Criteria.where("createTime").gte(startDate);
        
        LookupOperation lookup = LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userDetails")
            .let(OperationsEvaluator.LetOperations.newLetOperations()
                .addVariable("orderUserId", "$userId"))
            .pipeline(Aggregation.newAggregation(
                Aggregation.match(
                    Criteria.where("_id").is("$$orderUserId")
                        .and("city").is(city)
                )
            ));
        
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(orderCriteria),
            lookup,
            Aggregation.unwind("userDetails", true),
            Aggregation.match(Criteria.where("userDetails").ne(null)),
            Aggregation.project()
                .and("orderNo").as("orderNo")
                .and("amount").as("amount")
                .and("createTime").as("createTime")
                .and("userDetails.username").as("username")
                .and("userDetails.city").as("userCity")
                .and(ArrayOperators.arrayOf("items").length()).as("itemCount"),
            Aggregation.sort(Sort.Direction.DESC, "createTime")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", Document.class)
            .getMappedResults();
    }
    
    /**
     * 嵌套数组查询
     */
    public List<Document> findOrdersWithProductInfo() {
        // 模拟产品表关联(实际中应该有products集合)
        LookupOperation productLookup = LookupOperation.newLookup()
            .from("products")  // 假设有products集合
            .localField("items.productId")
            .foreignField("_id")
            .as("productInfo");
        
        // 添加字段处理
        AddFieldsOperation addFields = Aggregation.addFields()
            .addFieldWithValue("itemsWithProduct", new Document("$map", 
                new Document("input", "$items")
                    .append("as", "item")
                    .append("in", new Document()
                        .append("productId", "$$item.productId")
                        .append("productName", "$$item.productName")
                        .append("quantity", "$$item.quantity")
                        .append("price", "$$item.price")
                        .append("subTotal", new Document("$multiply", 
                            Arrays.asList("$$item.quantity", "$$item.price")))
                    )
            )).build();
        
        Aggregation aggregation = Aggregation.newAggregation(
            addFields,
            Aggregation.unwind("itemsWithProduct"),
            Aggregation.group("orderNo")
                .first("userId").as("userId")
                .first("amount").as("totalAmount")
                .push("itemsWithProduct").as("orderItems")
                .sum("itemsWithProduct.subTotal").as("calculatedTotal"),
            Aggregation.match(Criteria.where("calculatedTotal")
                .gte(1000)),
            Aggregation.sort(Sort.Direction.DESC, "calculatedTotal")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", Document.class)
            .getMappedResults();
    }
}

7.3 多层嵌套关联

java 复制代码
@Service
public class NestedLookupService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 多层关联查询示例
     */
    public List<Document> findCompleteOrderInfo() {
        // 第一层:关联用户信息
        LookupOperation userLookup = LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userInfo");
        
        // 第二层:关联产品信息(假设有products集合)
        LookupOperation productLookup = LookupOperation.newLookup()
            .from("products")
            .localField("items.productId")
            .foreignField("_id")
            .as("productDetails");
        
        // 第三层:关联物流信息(假设有logistics集合)
        LookupOperation logisticsLookup = LookupOperation.newLookup()
            .from("logistics")
            .localField("orderNo")
            .foreignField("orderNo")
            .as("logisticsInfo");
        
        Aggregation aggregation = Aggregation.newAggregation(
            userLookup,
            Aggregation.unwind("userInfo", true),
            productLookup,
            logisticsLookup,
            Aggregation.unwind("logisticsInfo", true),
            Aggregation.addFields()
                .addFieldWithValue("orderDate", 
                    DateOperators.dateOf("createTime").toString("%Y-%m-%d"))
                .build(),
            Aggregation.project()
                .andInclude("orderNo", "amount", "status")
                .and("userInfo.username").as("customerName")
                .and("userInfo.phone").as("customerPhone")
                .and("userInfo.address").as("deliveryAddress")
                .and("orderDate").as("orderDate")
                .and("logisticsInfo.trackingNo").as("trackingNumber")
                .and("logisticsInfo.status").as("logisticsStatus")
                .and("items").as("orderItems")
                .and("productDetails").as("productInfo"),
            Aggregation.match(Criteria.where("status").is(2)), // 只查询已支付订单
            Aggregation.sort(Sort.Direction.DESC, "createTime")
        );
        
        return mongoTemplate.aggregate(aggregation, "orders", Document.class)
            .getMappedResults();
    }
}

八、性能优化技巧

8.1 索引优化

java 复制代码
@Configuration
public class MongoIndexConfig {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    @PostConstruct
    public void createIndexes() {
        // 创建单字段索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("city", Sort.Direction.ASC)
        );
        
        // 创建复合索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("city", Sort.Direction.ASC)
                       .on("age", Sort.Direction.DESC)
        );
        
        // 创建TTL索引(自动过期)
        mongoTemplate.indexOps(Order.class).ensureIndex(
            new Index().on("createTime", Sort.Direction.ASC)
                       .expire(30, TimeUnit.DAYS) // 30天后自动删除
        );
        
        // 创建文本索引
        mongoTemplate.indexOps(User.class).ensureIndex(
            new Index().on("username", Sort.Direction.ASC)
                       .named("username_text_idx")
        );
    }
}

8.2 查询优化

java 复制代码
@Service
public class QueryOptimizationService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 使用投影减少返回字段
     */
    public List<Document> findUsersWithProjection() {
        Query query = new Query();
        query.fields()
            .include("username")
            .include("email")
            .include("city")
            .exclude("id");
        
        return mongoTemplate.find(query, Document.class, "users");
    }
    
    /**
     * 使用 hint 强制使用索引
     */
    public List<User> findUsersWithIndexHint() {
        Query query = Query.query(Criteria.where("city").is("北京"));
        query.withHint("city_1_age_-1"); // 使用指定的索引
        
        return mongoTemplate.find(query, User.class);
    }
    
    /**
     * 批量查询优化
     */
    public Map<String, User> batchFindUsers(List<String> userIds) {
        // 使用 in 查询而不是循环查询
        Query query = Query.query(Criteria.where("id").in(userIds));
        List<User> users = mongoTemplate.find(query, User.class);
        
        return users.stream()
            .collect(Collectors.toMap(User::getId, Function.identity()));
    }
}

九、完整示例:综合查询服务

java 复制代码
@Service
@Slf4j
public class ComprehensiveQueryService {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 综合查询:分组 + 排序 + 分页 + 关联
     */
    public PageResult<OrderSummaryDTO> comprehensiveQuery(
            OrderQueryParam param) {
        
        // 1. 构建聚合管道
        List<AggregationOperation> operations = buildAggregationPipeline(param);
        
        // 2. 添加分页
        operations.add(Aggregation.skip((long) (param.getPage() - 1) * param.getSize()));
        operations.add(Aggregation.limit(param.getSize()));
        
        // 3. 执行数据查询
        Aggregation dataAggregation = Aggregation.newAggregation(operations);
        List<OrderSummaryDTO> content = mongoTemplate
            .aggregate(dataAggregation, "orders", OrderSummaryDTO.class)
            .getMappedResults();
        
        // 4. 执行总数查询
        long total = countTotal(param);
        
        // 5. 构建返回结果
        int totalPages = (int) Math.ceil((double) total / param.getSize());
        return new PageResult<>(content, param.getPage(), param.getSize(), total, totalPages);
    }
    
    private List<AggregationOperation> buildAggregationPipeline(OrderQueryParam param) {
        List<AggregationOperation> operations = new ArrayList<>();
        
        // 条件过滤
        Criteria criteria = buildCriteria(param);
        if (criteria != null) {
            operations.add(Aggregation.match(criteria));
        }
        
        // 关联用户表
        operations.add(buildLookupOperation());
        operations.add(Aggregation.unwind("userInfo", true));
        
        // 分组统计
        operations.add(Aggregation.group("userInfo.city", "status")
            .count().as("orderCount")
            .sum("amount").as("totalAmount")
            .avg("amount").as("avgAmount"));
        
        // 投影
        operations.add(Aggregation.project()
            .and("_id.city").as("city")
            .and("_id.status").as("status")
            .and("orderCount").as("orderCount")
            .and("totalAmount").as("totalAmount")
            .and("avgAmount").as("avgAmount")
            .andExclude("_id"));
        
        // 排序
        if (StringUtils.isNotBlank(param.getSortField())) {
            operations.add(Aggregation.sort(
                param.getSortOrder() == 1 ? 
                Sort.Direction.ASC : Sort.Direction.DESC,
                param.getSortField()
            ));
        } else {
            operations.add(Aggregation.sort(Sort.Direction.DESC, "totalAmount"));
        }
        
        return operations;
    }
    
    private Criteria buildCriteria(OrderQueryParam param) {
        Criteria criteria = new Criteria();
        List<Criteria> criteriaList = new ArrayList<>();
        
        if (StringUtils.isNotBlank(param.getCity())) {
            criteriaList.add(Criteria.where("userInfo.city").is(param.getCity()));
        }
        
        if (param.getStatus() != null) {
            criteriaList.add(Criteria.where("status").is(param.getStatus()));
        }
        
        if (param.getStartDate() != null && param.getEndDate() != null) {
            criteriaList.add(Criteria.where("createTime")
                .gte(param.getStartDate()).lte(param.getEndDate()));
        }
        
        if (param.getMinAmount() != null) {
            criteriaList.add(Criteria.where("amount").gte(param.getMinAmount()));
        }
        
        if (param.getMaxAmount() != null) {
            criteriaList.add(Criteria.where("amount").lte(param.getMaxAmount()));
        }
        
        return criteriaList.isEmpty() ? null : criteria.andOperator(criteriaList.toArray(new Criteria[0]));
    }
    
    private LookupOperation buildLookupOperation() {
        return LookupOperation.newLookup()
            .from("users")
            .localField("userId")
            .foreignField("_id")
            .as("userInfo");
    }
    
    private long countTotal(OrderQueryParam param) {
        List<AggregationOperation> countOperations = new ArrayList<>();
        
        Criteria criteria = buildCriteria(param);
        if (criteria != null) {
            countOperations.add(Aggregation.match(criteria));
        }
        
        countOperations.add(buildLookupOperation());
        countOperations.add(Aggregation.unwind("userInfo", true));
        countOperations.add(Aggregation.group("userInfo.city", "status"));
        countOperations.add(Aggregation.group().count().as("total"));
        
        Aggregation countAggregation = Aggregation.newAggregation(countOperations);
        Document countResult = mongoTemplate
            .aggregate(countAggregation, "orders", Document.class)
            .getUniqueMappedResult();
        
        return countResult != null ? countResult.getLong("total") : 0;
    }
    
    @Data
    public static class OrderQueryParam {
        private Integer page = 1;
        private Integer size = 20;
        private String city;
        private Integer status;
        private Date startDate;
        private Date endDate;
        private Double minAmount;
        private Double maxAmount;
        private String sortField = "totalAmount";
        private Integer sortOrder = -1; // -1: desc, 1: asc
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class OrderSummaryDTO {
        private String city;
        private Integer status;
        private Long orderCount;
        private Double totalAmount;
        private Double avgAmount;
    }
}

十、最佳实践与注意事项

10.1 性能最佳实践

  1. 合理使用索引:为频繁查询的字段创建索引
  2. 避免全表扫描:尽量使用索引字段进行查询
  3. 限制返回字段:使用投影减少数据传输
  4. 分页查询优化:避免使用skip进行深度分页
  5. 批量操作:使用bulkWrite进行批量操作

10.2 代码组织建议

  1. 使用Repository模式:将数据访问逻辑封装在Repository中
  2. DTO转换:使用DTO进行数据传输,避免暴露实体类
  3. 异常处理:统一处理MongoDB异常
  4. 日志记录:记录关键操作的执行时间和结果
  5. 监控告警:监控慢查询和异常查询
相关推荐
范纹杉想快点毕业2 小时前
入门工程师指南:基于CRC校验的通信协议底层C语言实现
c语言·开发语言·mongodb
TG:@yunlaoda360 云老大2 小时前
如何确保华为云国际站代理商的服务可用性?
数据库·人工智能·华为云
又是忙碌的一天2 小时前
Myvatis 动态查询及关联查询
java·数据库·mybatis
java1234_小锋2 小时前
Redis是单线程还是多线程?
数据库·redis·缓存
若尘啊若辰2 小时前
安全通用要求之十安全运维管理
网络·数据库·网络安全·等保·等级保护·安全通用要求
云和恩墨2 小时前
数智聚力,开源破局!openGauss Summit 2025见证数据库产业革新,云和恩墨深耕生态载誉而归
数据库·开源
酸菜牛肉汤面3 小时前
12、数据库为什么使用B+树而不是B树
数据结构·数据库·b树
l1t3 小时前
PostgreSQL Distinct On 关键字的用法
数据库·sql·postgresql
TDengine (老段)3 小时前
快速掌握时序数据库 + TDengine 学习指南
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据