Spring Data MongoDB的核心用法 附示例代码
在使用 Spring Data MongoDB 操作 MongoDB 时,掌握实体映射规则与常用 CRUD 方法是开发高效应用的基础。本文将系统梳理 Spring Data MongoDB 的核心用法,涵盖实体类注解、增删改查、条件查询、分页排序等常见场景,并提供可直接复用的代码示例。
一、实体类与集合映射规则
- 集合命名规则
- 若实体类未使用
@Document注解,则默认生成的集合名称为首字母小写的驼峰格式,且区分大小写。 - 例如:实体类名为
UserDao→ 对应集合名为userDao。 - 使用
@Document(collection = "users")可显式指定集合名。
- 若实体类未使用
- 主键标识
- 使用
@Id标注主键字段。 - 若字段名为
id或_id,可省略@Id注解(Spring Data 会自动识别)。
- 使用
- 字段映射
- 当 Java 字段名与 MongoDB 中的字段名不一致时,使用
@Field("db_field_name")进行映射。
- 当 Java 字段名与 MongoDB 中的字段名不一致时,使用
User user = new User("张三", 25);
User saved = mongoTemplate.insert(user); // 返回带主键的对象⚠️ 注意:使用
mongoTemplate.insert()插入数据时,MongoDB 会自动添加_class字段,存储该文档对应的 Java 类全限定名(如com.example.User),用于反序列化时类型还原。
二、新增操作
User user = new User("张三", 25);
User saved = mongoTemplate.insert(user); // 返回带主键的对象- 若集合不存在,会自动创建。
- 自动注入
_class字段。
三、修改操作
3.1 全量替换(慎用)
mongoTemplate.save(user);
⚠️ 此方法会完全覆盖原文档!即使只设置了部分字段,其他字段也会被置为
null。
3.2 局部更新(推荐)
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().set("name", "李四");
// 更新第一个匹配项
mongoTemplate.updateFirst(query, update, "userDao");
// 更新所有匹配项
mongoTemplate.updateMulti(query, update, "userDao");Update.set()对应 MongoDB 的$set操作符。- 第三个参数为集合名(非实体类名)。
四、删除操作
// 1. 根据主键删除(只需设置 id 字段)
User user = new User();
user.setId("60d...");
mongoTemplate.remove(user);
// 2. 根据条件删除
Query query = Query.query(Criteria.where("age").lt(18));
mongoTemplate.remove(query, "userDao");五、查询操作
5.1 基础查询
// 查询全部
List<User> all = mongoTemplate.findAll(User.class);
// 根据 ID 查询
User user = mongoTemplate.findById("60d...", User.class);
// 查询第一条匹配结果
User first = mongoTemplate.findOne(query, User.class);
// 查询所有匹配结果
List<User> list = mongoTemplate.find(query, User.class);5.2 条件查询(Criteria)
判断字段是否存在
// 等价于 db.user.find({name: {$exists: false}})
Query query = Query.query(Criteria.where("name").exists(false));数值比较
| 含义 | MongoDB 操作符 | Spring Data 写法 |
|---|---|---|
| 大于 | $gt | Criteria.where("age").gt(18) |
| 大于等于 | $gte | Criteria.where("score").gte(90) |
| 小于 | $lt | Criteria.where("price").lt(100) |
| 小于等于 | $lte | Criteria.where("quantity").lte(5) |
组合示例:
Query query = new Query(Criteria.where("age").gt(20).lt(30));
List<User> users = mongoTemplate.find(query, User.class);模糊查询(正则匹配)
// 包含匹配(任意位置)→ SQL: LIKE '%name%'
Query query = Query.query(Criteria.where("name").regex(Pattern.quote(name), "i"));
// 前缀匹配 → SQL: LIKE 'name%'
Query query = Query.query(Criteria.where("name").regex("^" + Pattern.quote(name), "i"));
// 后缀匹配 → SQL: LIKE '%name'
Query query = Query.query(Criteria.where("name").regex(Pattern.quote(name) + "$", "i"));✅ 强烈建议使用
Pattern.quote()转义用户输入,防止正则注入!
去重查询
Query query = Query.query(Criteria.where("name").regex("张"));
List<String> distinctNames = mongoTemplate.findDistinct(query, "name", User.class, String.class);逻辑组合查询
- AND 查询(姓名=张三 且 年龄>20):
Criteria criteria = Criteria.where("name").is("张三").and("age").gt(20);
Query query = Query.query(criteria);- OR 查询(姓名=张三 或 年龄>20):
Criteria criteria = new Criteria().orOperator(
Criteria.where("name").is("张三"),
Criteria.where("age").gt(20)
);
Query query = Query.query(criteria);- 混合 AND/OR((张三 & 18岁) OR (李四 & 20岁))
Criteria and1 = Criteria.where("name").is("张三").and("age").is(18);
Criteria and2 = Criteria.where("name").is("李四").and("age").is(20);
Criteria or = new Criteria().orOperator(and1, and2);
List<User> result = mongoTemplate.find(Query.query(or), User.class);5.3 排序
Query query = new Query(Criteria.where("age").gte(2));
query.with(Sort.by(Sort.Direction.DESC, "age")); // 按 age 降序
List<User> list = mongoTemplate.find(query, User.class);5.4 分页
// 第0页(第一页),每页2条 Pageable pageable = PageRequest.of(0, 2); Query query = new Query().with(pageable); List<User> pageData = mongoTemplate.find(query, User.class); // 分页 + 排序 Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "name"));
5.5 统计总数
TypedAggregation<User> agg = Aggregation.newAggregation(
User.class,
Aggregation.group().count().as("count")
);
AggregationResults<Map> result = mongoTemplate.aggregate(agg, Map.class);
Long total = (Long) result.getUniqueMappedResult().get("count");5.6 带条件的分组统计
TypedAggregation<User> agg = Aggregation.newAggregation(
User.class,
Aggregation.match(Criteria.where("name").is("张三")), // 先过滤
Aggregation.group("name").count().as("count") // 再分组
);
List<Map> results = mongoTemplate.aggregate(agg, Map.class).getMappedResults();
for (Map map : results) {
System.out.println("姓名: " + map.get("_id") + ", 数量: " + map.get("count"));
}🔔
Aggregation.match()放在group前表示“先过滤后分组”,放在后面则是“先分组后过滤”。
六、常见问题与解决方案(FAQ)
在实际使用 Spring Data MongoDB 过程中,开发者常会遇到一些“坑”。以下是几个高频问题及其解决方法:
1.插入数据后集合中多出_class字段
问题现象:
使用 mongoTemplate.insert() 插入文档后,MongoDB 中自动多了一个 _class 字段,值为 Java 类的全限定名(如 com.example.User)。
原因:
Spring Data MongoDB 默认启用类型映射(Type Mapping),用于反序列化时确定目标 Java 类型。
解决方案:
- 方案一(推荐):若不需要类型信息,可在实体类上添加
@Document并禁用_class:
@Document(collection = "users")
@TypeAlias("user") // 可选:用简短别名替代全类名
public class User { ... }- 方案二(全局禁用):自定义
MongoCustomConversions,移除_class写入(适用于所有实体):
@Configuration
public class MongoConfig {
@Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.emptyList());
}
}⚠️ 注意:禁用后,若集合中存在多种类型文档,反序列化可能失败。
2.save()方法导致字段被置为 null
问题现象:
调用 mongoTemplate.save(user) 更新对象时,未设置的字段在数据库中被清空为 null。
原因:save() 是全量替换操作,不是局部更新。它会用传入对象的当前状态完全覆盖原文档。
解决方案:
改用 updateFirst() 或 updateMulti() 做局部更新:
Query query = Query.query(Criteria.where("_id").is(userId));
Update update = new Update().set("name", "新名字").set("age", 30);
mongoTemplate.updateFirst(query, update, "users");3.模糊查询因特殊字符报错或结果异常
问题现象:
用户输入如 张*三、李.四 等包含正则元字符的内容进行模糊查询时,抛出异常或匹配不到预期结果。
原因:regex() 直接将字符串当作正则表达式处理,而 *、.、? 等是正则特殊字符。
解决方案:
使用 Pattern.quote() 对输入进行转义:
String keyword = "张*三";
String escapedKeyword = Pattern.quote(keyword); // 转义为字面量
Query query = Query.query(Criteria.where("name").regex(escapedKeyword, "i"));✅ 同时建议加上
"i"标志实现不区分大小写匹配。
4.分页查询总数不准或性能差
问题现象:
分页时需要同时获取总条数,但每次都要执行一次 count 查询,大数据量下响应慢。
原因:PageRequest 本身不包含总数,需额外调用 mongoTemplate.count(query, clazz)。
解决方案:
- 小数据量:直接 count。
- 大数据量/高并发:
- 使用缓存(如 Redis 缓存总数,定时刷新)。
- 改用“游标分页”(基于
_id或时间戳的gt/lt查询),避免深度分页。 - 示例(基于最后一条记录的
_id下一页):
Query query = new Query(
Criteria.where("_id").gt(lastId)
).limit(20).with(Sort.by(Sort.Direction.ASC, "_id"));5.字段名不一致导致查不到数据
问题现象:
Java 实体类字段为 userName,但数据库中是 user_name,查询返回 null 或空对象。
原因:
未使用 @Field 注解建立映射关系。
解决方案:
在实体类字段上显式标注:
public class User {
@Field("user_name")
private String userName;
@Field("created_at")
private Date createdAt;
}6.主键类型不匹配(String vs ObjectId)
问题现象:
实体类主键为 String id,但 MongoDB 自动生成的是 ObjectId,导致 findById() 查不到数据。
原因:
MongoDB 默认 _id 为 ObjectId,而 Java 用 String 接收时格式不匹配。
解决方案:
- 统一使用
String(推荐):插入时手动指定_id为字符串,或让 Spring 自动转换。 - 或使用
ObjectId类型:
import org.bson.types.ObjectId; @Id private ObjectId id;
- 若坚持用
String,确保插入和查询都使用相同格式(如 hex 字符串)。
7.聚合查询返回空或类型转换异常
问题现象:
使用 aggregate() 后,getMappedResults() 返回空,或报 ClassCastException。
原因:
聚合结果结构与接收类型(如 Map.class)不匹配,或字段名错误(如 _id 被重命名)。
解决方案:
- 打印原始结果调试:
AggregationResults<Document> raw = mongoTemplate.aggregate(agg, Document.class); System.out.println(raw.getRawResults());
- 确保
as("xxx")的字段名与接收对象一致。 - 若只取一个值,可用
getUniqueMappedResult();多个结果用getMappedResults()。
到此这篇关于Spring Data MongoDB的核心用法 附示例代码的文章就介绍到这了,更多相关spring data mongodb用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能
这篇文章主要介绍了Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的非常详细,感兴趣的朋友一起看看吧2025-04-04
Spring Boot中JSON数值溢出问题从报错到优雅解决办法
这篇文章主要介绍了Spring Boot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和数据校验,解决了该问题,文章还提供了类型范围推荐场景和常见问题解答,文中通过代码介绍的非常详细,需要的朋友可以参考下2025-04-04


最新评论