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-10 | 20-50 | 根据平均并发量设置 |
| max-connections-per-host | 最大连接数 | 20-50 | 100-200 | 根据峰值并发量设置 |
| threads-allowed-to-block-for-connection-multiplier | 阻塞倍数 | 5 | 5 | 一般不需要调整 |
| connection-timeout | 连接超时(ms) | 10000 | 10000 | 网络不稳定时可增加 |
| socket-timeout | Socket超时(ms) | 30000-60000 | 60000-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 # 确保指定正确的认证数据库
安全建议
密码安全
- 不要在配置文件中明文存储密码
- 使用环境变量:
${MONGODB_PASSWORD} - 使用加密配置中心
网络安全
- 生产环境不要使用localhost
- 使用内网IP或VPN
- 配置MongoDB的防火墙规则
权限控制
- 为不同环境创建不同的数据库用户
- 遵循最小权限原则
- 定期更换密码
核心工具类实现
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");
避免深度分页
- skip()在数据量大时性能较差
- 考虑使用基于游标的分页
使用索引覆盖查询
// 查询条件和排序都使用索引字段
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的资料请关注脚本之家其它相关文章!
