java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot整合MongoDB

SpringBoot整合MongoDB的完整操作指南

作者:J_liaty

在实际项目开发中,合理封装MongoDB的操作工具类,可以大幅提升代码复用性和维护性,本文将带你从零开始实现一个功能完善的MongoDB工具类,涵盖基础CRUD和高级查询功能,需要的朋友可以参考下

依赖配置

pom.xml 中添加以下依赖:

<dependencies>
    <!-- SpringBoot Starter 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>
    
    <!-- Hutool工具包 (可选) -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.18</version>
    </dependency>
</dependencies>

配置文件

application.yml 中配置MongoDB连接信息:

基础配置(使用URI方式)

spring:
  data:
    mongodb:
      # MongoDB连接URI
      # 格式:mongodb://[username:password@]host:port/database
      # 示例说明:
      # - mongodb://localhost:27017:本地MongoDB,默认端口27017
      # - mongodb://admin:123456@localhost:27017:带用户名密码的连接
      # - mongodb://host1:27017,host2:27017:连接副本集
      uri: mongodb://localhost:27017
      
      # 数据库名称
      # 说明:指定要使用的MongoDB数据库
      # 注意:数据库不存在时会自动创建
      database: my_database
      
      # 自动创建索引
      # 说明:启动时自动为实体类上的@Indexed注解创建索引
      # 优点:开发环境方便,自动创建索引
      # 缺点:生产环境可能影响启动速度
      # 建议:生产环境设置为false,手动管理索引
      auto-index-creation: true

完整配置(详细配置方式)

spring:
  data:
    mongodb:
      # ==================== 连接基础配置 ====================
      
      # MongoDB服务器地址
      # 说明:可以是IP地址或域名
      # 示例:
      # - localhost:本地连接
      # - 192.168.1.100:指定IP地址
      # - mongodb.example.com:域名连接
      host: localhost
      
      # MongoDB服务器端口
      # 说明:MongoDB默认端口为27017
      # 注意:确保端口没有被防火墙阻止
      port: 27017
      
      # 数据库名称
      # 说明:指定要连接的数据库名称
      database: my_database
      
      # ==================== 认证配置 ====================
      
      # 用户名
      # 说明:如果MongoDB启用了认证,需要提供用户名
      # 注意:用户必须有对应数据库的访问权限
      # 示例:admin用户通常是管理员账户
      username: admin
      
      # 密码
      # 说明:对应用户名的密码
      # 安全建议:
      # - 生产环境不要明文存储密码
      # - 建议使用环境变量或加密配置
      # - 示例:${MONGODB_PASSWORD:admin123}
      password: admin123
      
      # 认证数据库
      # 说明:用户认证信息存储的数据库
      # 默认值:admin
      # 注意:大多数用户的认证信息存储在admin数据库中
      authentication-database: admin
      
      # ==================== 索引配置 ====================
      
      # 自动创建索引
      # 说明:应用启动时自动创建索引
      # 开发环境:建议设置为true,方便开发
      # 生产环境:建议设置为false,手动管理索引更安全
      # 原因:
      # - 生产环境索引创建可能影响性能
      # - 可以提前规划好索引,避免运行时创建
      auto-index-creation: true
      
      # ==================== 连接池配置 ====================
      
      # 每个主机最小连接数
      # 说明:连接池中保持的最小连接数量
      # 作用:避免频繁创建和销毁连接的开销
      # 建议:
      # - 低负载应用:5-10
      # - 中等负载应用:10-20
      # - 高负载应用:20-50
      min-connections-per-host: 10
      
      # 每个主机最大连接数
      # 说明:连接池中允许的最大连接数量
      # 作用:限制并发连接数,防止资源耗尽
      # 建议:
      # - 根据应用并发量设置
      # - 一般设置为CPU核心数的2-4倍
      # - 例如:8核CPU可以设置为32-100
      max-connections-per-host: 100
      
      # 连接线程阻塞倍数
      # 说明:允许等待连接的最大线程数
      # 计算公式:max-connections-per-host × multiplier
      # 示例:100 × 5 = 500个线程可以等待连接
      # 作用:当连接池耗尽时,允许一定数量的线程等待
      # 建议:一般设置为3-5倍
      threads-allowed-to-block-for-connection-multiplier: 5
      
      # 连接超时时间(毫秒)
      # 说明:获取连接的最大等待时间
      # 默认值:10000毫秒(10秒)
      # 作用:防止线程无限等待连接
      # 建议:
      # - 开发环境:5000-10000毫秒
      # - 生产环境:根据响应时间要求调整
      # - 网络不稳定时可以适当增加
      connection-timeout: 10000
      
      # Socket超时时间(毫秒)
      # 说明:Socket读写操作的超时时间
      # 默认值:0(表示无超时,永久等待)
      # 作用:防止网络故障导致请求挂起
      # 建议:
      # - 简单查询:30000-60000毫秒(30-60秒)
      # - 复杂查询:120000毫秒(120秒)或更长
      # - 注意:超时时间要根据实际业务需求设置
      socket-timeout: 60000
      
      # ==================== 其他可选配置 ====================
      
      # 编解码器(可选)
      # 说明:指定MongoDB驱动使用的编解码器
      # 默认值:org.bson.codecs.DocumentCodec
      # 用途:自定义文档的编码解码逻辑
      # codec: com.example.mongodb.CustomCodec
      
      # UUID表示方式(可选)
      # 说明:UUID类型在MongoDB中的存储格式
      # 可选值:
      # - JAVA_LEGACY:Java的传统格式(3.0之前)
      # - STANDARD:标准格式(推荐)
      # - C_SHARP_LEGACY:C#的传统格式
      # - PYTHON_LEGACY:Python的传统格式
      # uuid-representation: STANDARD
      
      # 副本集名称(可选)
      # 说明:连接副本集时指定的副本集名称
      # 用途:连接副本集时必须指定
      # 示例:replica-set-name
      # replica-set-name: myReplicaSet
      
      # 读取偏好(可选)
      # 说明:指定读取数据的首选节点
      # 可选值:
      # - primary:只从主节点读取(默认)
      # - primaryPreferred:优先从主节点读取
      # - secondary:只从从节点读取
      # - secondaryPreferred:优先从从节点读取
      # - nearest:从最近节点读取
      # read-preference: secondaryPreferred
      
      # 写关注级别(可选)
      # 说明:指定写入操作的安全级别
      # 可选值:
      # - ACKNOWLEDGED:默认,确认写入主节点
      # - W1:确认写入主节点和一个从节点
      # - W2:确认写入主节点和两个从节点
      # - MAJORITY:确认写入大多数节点
      # - UNACKNOWLEDGED:不等待确认,性能最好但不安全
      # write-concern: ACKNOWLEDGED

多环境配置示例

开发环境配置(application-dev.yml)

spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: dev_database
      username: dev_user
      password: dev_password
      auto-index-creation: true
      # 开发环境连接池配置较小
      min-connections-per-host: 5
      max-connections-per-host: 20
      connection-timeout: 10000
      socket-timeout: 30000

测试环境配置(application-test.yml)

spring:
  data:
    mongodb:
      host: test-mongodb.example.com
      port: 27017
      database: test_database
      username: test_user
      password: ${MONGODB_TEST_PASSWORD}
      auto-index-creation: false
      # 测试环境连接池配置中等
      min-connections-per-host: 10
      max-connections-per-host: 50
      connection-timeout: 10000
      socket-timeout: 60000

生产环境配置(application-prod.yml)

spring:
  data:
    mongodb:
      # 生产环境使用URI方式连接副本集
      uri: mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@mongo1.example.com:27017,mongo2.example.com:27017,mongo3.example.com:27017/${MONGODB_DATABASE}?replicaSet=prodReplicaSet&connectTimeoutMS=10000&socketTimeoutMS=60000
      
      auto-index-creation: false
      
      # 生产环境连接池配置较大
      min-connections-per-host: 20
      max-connections-per-host: 100
      threads-allowed-to-block-for-connection-multiplier: 5
      connection-timeout: 10000
      socket-timeout: 60000
      
      # 生产环境使用副本集,配置读取偏好和写关注
      # read-preference: secondaryPreferred
      # write-concern: MAJORITY

配置参数详解

连接池参数对比表

参数说明推荐值(小应用)推荐值(大应用)调优建议
min-connections-per-host最小连接数5-1020-50根据平均并发量设置
max-connections-per-host最大连接数20-50100-200根据峰值并发量设置
threads-allowed-to-block-for-connection-multiplier阻塞倍数55一般不需要调整
connection-timeout连接超时(ms)1000010000网络不稳定时可增加
socket-timeoutSocket超时(ms)30000-6000060000-120000根据查询复杂度调整

常见问题与解决方案

1. 连接超时问题

现象:应用启动或查询时频繁出现连接超时

解决方案

spring:
  data:
    mongodb:
      # 增加连接超时时间
      connection-timeout: 20000
      # 增加Socket超时时间
      socket-timeout: 120000
      # 增加连接池大小
      max-connections-per-host: 200

2. 副本集连接问题

现象:连接副本集时出现"not master and slaveOk=false"错误

解决方案

spring:
  data:
    mongodb:
      # 使用URI方式连接副本集
      uri: mongodb://user:pass@host1:27017,host2:27017,host3:27017/database?replicaSet=myReplicaSet&readPreference=secondaryPreferred

3. 认证失败问题

现象:认证失败,无法连接数据库

解决方案

spring:
  data:
    mongodb:
      username: your_username
      password: your_password
      authentication-database: admin  # 确保指定正确的认证数据库

安全建议

密码安全

网络安全

权限控制

核心工具类实现

1. 响应结果封装类

package com.example.mongodb.common;

import lombok.Data;
import java.io.Serializable;

/**
 * 统一响应结果封装
 */
@Data
public class Result<T> implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private Integer code;
    private String message;
    private T data;
    private Long total;
    
    public Result() {
    }
    
    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
    
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }
    
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(200, message, data);
    }
    
    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }
    
    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
    
    public Result<T> total(Long total) {
        this.total = total;
        return this;
    }
}

2. 分页请求参数类

package com.example.mongodb.common;

import lombok.Data;

/**
 * 分页请求参数
 */
@Data
public class PageRequest {
    
    /** 当前页码 */
    private Integer pageNum = 1;
    
    /** 每页条数 */
    private Integer pageSize = 10;
    
    /** 排序字段 */
    private String sortField;
    
    /** 排序方式:asc/desc */
    private String sortOrder = "asc";
    
    public PageRequest() {
    }
    
    public PageRequest(Integer pageNum, Integer pageSize) {
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }
    
    public int getSkip() {
        return (pageNum - 1) * pageSize;
    }
}

3. 核心MongoDB工具类(详细注释版)

package com.example.mongodb.utils;

import com.example.mongodb.common.PageRequest;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
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.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.regex.Pattern;

/**
 * MongoDB工具类 - 提供完整的CRUD操作和高级查询功能
 * 
 * ========================================
 * 使用说明:
 * ========================================
 * 1. 使用泛型<T>支持任意实体类型
 * 2. 基于MongoTemplate实现,提供了丰富的封装方法
 * 3. 支持事务、聚合、批量操作等高级特性
 * 4. 所有方法都有详细注释,方便理解和使用
 * 
 * @author 作者
 * @date 2026-02-09
 */
@Slf4j
@Component
public class MongoUtils<T> {
    
    // ==================== 核心依赖注入 ====================
    
    /**
     * MongoDB操作模板
     * 说明:Spring Data MongoDB的核心类,提供了对MongoDB数据库的所有操作
     * 使用场景:执行查询、插入、更新、删除等所有数据库操作
     */
    @Autowired
    private MongoTemplate mongoTemplate;
    
    /**
     * 获取集合名称
     * 功能:根据实体类获取对应的MongoDB集合名称(相当于关系数据库的表名)
     * 实现原理:通过@Document注解的collection属性,或使用类名小写形式
     * 
     * @param entityClass 实体类类型(带@Document注解的类)
     * @return 集合名称(MongoDB中的集合名,如"user"、"order"等)
     */
    private String getCollectionName(Class<?> entityClass) {
        return mongoTemplate.getCollectionName(entityClass);
    }
    
    // ==================== 基础CRUD操作 ====================
    
    /**
     * 保存单个实体
     * 功能:保存或更新实体对象
     * 
     * 执行逻辑:
     * 1. 如果实体对象包含_id字段且值不为空,则执行更新操作(upsert)
     * 2. 如果实体对象不包含_id或_id为空,则执行插入操作
     * 3. 插入成功后,MongoDB会自动生成ObjectId并赋值给_id字段
     * 
     * 使用场景:
     * - 新增数据时直接调用
     * - 修改数据时传入包含_id的对象
     * 
     * @param entity 要保存的实体对象(必须包含@Document注解)
     * @return 保存后的实体对象(包含生成的_id)
     */
    public T save(T entity) {
        return mongoTemplate.save(entity);
    }
    
    /**
     * 批量保存实体
     * 功能:一次性保存多个实体对象,提高批量插入性能
     * 
     * 性能优势:
     * - 相比循环调用save()方法,批量操作可以减少网络往返次数
     * - MongoDB支持批量插入,内部优化了写入性能
     * 
     * 注意事项:
     * - 如果数据量很大(如超过10000条),建议分批插入
     * - 如果其中某个文档插入失败,整个操作可能会回滚
     * 
     * @param entities 实体集合(List、Set等Collection类型)
     * @return 保存后的实体集合(包含生成的_id)
     */
    public List<T> saveBatch(Collection<T> entities) {
        return (List<T>) mongoTemplate.insertAll(entities);
    }
    
    /**
     * 插入单个实体
     * 功能:直接插入实体,不检查是否已存在
     * 
     * 与save()的区别:
     * - save():智能判断是插入还是更新(基于_id)
     * - insert():强制插入,如果_id已存在会抛出DuplicateKeyException异常
     * 
     * 使用场景:
     * - 确保不会重复插入时使用
     * - 需要明确区分新增和更新操作时使用
     * 
     * @param entity 要插入的实体对象
     * @return 插入后的实体对象
     * @throws org.springframework.dao.DuplicateKeyException 如果_id已存在
     */
    public T insert(T entity) {
        return mongoTemplate.insert(entity);
    }
    
    /**
     * 批量插入实体
     * 功能:批量插入到指定集合中,适合大量数据初始化
     * 
     * 使用说明:
     * - 通过entityClass参数指定目标集合
     * - 相比saveBatch(),此方法可以明确指定集合名称
     * 
     * @param entities 实体集合
     * @param entityClass 实体类类型(用于确定集合名称)
     * @return 插入后的实体集合
     */
    public List<T> insertBatch(Collection<T> entities, Class<T> entityClass) {
        return (List<T>) mongoTemplate.insert(entities, entityClass);
    }
    
    /**
     * 根据ID查询实体
     * 功能:通过MongoDB的文档ID(_id字段)精确查询单个文档
     * 
     * 执行逻辑:
     * 1. 构造查询条件:WHERE _id = ?
     * 2. 执行查询,返回匹配的第一个文档
     * 3. 将文档映射为Java实体对象
     * 
     * ID类型说明:
     * - MongoDB的_id默认类型是ObjectId(24位十六进制字符串)
     * - Spring Data MongoDB支持String、ObjectId、Long等多种类型
     * 
     * @param id 实体ID(通常是ObjectId类型或字符串)
     * @param entityClass 实体类类型(用于结果映射)
     * @return 查询到的实体对象,不存在则返回null
     */
    public T findById(Object id, Class<T> entityClass) {
        return mongoTemplate.findById(id, entityClass);
    }
    
    /**
     * 查询所有实体
     * 功能:查询集合中的所有文档
     * 
     * 性能警告:
     * - ⚠️ 如果集合中数据量很大,此方法可能导致内存溢出
     * - ⚠️ 生产环境中谨慎使用,建议使用分页查询
     * 
     * 使用场景:
     * - 数据量小的配置表
     * - 数据导入导出操作
     * 
     * @param entityClass 实体类类型
     * @return 所有实体的列表
     */
    public List<T> findAll(Class<T> entityClass) {
        return mongoTemplate.findAll(entityClass);
    }
    
    /**
     * 根据条件查询单个实体
     * 功能:根据自定义查询条件返回第一个匹配的文档
     * 
     * 执行逻辑:
     * 1. 执行传入的查询条件
     * 2. 只返回第一个匹配的文档
     * 3. 如果没有匹配文档,返回null
     * 
     * 使用场景:
     * - 根据唯一键查询(如用户名、手机号)
     * - 获取最新的一条记录(配合排序)
     * 
     * @param query 查询条件对象(通过Criteria构造)
     * @param entityClass 实体类类型
     * @return 查询到的实体对象,不存在则返回null
     */
    public T findOne(Query query, Class<T> entityClass) {
        return mongoTemplate.findOne(query, entityClass);
    }
    
    /**
     * 根据条件查询实体列表
     * 功能:根据自定义查询条件返回所有匹配的文档
     * 
     * 执行逻辑:
     * 1. 执行传入的查询条件
     * 2. 返回所有匹配的文档列表
     * 3. 如果没有匹配文档,返回空列表(不会返回null)
     * 
     * 使用场景:
     * - 条件查询(如查询某个状态的所有用户)
     * - 范围查询(如查询某个时间段的所有订单)
     * 
     * @param query 查询条件对象(可以通过Criteria添加多个条件)
     * @param entityClass 实体类类型
     * @return 匹配的实体列表(可能为空列表)
     */
    public List<T> find(Query query, Class<T> entityClass) {
        return mongoTemplate.find(query, entityClass);
    }
    
    /**
     * 根据ID更新实体
     * 功能:根据文档ID更新指定字段,只更新传入的字段
     * 
     * 执行逻辑:
     * 1. 构造查询条件:WHERE _id = ?
     * 2. 执行更新操作,只修改Update对象中指定的字段
     * 3. 其他字段保持不变
     * 
     * 与save()的区别:
     * - save():会替换整个文档(未传入的字段会被清空)
     * - updateById():只更新指定字段(更安全,性能更好)
     * 
     * @param id 文档ID(MongoDB的_id字段值)
     * @param update 更新操作对象(包含要更新的字段和新值)
     * @param entityClass 实体类类型
     * @return 更新结果对象,包含匹配数和修改数等信息
     */
    public UpdateResult updateById(Object id, Update update, Class<T> entityClass) {
        // 构造查询条件:ID等于传入的id
        Query query = new Query(Criteria.where("_id").is(id));
        return mongoTemplate.updateFirst(query, update, entityClass);
    }
    
    /**
     * 根据条件更新第一个匹配的文档
     * 功能:只更新查询到的第一个文档,即使有多个文档匹配条件
     * 
     * 执行逻辑:
     * 1. 根据query条件查找匹配的文档
     * 2. 只更新第一个匹配的文档
     * 3. 返回更新结果(包含影响的文档数)
     * 
     * 使用场景:
     * - 唯一键字段的更新
     * - 只需要更新一条记录的情况
     * 
     * @param query 查询条件对象(用于定位要更新的文档)
     * @param update 更新操作对象(包含要更新的字段和新值)
     * @param entityClass 实体类类型
     * @return 更新结果对象,包含修改的文档数等信息
     */
    public UpdateResult updateFirst(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.updateFirst(query, update, entityClass);
    }
    
    /**
     * 根据条件更新所有匹配的文档
     * 功能:更新查询条件匹配的所有文档
     * 
     * 执行逻辑:
     * 1. 根据query条件查找所有匹配的文档
     * 2. 更新所有匹配的文档
     * 3. 返回更新结果(包含影响的文档数)
     * 
     * 性能注意事项:
     * - 如果匹配的文档数量很多,此操作可能耗时较长
     * - 建议在查询条件中添加索引以提高性能
     * 
     * 使用场景:
     * - 批量修改(如将所有状态为0的用户改为1)
     * - 数据修复和迁移
     * 
     * @param query 查询条件对象(用于定位要更新的文档)
     * @param update 更新操作对象(包含要更新的字段和新值)
     * @param entityClass 实体类类型
     * @return 更新结果对象,包含修改的文档数等信息
     */
    public UpdateResult updateMulti(Query query, Update update, Class<T> entityClass) {
        return mongoTemplate.updateMulti(query, update, entityClass);
    }
    
    /**
     * 根据ID删除实体
     * 功能:删除指定ID的文档
     * 
     * 执行逻辑:
     * 1. 构造查询条件:WHERE _id = ?
     * 2. 执行删除操作
     * 3. 返回删除结果(包含删除的文档数)
     * 
     * 使用场景:
     * - 根据主键删除单个文档
     * - 物理删除数据
     * 
     * @param id 文档ID(MongoDB的_id字段值)
     * @param entityClass 实体类类型
     * @return 删除结果对象,包含删除的文档数
     */
    public DeleteResult deleteById(Object id, Class<T> entityClass) {
        Query query = new Query(Criteria.where("_id").is(id));
        return mongoTemplate.remove(query, entityClass);
    }
    
    /**
     * 根据条件删除实体
     * 功能:删除查询条件匹配的所有文档
     * 
     * 执行逻辑:
     * 1. 根据query条件查找所有匹配的文档
     * 2. 删除所有匹配的文档
     * 3. 返回删除结果(包含删除的文档数)
     * 
     * ⚠️ 安全警告:
     * - 如果query为空或条件太宽泛,会删除大量数据
     * - 生产环境中务必仔细检查查询条件
     * 
     * @param query 查询条件对象(用于定位要删除的文档)
     * @param entityClass 实体类类型
     * @return 删除结果对象,包含删除的文档数
     */
    public DeleteResult delete(Query query, Class<T> entityClass) {
        return mongoTemplate.remove(query, entityClass);
    }
    
    /**
     * 删除所有实体
     * 功能:清空整个集合中的所有文档
     * 
     * ⚠️ 危险操作:
     * - 此操作会删除集合中的所有文档
     * - 集合本身不会被删除,但数据会全部清空
     * - 生产环境中谨慎使用,建议先备份数据
     * 
     * 使用场景:
     * - 测试数据清理
     * - 数据重置
     * 
     * @param entityClass 实体类类型
     * @return 删除结果对象,包含删除的文档数
     */
    public DeleteResult deleteAll(Class<T> entityClass) {
        return mongoTemplate.remove(new Query(), entityClass);
    }
    
    /**
     * 检查实体是否存在
     * 功能:检查是否存在匹配查询条件的文档
     * 
     * 执行逻辑:
     * 1. 根据query条件查询
     * 2. 如果至少存在一个匹配文档,返回true
     * 3. 如果没有匹配文档,返回false
     * 
     * 性能优势:
     * - 相比count(),此方法在找到第一个匹配文档后立即返回
     * - 对于判断存在性的场景,性能更好
     * 
     * 使用场景:
     * - 检查用户名是否已存在
     * - 检查某个数据是否存在
     * 
     * @param query 查询条件对象
     * @param entityClass 实体类类型
     * @return true表示存在,false表示不存在
     */
    public boolean exists(Query query, Class<T> entityClass) {
        return mongoTemplate.exists(query, entityClass);
    }
    
    /**
     * 统计文档数量
     * 功能:统计匹配查询条件的文档总数
     * 
     * 执行逻辑:
     * 1. 根据query条件查询
     * 2. 统计所有匹配文档的数量
     * 3. 返回总数(long类型)
     * 
     * 使用场景:
     * - 分页查询时先统计总数
     * - 数据统计报表
     * - 数据质量检查
     * 
     * 性能注意事项:
     * - 在大表上执行count()可能较慢
     * - 建议为查询条件添加索引
     * 
     * @param query 查询条件对象
     * @param entityClass 实体类类型
     * @return 文档数量
     */
    public long count(Query query, Class<T> entityClass) {
        return mongoTemplate.count(query, entityClass);
    }
    
    // ==================== 分页查询 ====================
    
    /**
     * 分页查询
     * 功能:支持排序和分页的综合查询方法
     * 
     * 执行逻辑:
     * 1. 如果指定了排序字段,添加排序条件
     * 2. 先统计符合条件的总记录数
     * 3. 根据页码和每页大小计算skip和limit
     * 4. 执行分页查询
     * 5. 构造Spring Data的Page对象返回
     * 
     * 返回的Page对象包含:
     * - content:当前页的数据列表
     * - totalElements:总记录数
     * - totalPages:总页数
     * - currentPage:当前页码
     * - hasNext/hasPrevious:是否有下一页/上一页
     * 
     * 性能优化建议:
     * - 为排序字段和查询条件字段添加索引
     * - 避免使用过大的skip值(深度分页)
     * 
     * @param query 查询条件对象(可以为空,表示查询所有)
     * @param pageRequest 分页参数对象(包含页码、每页大小、排序信息)
     * @param entityClass 实体类类型
     * @return Spring Data的Page对象,包含数据和分页信息
     */
    public Page<T> findPage(Query query, PageRequest pageRequest, Class<T> entityClass) {
        // 如果指定了排序字段,则添加排序条件
        if (pageRequest.getSortField() != null) {
            // 判断排序方向:desc表示降序,否则为升序
            Sort.Direction direction = "desc".equalsIgnoreCase(pageRequest.getSortOrder()) 
                ? Sort.Direction.DESC : Sort.Direction.ASC;
            // 将排序条件添加到查询对象中
            query.with(Sort.by(direction, pageRequest.getSortField()));
        }
        
        // 先统计总数,用于分页信息
        long total = mongoTemplate.count(query, entityClass);
        
        // 设置分页参数:跳过前N条数据,然后限制返回数量
        // skip: 跳过的记录数 = (页码-1) * 每页大小
        // limit: 每页返回的记录数
        query.skip(pageRequest.getSkip()).limit(pageRequest.getPageSize());
        
        // 执行分页查询
        List<T> list = mongoTemplate.find(query, entityClass);
        
        // 构造Spring Data的Page对象返回
        return new PageImpl<>(list, 
            PageRequest.of(pageRequest.getPageNum() - 1, pageRequest.getPageSize()), 
            total);
    }
    
    // ==================== 高级查询操作 ====================
    
    /**
     * 模糊查询(不区分大小写)
     * 功能:支持SQL LIKE '%keyword%'的模糊匹配,不区分大小写
     * 
     * 实现原理:
     * 1. 使用正则表达式实现模糊匹配
     * 2. Pattern.CASE_INSENSITIVE标志表示不区分大小写
     * 3. MongoDB的$regex操作符支持正则表达式查询
     * 
     * 性能注意事项:
     * - 模糊查询无法使用普通索引,只能全表扫描
     * - 在数据量大的情况下性能较差
     * - 建议配合其他条件缩小查询范围
     * 
     * 使用场景:
     * - 搜索功能(如搜索用户名、商品名)
     * - 数据模糊匹配
     * 
     * @param fieldName 字段名(要搜索的字段)
     * @param keyword 关键词(搜索的关键词)
     * @return 查询条件对象(Criteria)
     */
    public Criteria like(String fieldName, String keyword) {
        // 使用正则表达式实现模糊匹配,CASE_INSENSITIVE表示不区分大小写
        Pattern pattern = Pattern.compile(keyword, Pattern.CASE_INSENSITIVE);
        return Criteria.where(fieldName).regex(pattern);
    }
    
    /**
     * 多字段模糊查询
     * 功能:在多个字段中搜索关键词,任意一个字段匹配即满足条件
     * 
     * 实现原理:
     * 1. 为每个字段构造一个模糊查询条件
     * 2. 使用OR操作符连接多个条件
     * 3. MongoDB的$or操作符实现OR逻辑
     * 
     * SQL等价:
     * WHERE field1 LIKE '%keyword%' OR field2 LIKE '%keyword%' OR ...
     * 
     * 使用场景:
     * - 全局搜索(同时搜索标题、内容、作者等)
     * - 多字段组合搜索
     * 
     * @param fields 字段名数组(要在哪些字段中搜索)
     * @param keyword 关键词(搜索的关键词)
     * @return 查询条件对象(Criteria,使用OR连接)
     */
    public Criteria multiFieldLike(String[] fields, String keyword) {
        // 创建查询条件数组,每个字段一个条件
        Criteria[] criteriaArray = new Criteria[fields.length];
        for (int i = 0; i < fields.length; i++) {
            criteriaArray[i] = like(fields[i], keyword);
        }
        // 使用orOperator将多个条件用OR连接
        return new Criteria().orOperator(criteriaArray);
    }
    
    /**
     * 范围查询
     * 功能:查询字段值在指定范围内的文档(包含边界值)
     * 
     * 实现原理:
     * 1. gte: Greater Than or Equal(大于等于)
     * 2. lte: Less Than or Equal(小于等于)
     * 3. MongoDB的$gte和$lte操作符实现范围查询
     * 
     * SQL等价:
     * WHERE field >= min AND field <= max
     * 
     * 使用场景:
     * - 年龄范围查询(18-30岁)
     * - 价格范围查询(100-500元)
     * - 时间范围查询(某个时间段的数据)
     * 
     * 性能优化:
     * - 为范围查询字段添加索引可以显著提高性能
     * - 复合索引:createIndex({field: 1})
     * 
     * @param fieldName 字段名(要进行范围查询的字段)
     * @param min 最小值(包含)
     * @param max 最大值(包含)
     * @return 查询条件对象(Criteria)
     */
    public Criteria between(String fieldName, Object min, Object max) {
        // gte: 大于等于,lte: 小于等于
        return Criteria.where(fieldName).gte(min).lte(max);
    }
    
    /**
     * 大于查询
     * 功能:查询字段值大于指定值的文档
     * 
     * SQL等价:WHERE field > value
     * 
     * @param fieldName 字段名
     * @param value 比较值
     * @return 查询条件对象(Criteria)
     */
    public Criteria gt(String fieldName, Object value) {
        return Criteria.where(fieldName).gt(value);
    }
    
    /**
     * 小于查询
     * 功能:查询字段值小于指定值的文档
     * 
     * SQL等价:WHERE field < value
     * 
     * @param fieldName 字段名
     * @param value 比较值
     * @return 查询条件对象(Criteria)
     */
    public Criteria lt(String fieldName, Object value) {
        return Criteria.where(fieldName).lt(value);
    }
    
    /**
     * 大于等于查询
     * 功能:查询字段值大于等于指定值的文档
     * 
     * SQL等价:WHERE field >= value
     * 
     * @param fieldName 字段名
     * @param value 比较值
     * @return 查询条件对象(Criteria)
     */
    public Criteria gte(String fieldName, Object value) {
        return Criteria.where(fieldName).gte(value);
    }
    
    /**
     * 小于等于查询
     * 功能:查询字段值小于等于指定值的文档
     * 
     * SQL等价:WHERE field <= value
     * 
     * @param fieldName 字段名
     * @param value 比较值
     * @return 查询条件对象(Criteria)
     */
    public Criteria lte(String fieldName, Object value) {
        return Criteria.where(fieldName).lte(value);
    }
    
    /**
     * 不等于查询
     * 功能:查询字段值不等于指定值的文档
     * 
     * SQL等价:WHERE field <> value 或 WHERE field != value
     * 
     * 使用场景:
     * - 排除某个值
     * - 查询非空值
     * 
     * @param fieldName 字段名
     * @param value 比较值
     * @return 查询条件对象(Criteria)
     */
    public Criteria ne(String fieldName, Object value) {
        return Criteria.where(fieldName).ne(value);
    }
    
    /**
     * IN查询
     * 功能:查询字段值在指定值列表中的文档
     * 
     * SQL等价:
     * WHERE field IN (value1, value2, value3, ...)
     * 
     * 实现原理:
     * - MongoDB的$in操作符匹配数组中的任意一个值
     * - 适用于多选条件查询
     * 
     * 使用场景:
     * - 多状态查询(如查询状态为0或1的用户)
     * - ID列表查询(根据多个ID查询对应的文档)
     * - 标签查询(查询包含某些标签的文档)
     * 
     * 性能优化:
     * - 为IN查询字段添加索引可以显著提高性能
     * - IN列表长度不宜过长(建议不超过100个)
     * 
     * @param fieldName 字段名
     * @param values 值列表(Collection类型,如List、Set等)
     * @return 查询条件对象(Criteria)
     */
    public Criteria in(String fieldName, Collection<?> values) {
        return Criteria.where(fieldName).in(values);
    }
    
    /**
     * NOT IN查询
     * 功能:查询字段值不在指定值列表中的文档
     * 
     * SQL等价:
     * WHERE field NOT IN (value1, value2, value3, ...)
     * 
     * 实现原理:
     * - MongoDB的$nin操作符匹配数组外的所有值
     * - 适用于排除多个值的场景
     * 
     * 使用场景:
     * - 排除多个状态
     * - 排除某些特定值
     * 
     * 性能注意事项:
     * - NOT IN查询的性能通常低于IN查询
     * - 大列表的NOT IN查询可能较慢
     * 
     * @param fieldName 字段名
     * @param values 值列表(Collection类型)
     * @return 查询条件对象(Criteria)
     */
    public Criteria notIn(String fieldName, Collection<?> values) {
        return Criteria.where(fieldName).nin(values);
    }
    
    /**
     * NULL查询
     * 功能:查询字段值为null的文档
     * 
     * SQL等价:WHERE field IS NULL
     * 
     * 使用场景:
     * - 查找未填写的字段
     * - 数据完整性检查
     * 
     * @param fieldName 字段名
     * @return 查询条件对象(Criteria)
     */
    public Criteria isNull(String fieldName) {
        return Criteria.where(fieldName).is(null);
    }
    
    /**
     * NOT NULL查询
     * 功能:查询字段值不为null的文档
     * 
     * SQL等价:WHERE field IS NOT NULL
     * 
     * 使用场景:
     * - 查找已填写的字段
     * - 过滤掉缺失的数据
     * 
     * @param fieldName 字段名
     * @return 查询条件对象(Criteria)
     */
    public Criteria notNull(String fieldName) {
        return Criteria.where(fieldName).ne(null);
    }
    
    /**
     * 数组大小查询
     * 功能:查询数组字段长度等于指定值的文档
     * 
     * 实现原理:
     * - MongoDB的$size操作符用于查询数组长度
     * - 只能匹配精确的数组长度,不能匹配范围
     * 
     * 使用场景:
     * - 查询标签数量(如查询有3个标签的用户)
     * - 查询评论数量(如查询有10条评论的文章)
     * 
     * 性能注意事项:
     * - $size操作符无法使用普通索引
     * - 如果数据量大,考虑添加专门的索引
     * 
     * @param fieldName 字段名(必须是数组类型的字段)
     * @param size 数组大小(精确匹配)
     * @return 查询条件对象(Criteria)
     */
    public Criteria size(String fieldName, int size) {
        return Criteria.where(fieldName).size(size);
    }
    
    /**
     * 数组元素查询
     * 功能:查询数组字段中包含指定元素的文档
     * 
     * 实现原理:
     * - 直接使用is()操作符查询数组字段
     * - 如果数组中包含该元素,则匹配成功
     * 
     * 使用场景:
     * - 查询包含某个标签的文档
     * - 查询包含某个值的数组
     * 
     * 示例:
     * - 查询tags字段包含"java"的文档:arrayContains("tags", "java")
     * 
     * @param fieldName 字段名(数组类型字段)
     * @param value 数组元素值(要在数组中查找的值)
     * @return 查询条件对象(Criteria)
     */
    public Criteria arrayContains(String fieldName, Object value) {
        return Criteria.where(fieldName).is(value);
    }
    
    /**
     * 地理位置查询 - 圆形范围
     * 功能:查询指定经纬度坐标附近指定范围内的文档
     * 
     * 实现原理:
     * 1. 创建GeoJsonPoint对象存储经纬度
     * 2. 使用$nearSphere操作符进行球面距离查询
     * 3. maxDistance参数需要转换为弧度(除以地球半径)
     * 
     * 坐标说明:
     * - 经度:东西方向,范围-180到180
     * - 纬度:南北方向,范围-90到90
     * - 中国大致范围:经度73-135,纬度18-53
     * 
     * 距离计算:
     * - nearSphere使用球面距离计算(考虑地球曲率)
     * - 地球半径:6378137米(赤道半径)
     * - 转换公式:弧度 = 距离(米) / 地球半径(米)
     * 
     * 使用场景:
     * - 附近的人/商家/酒店查询
     * - 地理位置相关的推荐
     * - 距离排序
     * 
     * 性能优化:
     * - 必须为地理位置字段创建2dsphere索引
     * - 建议同时创建复合索引提高查询性能
     * 
     * 示例:
     * - 查询天安门(116.397, 39.918)附近1000米内的地点
     * 
     * @param fieldName 字段名(存储地理坐标的字段,类型通常为GeoJsonPoint)
     * @param longitude 经度(东西方向,-180到180)
     * @param latitude 纬度(南北方向,-90到90)
     * @param maxDistance 最大距离(单位:米)
     * @return 查询条件对象(Criteria)
     */
    public Criteria nearSphere(String fieldName, double longitude, double latitude, double maxDistance) {
        // 创建地理坐标点对象(经度在前,纬度在后)
        Point point = new Point(longitude, latitude);
        
        // nearSphere使用球面距离计算,更精确(考虑地球曲率)
        // maxDistance需要除以地球半径(6378137米)转换为弧度
        // 因为MongoDB使用弧度作为距离单位
        return Criteria.where(fieldName).nearSphere(point).maxDistance(maxDistance / 6378137.0);
    }
    
    /**
     * 正则表达式查询
     * 功能:支持使用正则表达式进行灵活匹配
     * 
     * 实现原理:
     * - MongoDB的$regex操作符支持PCRE正则表达式
     * - 支持复杂的字符串匹配模式
     * 
     * 常用正则表达式示例:
     * - ^abc: 以abc开头
     * - abc$: 以abc结尾
     * - a.c: 中间是任意字符
     * - a*: 0个或多个a
     * - a+: 1个或多个a
     * - a?b: 可选的a后面跟着b
     * 
     * 使用场景:
     * - 复杂的字符串匹配
     * - 邮箱、手机号、身份证号验证
     * - 特定格式的数据查询
     * 
     * 性能注意事项:
     * - 正则查询无法使用普通索引(除前缀匹配外)
     * - 复杂的正则表达式可能导致全表扫描
     * - 建议配合其他条件缩小查询范围
     * 
     * @param fieldName 字段名
     * @param pattern 正则表达式字符串(PCRE格式)
     * @return 查询条件对象(Criteria)
     */
    public Criteria regex(String fieldName, String pattern) {
        return Criteria.where(fieldName).regex(pattern);
    }
    
    // ==================== 高级更新操作 ====================
    
    /**
     * 字段自增
     * 功能:将数值字段的值增加指定数量(支持正数和负数)
     * 
     * 实现原理:
     * - MongoDB的$inc操作符用于数值字段的原子自增/自减
     * - 原子操作:不需要先读再写,直接在数据库端完成
     * 
     * 使用场景:
     * - 计数器(浏览量、点赞数、评论数等)
     * - 库存扣减(减法操作)
     * - 积分增加(加法操作)
     * 
     * 优势:
     * - 原子操作,避免并发问题
     * - 性能优于先读后写
     * - 支持小数运算
     * 
     * 示例:
     * - increment("age", 1): 年龄加1
     * - increment("view_count", 100): 浏览量加100
     * - increment("stock", -5): 库存减5
     * 
     * @param fieldName 字段名(必须是数值类型)
     * @param value 增量值(正数表示增加,负数表示减少)
     * @return 更新操作对象(Update)
     */
    public Update increment(String fieldName, Number value) {
        return new Update().inc(fieldName, value);
    }
    
    /**
     * 字段自乘
     * 功能:将数值字段的值乘以指定倍数
     * 
     * 实现原理:
     * - MongoDB的$mul操作符用于数值字段的乘法运算
     * - 原子操作,避免并发问题
     * 
     * 使用场景:
     * - 价格调整(打折扣)
     * - 权重计算
     * - 比例调整
     * 
     * 示例:
     * - multiply("price", 0.9): 价格打9折
     * - multiply("quantity", 2): 数量翻倍
     * 
     * @param fieldName 字段名(必须是数值类型)
     * @param value 乘数(乘数)
     * @return 更新操作对象(Update)
     */
    public Update multiply(String fieldName, Number value) {
        return new Update().mul(fieldName, value);
    }
    
    /**
     * 数组添加元素
     * 功能:向数组字段添加一个元素(如果数组不存在则创建)
     * 
     * 实现原理:
     * - MongoDB的$push操作符用于向数组添加元素
     * - 如果字段不存在,会自动创建并添加元素
     * - 如果字段存在但不是数组,会报错
     * 
     * 使用场景:
     * - 添加标签
     * - 添加评论
     * - 添加收藏项
     * 
     * 示例:
     * - push("tags", "java"): 向tags数组添加"java"
     * 
     * 注意事项:
     * - 此方法会在数组末尾添加元素
     * - 如果要添加多个元素,使用pushAll()
     * - 如果要避免重复元素,使用$addToSet(Spring Data MongoDB默认)
     * 
     * @param fieldName 字段名(数组类型字段)
     * @param value 要添加的元素值
     * @return 更新操作对象(Update)
     */
    public Update push(String fieldName, Object value) {
        return new Update().push(fieldName, value);
    }
    
    /**
     * 数组批量添加元素
     * 功能:向数组字段一次性添加多个元素
     * 
     * 实现原理:
     * - MongoDB的$pushAll操作符用于批量添加数组元素
     * - 一次操作添加多个元素,提高性能
     * 
     * 使用场景:
     * - 批量添加标签
     * - 批量添加列表项
     * 
     * 性能优势:
     * - 相比循环调用push(),此方法性能更好
     * - 减少网络往返次数
     * 
     * @param fieldName 字段名(数组类型字段)
     * @param values 要添加的元素数组
     * @return 更新操作对象(Update)
     */
    public Update pushAll(String fieldName, Object[] values) {
        return new Update().pushAll(fieldName, values);
    }
    
    /**
     * 数组删除元素
     * 功能:从数组字段中删除指定值的所有匹配项
     * 
     * 实现原理:
     * - MongoDB的$pull操作符用于删除数组中匹配的元素
     * - 如果有多个相同的值,都会被删除
     * - 如果值不存在,不会报错
     * 
     * 使用场景:
     * - 删除标签
     * - 删除评论
     * - 删除列表项
     * 
     * 示例:
     * - pull("tags", "java"): 从tags数组删除所有"java"
     * 
     * @param fieldName 字段名(数组类型字段)
     * @param value 要删除的元素值
     * @return 更新操作对象(Update)
     */
    public Update pull(String fieldName, Object value) {
        return new Update().pull(fieldName, value);
    }
    
    /**
     * 数组删除多个元素
     * 功能:从数组字段中批量删除指定的多个元素
     * 
     * 实现原理:
     * - MongoDB的$pullAll操作符用于批量删除数组元素
     * - 一次操作删除多个元素,提高性能
     * 
     * 使用场景:
     * - 批量删除标签
     * - 批量删除列表项
     * 
     * @param fieldName 字段名(数组类型字段)
     * @param values 要删除的元素数组
     * @return 更新操作对象(Update)
     */
    public Update pullAll(String fieldName, Object[] values) {
        return new Update().pullAll(fieldName, values);
    }
    
    /**
     * 修改数组中指定位置的元素
     * 功能:使用位置运算符更新数组中特定位置的元素
     * 
     * 实现原理:
     * - 使用$set操作符更新指定位置的数组元素
     * - 需要配合查询条件确定数组索引
     * 
     * 使用方式:
     * 1. 使用字段名加索引的方式指定位置,如"tags.0"表示第一个元素
     * 2. 可以配合查询条件使用,如"tags.$.value"(使用$表示匹配的元素)
     * 
     * 使用场景:
     * - 修改数组中特定位置的元素
     * - 配合$elemMatch使用
     * 
     * 示例:
     * - setAtIndex("tags.0", "python"): 修改tags数组的第一个元素为"python"
     * 
     * 注意事项:
     * - 直接指定索引时,需要确保索引在数组范围内
     * - 建议配合查询条件使用更安全
     * 
     * @param fieldName 字段名(包含位置表达式,如"tags.0"或使用$运算符)
     * @param value 新的元素值
     * @return 更新操作对象(Update)
     */
    public Update setAtIndex(String fieldName, Object value) {
        return new Update().set(fieldName, value);
    }
    
    /**
     * 如果字段不存在则设置
     * 功能:仅在插入新文档时设置字段值,更新时不会覆盖已存在的字段
     * 
     * 实现原理:
     * - MongoDB的$setOnInsert操作符
     * - 只在文档不存在(即插入新文档)时设置字段
     * - 如果文档已存在(即更新操作),此字段不会被修改
     * 
     * 使用场景:
     * - 设置创建时间(只在创建时设置,更新时不修改)
     * - 设置默认值(只在首次创建时设置)
     * - 设置初始化标志
     * 
     * 示例:
     * - setOnInsert("create_time", new Date()): 只在插入时设置创建时间
     * - setOnInsert("version", 1): 只在插入时设置初始版本号
     * 
     * 注意事项:
     * - 通常与upsert操作配合使用
     * - 如果不使用upsert,此操作无意义
     * 
     * @param fieldName 字段名
     * @param value 字段值
     * @return 更新操作对象(Update)
     */
    public Update setOnInsert(String fieldName, Object value) {
        return new Update().setOnInsert(fieldName, value);
    }
    
    /**
     * 重命名字段
     * 功能:将文档中的字段重命名
     * 
     * 实现原理:
     * - MongoDB的$rename操作符
     * - 原子操作,不需要先读后写
     * 
     * 使用场景:
     * - 字段名优化(如将old_name改为newName)
     * - 数据结构迁移
     * - 字段名规范化
     * 
     * 示例:
     * - rename("username", "user_name"): 将username字段重命名为user_name
     * 
     * 注意事项:
     * - 如果新字段名已存在,会被覆盖
     * - 如果旧字段名不存在,不会有任何效果
     * - 此操作在大型集合上可能较慢
     * 
     * @param oldName 旧字段名
     * @param newName 新字段名
     * @return 更新操作对象(Update)
     */
    public Update rename(String oldName, String newName) {
        return new Update().rename(oldName, newName);
    }
    
    /**
     * 删除字段
     * 功能:从文档中删除指定字段
     * 
     * 实现原理:
     * - MongoDB的$unset操作符
     * - 原子操作,不需要先读后写
     * 
     * 使用场景:
     * - 数据清理(删除不需要的字段)
     * - 敏感信息删除
     * - 数据结构优化
     * 
     * 示例:
     * - unset("password"): 删除password字段
     * - unset("temporary_field"): 删除临时字段
     * 
     * 注意事项:
     * - 如果字段不存在,不会有任何效果(不会报错)
     * - 删除操作是永久的,无法恢复
     * 
     * @param fieldName 字段名
     * @return 更新操作对象(Update)
     */
    public Update unset(String fieldName) {
        return new Update().unset(fieldName);
    }
    
    /**
     * 当前时间更新
     * 功能:将指定字段更新为当前日期时间
     * 
     * 实现原理:
     * - MongoDB的$currentDate操作符
     * - 使用服务器当前时间
     * - 原子操作,不需要先读后写
     * 
     * 使用场景:
     * - 更新时间戳(最后修改时间)
     * - 记录更新时间
     * - 审计日志
     * 
     * 示例:
     * - currentDate("update_time"): 将update_time字段更新为当前时间
     * - currentDate("last_modified"): 将last_modified字段更新为当前时间
     * 
     * 优势:
     * - 不需要在Java代码中获取时间
     * - 避免客户端和服务器时间不一致的问题
     * - 性能优于先读后写
     * 
     * @param fieldName 字段名(通常是日期或时间戳类型)
     * @return 更新操作对象(Update)
     */
    public Update currentDate(String fieldName) {
        return new Update().currentDate(fieldName);
    }
    
    // ==================== 聚合操作 ====================
    
    /**
     * 执行聚合查询
     * 功能:执行自定义的聚合管道,支持复杂的数据处理和统计
     * 
     * 聚合管道概念:
     * - 聚合管道是MongoDB强大的数据处理功能
     * - 通过多个阶段(stage)依次处理数据
     * - 每个阶段接收上阶段的输出作为输入
     * 
     * 常用聚合阶段:
     * - $match: 过滤文档(类似WHERE)
     * - $group: 分组统计(类似GROUP BY)
     * - $sort: 排序(类似ORDER BY)
     * - $project: 投影(选择字段)
     * - $limit: 限制返回数量
     * - $skip: 跳过指定数量
     * - $lookup: 关联查询(类似JOIN)
     * - $unwind: 拆分数组
     * 
     * 使用场景:
     * - 复杂的数据统计
     * - 数据报表生成
     * - 数据分析
     * - 数据关联查询
     * 
     * 性能优化:
     * - 尽早使用$match过滤数据
     * - 为查询字段添加索引
     * - 合理使用投影减少数据传输量
     * 
     * @param aggregation 聚合操作对象(包含多个聚合阶段)
     * @param entityClass 实体类类型(输入类型)
     * @return 聚合结果对象(包含映射结果)
     */
    public AggregationResults<T> aggregate(Aggregation aggregation, Class<T> entityClass) {
        return mongoTemplate.aggregate(aggregation, entityClass, entityClass);
    }
    
    /**
     * 分组计数
     * 功能:按指定字段分组,统计每组的文档数量
     * 
     * SQL等价:
     * SELECT field, COUNT(*) as count FROM table GROUP BY field ORDER BY count DESC
     * 
     * 聚合管道:
     * 1. $group: 按指定字段分组,使用count()统计数量
     * 2. $sort: 按计数降序排序
     * 
     * 使用场景:
     * - 统计各分类下的文章数量
     * - 统计各状态的用户数量
     * - 统计各个地区的订单数量
     * 
     * 返回结果:
     * - List<Map>,每个Map包含:
     *   - _id: 分组字段的值
     *   - count: 文档数量
     * 
     * @param groupByField 分组字段名(要按哪个字段分组)
     * @param entityClass 实体类类型
     * @return 分组统计结果列表(每项包含字段值和计数)
     */
    public List<Map> groupCount(String groupByField, Class<T> entityClass) {
        // 构建聚合管道:
        // 1. $group: 按指定字段分组,并统计每组的文档数
        // 2. $sort: 按计数降序排序
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group(groupByField).count().as("count"),
            Aggregation.sort(Sort.Direction.DESC, "count")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults();
    }
    
    /**
     * 分组求和
     * 功能:按指定字段分组,计算每组的数值字段总和
     * 
     * SQL等价:
     * SELECT field, SUM(num_field) as total FROM table GROUP BY field ORDER BY total DESC
     * 
     * 聚合管道:
     * 1. $group: 按指定字段分组,使用sum()求和
     * 2. $sort: 按总和降序排序
     * 
     * 使用场景:
     * - 统计各分类的总销售额
     * - 统计各部门的总支出
     * - 统计各用户的总消费
     * 
     * 返回结果:
     * - List<Map>,每个Map包含:
     *   - _id: 分组字段的值
     *   - total: 总和
     * 
     * @param groupByField 分组字段名
     * @param sumField 要求和的数值字段名
     * @param entityClass 实体类类型
     * @return 分组求和结果列表
     */
    public List<Map> groupSum(String groupByField, String sumField, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group(groupByField).sum(sumField).as("total"),
            Aggregation.sort(Sort.Direction.DESC, "total")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults();
    }
    
    /**
     * 分组求平均值
     * 功能:按指定字段分组,计算每组的数值字段平均值
     * 
     * SQL等价:
     * SELECT field, AVG(num_field) as average FROM table GROUP BY field ORDER BY average DESC
     * 
     * 聚合管道:
     * 1. $group: 按指定字段分组,使用avg()求平均
     * 2. $sort: 按平均值降序排序
     * 
     * 使用场景:
     * - 统计各分类的平均价格
     * - 统计各班级的平均分
     * - 统计各员工的平均绩效
     * 
     * 返回结果:
     * - List<Map>,每个Map包含:
     *   - _id: 分组字段的值
     *   - average: 平均值
     * 
     * @param groupByField 分组字段名
     * @param avgField 要求平均的数值字段名
     * @param entityClass 实体类类型
     * @return 分组求平均结果列表
     */
    public List<Map> groupAvg(String groupByField, String avgField, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group(groupByField).avg(avgField).as("average"),
            Aggregation.sort(Sort.Direction.DESC, "average")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults();
    }
    
    /**
     * 聚合分页查询
     * 功能:对聚合查询结果进行分页,支持统计总数和返回分页数据
     * 
     * 实现难点:
     * - 聚合查询的分页需要执行两次聚合
     * - 第一次:统计总数(使用$count)
     * - 第二次:分页查询(使用$skip和$limit)
     * 
     * 执行逻辑:
     * 1. 如果指定了排序,添加排序阶段
     * 2. 第一次聚合:统计总数
     * 3. 第二次聚合:执行分页查询
     * 4. 构造Page对象返回
     * 
     * 性能优化:
     * - 两次聚合可能影响性能
     * - 考虑使用缓存优化
     * - 对于大数据集,考虑使用游标分页
     * 
     * @param aggregation 聚合操作对象
     * @param pageRequest 分页参数对象
     * @param entityClass 实体类类型
     * @return 分页结果对象(Page接口)
     */
    public Page<T> aggregatePage(Aggregation aggregation, PageRequest pageRequest, 
                                Class<T> entityClass) {
        // 如果指定了排序字段,添加排序条件
        if (pageRequest.getSortField() != null) {
            Sort.Direction direction = "desc".equalsIgnoreCase(pageRequest.getSortOrder()) 
                ? Sort.Direction.DESC : Sort.Direction.ASC;
            // 在原有聚合操作基础上追加排序阶段
            aggregation = Aggregation.newAggregation(
                aggregation.getOperations(),
                Aggregation.sort(Sort.by(direction, pageRequest.getSortField()))
            );
        }
        
        // 第一次聚合:统计总数
        // 在聚合管道末尾添加$count阶段
        Aggregation countAggregation = Aggregation.newAggregation(
            aggregation.getOperations(),
            Aggregation.count().as("total")
        );
        AggregationResults<Map> countResults = mongoTemplate.aggregate(
            countAggregation, entityClass, Map.class);
        // 提取总数,如果没有结果则为0
        long total = countResults.getMappedResults().isEmpty() ? 0 : 
            (Long) countResults.getMappedResults().get(0).get("total");
        
        // 第二次聚合:执行分页查询
        // 在聚合管道末尾添加$skip和$limit阶段
        Aggregation pageAggregation = Aggregation.newAggregation(
            aggregation.getOperations(),
            Aggregation.skip(pageRequest.getSkip()),
            Aggregation.limit(pageRequest.getPageSize())
        );
        AggregationResults<T> results = mongoTemplate.aggregate(
            pageAggregation, entityClass, entityClass);
        
        // 构造并返回Page对象
        return new PageImpl<>(results.getMappedResults(), 
            PageRequest.of(pageRequest.getPageNum() - 1, pageRequest.getPageSize()), 
            total);
    }
    
    /**
     * 查找最大值
     * 功能:查询指定字段的最大值
     * 
     * SQL等价:SELECT MAX(field) as maxValue FROM table
     * 
     * 聚合管道:
     * - $group: 不分组(使用空的group()),使用max()求最大值
     * 
     * 使用场景:
     * - 查找最高价格
     * - 查找最大年龄
     * - 查找最新时间
     * 
     * @param fieldName 字段名(必须是数值或日期类型)
     * @param entityClass 实体类类型
     * @return 最大值对象(如果没有数据则返回null)
     */
    public Object max(String fieldName, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group().max(fieldName).as("maxValue")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults().isEmpty() ? null : 
            results.getMappedResults().get(0).get("maxValue");
    }
    
    /**
     * 查找最小值
     * 功能:查询指定字段的最小值
     * 
     * SQL等价:SELECT MIN(field) as minValue FROM table
     * 
     * 聚合管道:
     * - $group: 不分组(使用空的group()),使用min()求最小值
     * 
     * 使用场景:
     * - 查找最低价格
     * - 查找最小年龄
     * - 查找最早时间
     * 
     * @param fieldName 字段名(必须是数值或日期类型)
     * @param entityClass 实体类类型
     * @return 最小值对象(如果没有数据则返回null)
     */
    public Object min(String fieldName, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group().min(fieldName).as("minValue")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults().isEmpty() ? null : 
            results.getMappedResults().get(0).get("minValue");
    }
    
    /**
     * 查找平均值
     * 功能:查询指定字段的平均值
     * 
     * SQL等价:SELECT AVG(field) as avgValue FROM table
     * 
     * 聚合管道:
     * - $group: 不分组(使用空的group()),使用avg()求平均值
     * 
     * 使用场景:
     * - 计算平均价格
     * - 计算平均年龄
     * - 计算平均分
     * 
     * @param fieldName 字段名(必须是数值类型)
     * @param entityClass 实体类类型
     * @return 平均值(Double类型,如果没有数据则返回null)
     */
    public Double avg(String fieldName, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group().avg(fieldName).as("avgValue")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults().isEmpty() ? null : 
            (Double) results.getMappedResults().get(0).get("avgValue");
    }
    
    /**
     * 求和
     * 功能:查询指定字段的总和
     * 
     * SQL等价:SELECT SUM(field) as sumValue FROM table
     * 
     * 聚合管道:
     * - $group: 不分组(使用空的group()),使用sum()求和
     * 
     * 使用场景:
     * - 计算总销售额
     * - 计算总数量
     * - 计算总积分
     * 
     * @param fieldName 字段名(必须是数值类型)
     * @param entityClass 实体类类型
     * @return 总和(Double类型,如果没有数据则返回null)
     */
    public Double sum(String fieldName, Class<T> entityClass) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.group().sum(fieldName).as("sumValue")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(
            aggregation, entityClass, Map.class);
        return results.getMappedResults().isEmpty() ? null : 
            ((Number) results.getMappedResults().get(0).get("sumValue")).doubleValue();
    }
    
    // ==================== 事务处理 ====================
    
    /**
     * 执行事务操作(无返回值)
     * 功能:在事务中执行操作,发生异常时自动回滚
     * 
     * MongoDB事务特性:
     * - 需要MongoDB 4.0+版本
     * - 需要副本集环境(Replica Set)
     * - 支持ACID特性(原子性、一致性、隔离性、持久性)
     * - 同一事务内只能操作同一个数据库
     * 
     * 使用场景:
     * - 转账操作(涉及两个账户)
     * - 订单处理(订单、库存、日志同时更新)
     * - 多步操作需要保证数据一致性
     * 
     * 性能注意事项:
     * - 事务操作会降低性能
     * - 事务执行时间不宜过长(建议不超过60秒)
     * - 避免在事务中执行大量查询
     * 
     * 示例:
     * ```java
     * mongoUtils.executeInTransaction(() -> {
     *     // 扣减余额
     *     updateBalance(fromId, -100);
     *     // 增加余额
     *     updateBalance(toId, 100);
     * });
     * ```
     * 
     * @param action 要执行的操作(Runnable接口,无返回值)
     * @return 操作是否成功(成功返回true,抛出异常返回false)
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean executeInTransaction(Runnable action) {
        try {
            // 执行事务内的操作
            action.run();
            // 如果没有异常,返回true表示成功
            return true;
        } catch (Exception e) {
            // 记录错误日志
            log.error("事务执行失败", e);
            // 重新抛出异常以触发事务回滚
            throw e;
        }
    }
    
    /**
     * 执行事务操作并返回结果
     * 功能:在事务中执行有返回值的操作,发生异常时自动回滚
     * 
     * 与executeInTransaction()的区别:
     * - 此方法可以返回操作结果
     * - 使用Supplier接口(有返回值)
     * - 适用于需要获取操作结果的场景
     * 
     * 使用场景:
     * - 创建对象并返回ID
     * - 执行计算并返回结果
     * - 任何需要返回值的事务操作
     * 
     * 示例:
     * ```java
     * User user = mongoUtils.executeInTransactionWithResult(() -> {
     *     // 创建用户
     *     return saveUser(user);
     *     // 创建关联数据
     *     saveProfile(profile);
     * });
     * ```
     * 
     * @param action 要执行的操作(Supplier接口,有返回值)
     * @param <R> 返回值类型
     * @return 操作结果
     * @throws RuntimeException 如果事务执行失败,抛出异常触发回滚
     */
    @Transactional(rollbackFor = Exception.class)
    public <R> R executeInTransactionWithResult(java.util.function.Supplier<R> action) {
        try {
            // 执行事务内的操作并返回结果
            return action.get();
        } catch (Exception e) {
            // 记录错误日志
            log.error("事务执行失败", e);
            // 重新抛出异常以触发事务回滚
            throw e;
        }
    }
    
    // ==================== 索引操作 ====================
    
    /**
     * 创建索引
     * 功能:根据实体类上的@Indexed注解创建索引
     * 
     * 索引类型:
     * - 单字段索引:createIndex({field: 1})
     * - 复合索引:createIndex({field1: 1, field2: -1})
     * - 唯一索引:@Indexed(unique = true)
     * - 过期索引:@Indexed(expireAfterSeconds = 3600)
     * - 地理索引:@GeoSpatialIndexed
     * 
     * 性能影响:
     * - 索引会提高查询性能
     * - 索引会降低插入、更新、删除性能
     * - 索引占用磁盘空间
     * 
     * 最佳实践:
     * - 为常用查询字段创建索引
     * - 避免过度索引
     * - 定期分析慢查询,优化索引
     * 
     * @param entityClass 实体类类型(包含@Indexed注解)
     */
    public void createIndex(Class<T> entityClass) {
        mongoTemplate.indexOps(entityClass).ensureIndexes();
    }
    
    /**
     * 删除索引
     * 功能:删除集合的所有索引(保留_id索引)
     * 
     * 注意事项:
     * - _id索引无法被删除(MongoDB默认索引)
     * - 删除索引后,相关查询性能会下降
     * - 生产环境中谨慎使用
     * 
     * 使用场景:
     * - 重建索引(删除后重新创建)
     * - 数据迁移
     * - 性能测试
     * 
     * @param entityClass 实体类类型
     */
    public void dropIndex(Class<T> entityClass) {
        mongoTemplate.indexOps(entityClass).dropAllIndexes();
    }
    
    /**
     * 获取所有索引
     * 功能:查询集合的所有索引信息
     * 
     * 返回信息:
     * - 索引名称
     * - 索引字段
     * - 索引类型(单字段、复合、唯一等)
     * - 索引大小
     * 
     * 使用场景:
     * - 检查索引创建情况
     * - 性能分析
     * - 索引优化
     * 
     * @param entityClass 实体类类型
     * @return 索引信息列表(IndexInfo对象列表)
     */
    public List<IndexInfo> getIndexes(Class<T> entityClass) {
        return mongoTemplate.indexOps(entityClass).getIndexInfo();
    }
    
    // ==================== 批量操作 ====================
    
    /**
     * 批量插入
     * 功能:批量插入多个文档
     * 
     * 性能优势:
     * - 减少网络往返次数
     * - MongoDB内部优化批量写入
     * 
     * 使用场景:
     * - 数据初始化
     * - 数据导入
     * - 批量新增
     * 
     * @param entities 实体集合
     * @param entityClass 实体类类型
     * @return 插入的实体集合
     */
    public List<T> bulkInsert(Collection<T> entities, Class<T> entityClass) {
        return (List<T>) mongoTemplate.insertAll(entities);
    }
    
    /**
     * 批量更新
     * 功能:执行多个更新操作,一次性提交,提高性能
     * 
     * 实现原理:
     * - 使用BulkOperations进行批量操作
     * - UNORDERED模式:无序执行,效率更高
     * - 一次性提交所有更新操作
     * 
     * 性能优势:
     * - 减少网络往返次数
     * - MongoDB内部优化批量执行
     * - 显著提高大量更新的性能
     * 
     * 使用场景:
     * - 批量修改数据
     * - 数据修复
     * - 批量更新状态
     * 
     * 示例:
     * ```java
     * List<BulkUpdateOperation> updates = new ArrayList<>();
     * updates.add(new BulkUpdateOperation(query1, update1));
     * updates.add(new BulkUpdateOperation(query2, update2));
     * BulkOperationsResult result = bulkUpdate(updates, User.class);
     * ```
     * 
     * @param updates 更新操作列表(每个元素包含查询条件和更新内容)
     * @param entityClass 实体类类型
     * @return 批量操作结果(包含插入、修改、删除的文档数)
     */
    public BulkOperationsResult bulkUpdate(List<BulkUpdateOperation> updates, Class<T> entityClass) {
        // 创建批量操作对象,使用UNORDERED模式(无序执行,效率更高)
        // UNORDERED: 并发执行,速度快但错误处理复杂
        // ORDERED: 顺序执行,遇到错误停止,速度较慢
        BulkOperations bulkOps = mongoTemplate.bulkOps(
            BulkOperations.BulkMode.UNORDERED, entityClass);
        
        // 将每个更新操作添加到批量操作中
        for (BulkUpdateOperation update : updates) {
            bulkOps.updateOne(update.getQuery(), update.getUpdate());
        }
        
        // 执行批量操作并获取结果
        com.mongodb.client.result.BulkWriteResult result = bulkOps.execute();
        // 封装并返回批量操作结果
        return new BulkOperationsResult(
            result.getInsertedCount(),
            result.getModifiedCount(),
            result.getDeletedCount()
        );
    }
    
    /**
     * 批量操作结果封装类
     * 功能:封装批量操作的执行结果,方便调用方获取操作统计信息
     * 
     * 包含信息:
     * - insertedCount: 插入的文档数
     * - modifiedCount: 修改的文档数
     * - deletedCount: 删除的文档数
     * 
     * 使用场景:
     * - 统计批量操作的影响范围
     * - 日志记录
     * - 操作验证
     */
    @Data
    public static class BulkOperationsResult {
        // 插入的文档数
        private long insertedCount;
        // 修改的文档数
        private long modifiedCount;
        // 删除的文档数
        private long deletedCount;
        
        /**
         * 构造函数
         * @param insertedCount 插入的文档数
         * @param modifiedCount 修改的文档数
         * @param deletedCount 删除的文档数
         */
        public BulkOperationsResult(long insertedCount, long modifiedCount, long deletedCount) {
            this.insertedCount = insertedCount;
            this.modifiedCount = modifiedCount;
            this.deletedCount = deletedCount;
        }
    }
    
    /**
     * 批量更新操作封装类
     * 功能:封装单个批量更新操作的查询条件和更新内容
     * 
     * 包含信息:
     * - query: 查询条件(用于匹配要更新的文档)
     * - update: 更新操作(包含要更新的字段和新值)
     * 
     * 使用场景:
     * - 构造批量更新操作列表
     * - 传递给bulkUpdate()方法
     * 
     * 示例:
     * ```java
     * Query query = new Query(Criteria.where("status").is(0));
     * Update update = new Update().set("status", 1);
     * BulkUpdateOperation operation = new BulkUpdateOperation(query, update);
     * ```
     */
    @Data
    public static class BulkUpdateOperation {
        // 查询条件(用于匹配要更新的文档)
        private Query query;
        // 更新操作(包含要更新的字段和新值)
        private Update update;
        
        /**
         * 构造函数
         * @param query 查询条件
         * @param update 更新操作
         */
        public BulkUpdateOperation(Query query, Update update) {
            this.query = query;
            this.update = update;
        }
    }
}

基础CRUD操作

实体类示例

package com.example.mongodb.entity;

import lombok.Data;
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.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 用户实体类
 * 
 * 注解说明:
 * - @Document: 指定对应的MongoDB集合名称
 * - @Id: 指定主键字段
 * - @Indexed: 为字段创建索引
 * - @Field: 指定字段在MongoDB中的名称
 */
@Data
@Document(collection = "user")
public class User implements Serializable {
    
    // 主键,MongoDB自动生成ObjectId
    @Id
    private String id;
    
    // 用户名,创建唯一索引
    @Indexed(unique = true)
    @Field("username")
    private String username;
    
    // 密码
    @Field("password")
    private String password;
    
    // 邮箱
    @Field("email")
    private String email;
    
    // 手机号
    @Field("phone")
    private String phone;
    
    // 年龄
    @Field("age")
    private Integer age;
    
    // 性别(0-未知,1-男,2-女)
    @Field("gender")
    private Integer gender;
    
    // 状态(0-禁用,1-启用)
    @Field("status")
    private Integer status;
    
    // 标签数组
    @Field("tags")
    private List<String> tags;
    
    // 创建时间
    @Field("create_time")
    private Date createTime;
    
    // 更新时间
    @Field("update_time")
    private Date updateTime;
}

Service层示例

package com.example.mongodb.service;

import com.example.mongodb.entity.User;
import com.example.mongodb.utils.MongoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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.Service;

import java.util.Date;
import java.util.List;

/**
 * 用户服务类 - 演示如何使用MongoUtils工具类
 */
@Service
public class UserService {
    
    @Autowired
    private MongoUtils<User> mongoUtils;
    
    /**
     * 保存用户
     * 功能:创建新用户,设置创建时间和更新时间
     */
    public User saveUser(User user) {
        // 设置创建时间和更新时间
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        // 调用工具类保存
        return mongoUtils.save(user);
    }
    
    /**
     * 批量保存用户
     * 功能:一次性保存多个用户
     */
    public List<User> saveUsers(List<User> users) {
        // 为每个用户设置时间
        Date now = new Date();
        users.forEach(user -> {
            user.setCreateTime(now);
            user.setUpdateTime(now);
        });
        // 批量保存
        return mongoUtils.saveBatch(users);
    }
    
    /**
     * 根据ID查询用户
     */
    public User getUserById(String id) {
        return mongoUtils.findById(id, User.class);
    }
    
    /**
     * 查询所有用户
     * 注意:如果数据量大,建议使用分页查询
     */
    public List<User> getAllUsers() {
        return mongoUtils.findAll(User.class);
    }
    
    /**
     * 根据用户名查询用户
     * 功能:通过用户名精确查询
     */
    public User getUserByUsername(String username) {
        Query query = new Query(Criteria.where("username").is(username));
        return mongoUtils.findOne(query, User.class);
    }
    
    /**
     * 根据用户名和密码查询用户
     * 功能:用户登录验证
     */
    public User getUserByUsernameAndPassword(String username, String password) {
        Query query = new Query(Criteria.where("username").is(username)
            .and("password").is(password));
        return mongoUtils.findOne(query, User.class);
    }
    
    /**
     * 根据ID更新用户
     * 功能:只更新传入的字段,其他字段保持不变
     */
    public long updateUser(String id, User user) {
        // 创建更新对象
        Update update = new Update();
        
        // 只更新非空字段
        if (user.getUsername() != null) {
            update.set("username", user.getUsername());
        }
        if (user.getPassword() != null) {
            update.set("password", user.getPassword());
        }
        if (user.getEmail() != null) {
            update.set("email", user.getEmail());
        }
        if (user.getPhone() != null) {
            update.set("phone", user.getPhone());
        }
        if (user.getAge() != null) {
            update.set("age", user.getAge());
        }
        if (user.getGender() != null) {
            update.set("gender", user.getGender());
        }
        if (user.getStatus() != null) {
            update.set("status", user.getStatus());
        }
        
        // 更新时间
        update.set("update_time", new Date());
        
        // 执行更新并返回修改的文档数
        return mongoUtils.updateById(id, update, User.class).getModifiedCount();
    }
    
    /**
     * 根据ID删除用户
     */
    public long deleteUser(String id) {
        return mongoUtils.deleteById(id, User.class).getDeletedCount();
    }
    
    /**
     * 检查用户名是否存在
     */
    public boolean existsUsername(String username) {
        Query query = new Query(Criteria.where("username").is(username));
        return mongoUtils.exists(query, User.class);
    }
    
    /**
     * 统计用户数量
     */
    public long countUsers() {
        return mongoUtils.count(new Query(), User.class);
    }
}

高级查询功能

复杂查询示例

package com.example.mongodb.service;

import com.example.mongodb.common.PageRequest;
import com.example.mongodb.entity.User;
import com.example.mongodb.utils.MongoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 高级查询服务类 - 演示各种高级查询功能
 */
@Service
public class UserAdvancedQueryService {
    
    @Autowired
    private MongoUtils<User> mongoUtils;
    
    /**
     * 模糊查询用户
     * 功能:根据用户名模糊查询,不区分大小写
     */
    public List<User> searchUsers(String keyword) {
        Query query = new Query(mongoUtils.like("username", keyword));
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 多字段模糊查询
     * 功能:同时在用户名、邮箱、手机号中搜索关键词
     */
    public List<User> multiFieldSearch(String keyword) {
        String[] fields = {"username", "email", "phone"};
        Query query = new Query(mongoUtils.multiFieldLike(fields, keyword));
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 年龄范围查询
     * 功能:查询年龄在指定范围内的用户
     */
    public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
        Query query = new Query(mongoUtils.between("age", minAge, maxAge));
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 状态和年龄组合查询
     * 功能:查询指定状态且年龄大于某个值的用户
     */
    public List<User> getUsersByStatusAndAge(Integer status, Integer age) {
        Query query = new Query(
            Criteria.where("status").is(status)
            .and("age").gt(age)
        );
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * IN查询
     * 功能:查询状态在指定列表中的用户
     */
    public List<User> getUsersByStatusList(List<Integer> statusList) {
        Query query = new Query(mongoUtils.in("status", statusList));
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 标签包含查询
     * 功能:查询包含指定标签的用户
     */
    public List<User> getUsersByTag(String tag) {
        Query query = new Query(mongoUtils.arrayContains("tags", tag));
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 复合条件查询
     * 功能:使用AND连接多个条件
     */
    public List<User> getUsersByComplexCondition(String keyword, Integer minAge, List<Integer> statusList) {
        Query query = new Query(
            new Criteria().andOperator(
                mongoUtils.like("username", keyword),
                mongoUtils.gte("age", minAge),
                mongoUtils.in("status", statusList)
            )
        );
        return mongoUtils.find(query, User.class);
    }
    
    /**
     * 分页查询
     * 功能:查询所有用户并分页
     */
    public Page<User> getUsersByPage(PageRequest pageRequest) {
        Query query = new Query();
        return mongoUtils.findPage(query, pageRequest, User.class);
    }
    
    /**
     * 条件分页查询
     * 功能:查询指定状态的用户并分页
     */
    public Page<User> getUsersByPageWithCondition(Integer status, PageRequest pageRequest) {
        Query query = new Query(Criteria.where("status").is(status));
        return mongoUtils.findPage(query, pageRequest, User.class);
    }
}

高级更新操作示例

package com.example.mongodb.service;

import com.example.mongodb.entity.User;
import com.example.mongodb.utils.MongoUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;

import java.util.List;

/**
 * 高级更新服务类 - 演示各种高级更新功能
 */
@Service
public class UserAdvancedUpdateService {
    
    @Autowired
    private MongoUtils<User> mongoUtils;
    
    /**
     * 用户年龄自增
     * 功能:将指定用户的年龄加1
     */
    public long incrementAge(String userId) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.increment("age", 1);
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 批量添加标签
     * 功能:向用户的标签数组添加多个标签
     */
    public long addTags(String userId, List<String> newTags) {
        Query query = new Query(Criteria.where("_id").is(userId));
        for (String tag : newTags) {
            Update update = mongoUtils.push("tags", tag);
            mongoUtils.updateFirst(query, update, User.class);
        }
        return newTags.size();
    }
    
    /**
     * 批量删除标签
     * 功能:从用户的标签数组删除多个标签
     */
    public long removeTags(String userId, List<String> tagsToRemove) {
        Query query = new Query(Criteria.where("_id").is(userId));
        for (String tag : tagsToRemove) {
            Update update = mongoUtils.pull("tags", tag);
            mongoUtils.updateFirst(query, update, User.class);
        }
        return tagsToRemove.size();
    }
    
    /**
     * 批量添加标签(一次性)
     * 功能:使用pushAll一次性添加多个标签,性能更好
     */
    public long addTagsBatch(String userId, String[] newTags) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.pushAll("tags", newTags);
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 更新用户状态(存在则更新,不存在则设置默认值)
     * 功能:演示setOnInsert的使用
     */
    public long updateStatusWithDefault(String userId, Integer status) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.setOnInsert("status", 1);
        update.set("status", status);
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 重命名字段
     * 功能:将字段名从oldName改为newName
     */
    public long renameField(String userId, String oldName, String newName) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.rename(oldName, newName);
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 删除字段
     * 功能:从文档中删除指定字段
     */
    public long removeField(String userId, String fieldName) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.unset(fieldName);
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 更新时间戳
     * 功能:将update_time字段更新为当前时间
     */
    public long updateTimestamp(String userId) {
        Query query = new Query(Criteria.where("_id").is(userId));
        Update update = mongoUtils.currentDate("update_time");
        return mongoUtils.updateFirst(query, update, User.class).getModifiedCount();
    }
    
    /**
     * 批量更新操作
     * 功能:批量更新多个用户的状态
     */
    public long bulkUpdateUsers(List<String> userIds, Integer newStatus) {
        List<MongoUtils.BulkUpdateOperation> updates = new java.util.ArrayList<>();
        
        for (String userId : userIds) {
            Query query = new Query(Criteria.where("_id").is(userId));
            Update update = new Update().set("status", newStatus);
            updates.add(new MongoUtils.BulkUpdateOperation(query, update));
        }
        
        MongoUtils.BulkOperationsResult result = mongoUtils.bulkUpdate(updates, User.class);
        return result.getModifiedCount();
    }
}

聚合操作示例

package com.example.mongodb.service;

import com.example.mongodb.entity.User;
import com.example.mongodb.utils.MongoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

/**
 * 聚合查询服务类 - 演示各种聚合操作
 */
@Service
public class UserAggregationService {
    
    @Autowired
    private MongoUtils<User> mongoUtils;
    
    /**
     * 按性别分组统计用户数量
     */
    public List<Map> groupCountByGender() {
        return mongoUtils.groupCount("gender", User.class);
    }
    
    /**
     * 按状态分组统计用户数量
     */
    public List<Map> groupCountByStatus() {
        return mongoUtils.groupCount("status", User.class);
    }
    
    /**
     * 按年龄段分组统计
     * 功能:自定义聚合管道,按10岁一个年龄段分组
     */
    public List<Map> groupCountByAgeRange() {
        Aggregation aggregation = Aggregation.newAggregation(
            // 使用表达式将年龄分段:0-9, 10-19, 20-29...
            Aggregation.project()
                .andExpression("floor(age / 10) * 10").as("ageRange"),
            // 按年龄段分组统计
            Aggregation.group("ageRange")
                .count().as("count"),
            // 按年龄段升序排序
            Aggregation.sort(org.springframework.data.domain.Sort.Direction.ASC, "_id")
        );
        
        AggregationResults<Map> results = mongoUtils.aggregate(aggregation, User.class, Map.class);
        return results.getMappedResults();
    }
    
    /**
     * 统计各状态用户的平均年龄
     */
    public List<Map> groupAvgAgeByStatus() {
        return mongoUtils.groupAvg("status", "age", User.class);
    }
    
    /**
     * 查找最大年龄
     */
    public Object getMaxAge() {
        return mongoUtils.max("age", User.class);
    }
    
    /**
     * 查找最小年龄
     */
    public Object getMinAge() {
        return mongoUtils.min("age", User.class);
    }
    
    /**
     * 计算平均年龄
     */
    public Double getAvgAge() {
        return mongoUtils.avg("age", User.class);
    }
    
    /**
     * 统计总年龄
     */
    public Double getSumAge() {
        return mongoUtils.sum("age", User.class);
    }
    
    /**
     * 复杂聚合查询:按状态分组,统计数量、平均年龄、最大年龄
     */
    public List<Map> complexGroupByStatus() {
        Aggregation aggregation = Aggregation.newAggregation(
            // 按状态分组
            Aggregation.group("status")
                // 统计用户数
                .count().as("userCount")
                // 统计平均年龄
                .avg("age").as("avgAge")
                // 统计最大年龄
                .max("age").as("maxAge")
                // 统计最小年龄
                .min("age").as("minAge"),
            // 按用户数降序排序
            Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "userCount")
        );
        
        AggregationResults<Map> results = mongoUtils.aggregate(aggregation, User.class, Map.class);
        return results.getMappedResults();
    }
}

最佳实践

1. 索引优化

为常用查询字段创建索引

@Indexed
private String username;

创建复合索引优化多字段查询

@CompoundIndex(name = "idx_status_create_time", def = "{'status': 1, 'createTime': -1}")

避免过度索引

2. 查询优化

只查询需要的字段

Query query = new Query();
query.fields().include("username").include("email");

避免深度分页

使用索引覆盖查询

// 查询条件和排序都使用索引字段
query.addCriteria(Criteria.where("status").is(1));
query.with(Sort.by(Sort.Direction.DESC, "createTime"));

3. 批量操作

大量数据插入使用批量插入

mongoUtils.saveBatch(users);

多文档更新使用BulkOperations

mongoUtils.bulkUpdate(updates, User.class);

总结

本文详细介绍了SpringBoot整合MongoDB的完整工具类实现,涵盖了基础CRUD、高级查询、聚合操作、事务处理等功能。通过合理封装MongoDB操作,可以显著提高开发效率和代码质量。

以上就是SpringBoot整合MongoDB的完整操作指南的详细内容,更多关于SpringBoot整合MongoDB的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文