Spring Data MongoDB的核心用法 附示例代碼
在使用 Spring Data MongoDB 操作 MongoDB 時(shí),掌握實(shí)體映射規(guī)則與常用 CRUD 方法是開發(fā)高效應(yīng)用的基礎(chǔ)。本文將系統(tǒng)梳理 Spring Data MongoDB 的核心用法,涵蓋實(shí)體類注解、增刪改查、條件查詢、分頁(yè)排序等常見場(chǎng)景,并提供可直接復(fù)用的代碼示例。
一、實(shí)體類與集合映射規(guī)則
- 集合命名規(guī)則
- 若實(shí)體類未使用
@Document注解,則默認(rèn)生成的集合名稱為首字母小寫的駝峰格式,且區(qū)分大小寫。 - 例如:實(shí)體類名為
UserDao→ 對(duì)應(yīng)集合名為userDao。 - 使用
@Document(collection = "users")可顯式指定集合名。
- 若實(shí)體類未使用
- 主鍵標(biāo)識(shí)
- 使用
@Id標(biāo)注主鍵字段。 - 若字段名為
id或_id,可省略@Id注解(Spring Data 會(huì)自動(dòng)識(shí)別)。
- 使用
- 字段映射
- 當(dāng) Java 字段名與 MongoDB 中的字段名不一致時(shí),使用
@Field("db_field_name")進(jìn)行映射。
- 當(dāng) Java 字段名與 MongoDB 中的字段名不一致時(shí),使用
User user = new User("張三", 25);
User saved = mongoTemplate.insert(user); // 返回帶主鍵的對(duì)象?? 注意:使用
mongoTemplate.insert()插入數(shù)據(jù)時(shí),MongoDB 會(huì)自動(dòng)添加_class字段,存儲(chǔ)該文檔對(duì)應(yīng)的 Java 類全限定名(如com.example.User),用于反序列化時(shí)類型還原。
二、新增操作
User user = new User("張三", 25);
User saved = mongoTemplate.insert(user); // 返回帶主鍵的對(duì)象- 若集合不存在,會(huì)自動(dòng)創(chuàng)建。
- 自動(dòng)注入
_class字段。
三、修改操作
3.1 全量替換(慎用)
mongoTemplate.save(user);
?? 此方法會(huì)完全覆蓋原文檔!即使只設(shè)置了部分字段,其他字段也會(huì)被置為
null。
3.2 局部更新(推薦)
Query query = Query.query(Criteria.where("id").is(userId));
Update update = new Update().set("name", "李四");
// 更新第一個(gè)匹配項(xiàng)
mongoTemplate.updateFirst(query, update, "userDao");
// 更新所有匹配項(xiàng)
mongoTemplate.updateMulti(query, update, "userDao");Update.set()對(duì)應(yīng) MongoDB 的$set操作符。- 第三個(gè)參數(shù)為集合名(非實(shí)體類名)。
四、刪除操作
// 1. 根據(jù)主鍵刪除(只需設(shè)置 id 字段)
User user = new User();
user.setId("60d...");
mongoTemplate.remove(user);
// 2. 根據(jù)條件刪除
Query query = Query.query(Criteria.where("age").lt(18));
mongoTemplate.remove(query, "userDao");五、查詢操作
5.1 基礎(chǔ)查詢
// 查詢?nèi)?
List<User> all = mongoTemplate.findAll(User.class);
// 根據(jù) ID 查詢
User user = mongoTemplate.findById("60d...", User.class);
// 查詢第一條匹配結(jié)果
User first = mongoTemplate.findOne(query, User.class);
// 查詢所有匹配結(jié)果
List<User> list = mongoTemplate.find(query, User.class);5.2 條件查詢(Criteria)
判斷字段是否存在
// 等價(jià)于 db.user.find({name: {$exists: false}})
Query query = Query.query(Criteria.where("name").exists(false));數(shù)值比較
| 含義 | 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"));? 強(qiáng)烈建議使用
Pattern.quote()轉(zhuǎn)義用戶輸入,防止正則注入!
去重查詢
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 分頁(yè)
// 第0頁(yè)(第一頁(yè)),每頁(yè)2條 Pageable pageable = PageRequest.of(0, 2); Query query = new Query().with(pageable); List<User> pageData = mongoTemplate.find(query, User.class); // 分頁(yè) + 排序 Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.ASC, "name"));
5.5 統(tǒng)計(jì)總數(shù)
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 帶條件的分組統(tǒng)計(jì)
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") + ", 數(shù)量: " + map.get("count"));
}??
Aggregation.match()放在group前表示“先過濾后分組”,放在后面則是“先分組后過濾”。
六、常見問題與解決方案(FAQ)
在實(shí)際使用 Spring Data MongoDB 過程中,開發(fā)者常會(huì)遇到一些“坑”。以下是幾個(gè)高頻問題及其解決方法:
1.插入數(shù)據(jù)后集合中多出_class字段
問題現(xiàn)象:
使用 mongoTemplate.insert() 插入文檔后,MongoDB 中自動(dòng)多了一個(gè) _class 字段,值為 Java 類的全限定名(如 com.example.User)。
原因:
Spring Data MongoDB 默認(rèn)啟用類型映射(Type Mapping),用于反序列化時(shí)確定目標(biāo) Java 類型。
解決方案:
- 方案一(推薦):若不需要類型信息,可在實(shí)體類上添加
@Document并禁用_class:
@Document(collection = "users")
@TypeAlias("user") // 可選:用簡(jiǎn)短別名替代全類名
public class User { ... }- 方案二(全局禁用):自定義
MongoCustomConversions,移除_class寫入(適用于所有實(shí)體):
@Configuration
public class MongoConfig {
@Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.emptyList());
}
}?? 注意:禁用后,若集合中存在多種類型文檔,反序列化可能失敗。
2.save()方法導(dǎo)致字段被置為 null
問題現(xiàn)象:
調(diào)用 mongoTemplate.save(user) 更新對(duì)象時(shí),未設(shè)置的字段在數(shù)據(jù)庫(kù)中被清空為 null。
原因:save() 是全量替換操作,不是局部更新。它會(huì)用傳入對(duì)象的當(dāng)前狀態(tài)完全覆蓋原文檔。
解決方案:
改用 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.模糊查詢因特殊字符報(bào)錯(cuò)或結(jié)果異常
問題現(xiàn)象:
用戶輸入如 張*三、李.四 等包含正則元字符的內(nèi)容進(jìn)行模糊查詢時(shí),拋出異?;蚱ヅ洳坏筋A(yù)期結(jié)果。
原因:regex() 直接將字符串當(dāng)作正則表達(dá)式處理,而 *、.、? 等是正則特殊字符。
解決方案:
使用 Pattern.quote() 對(duì)輸入進(jìn)行轉(zhuǎn)義:
String keyword = "張*三";
String escapedKeyword = Pattern.quote(keyword); // 轉(zhuǎn)義為字面量
Query query = Query.query(Criteria.where("name").regex(escapedKeyword, "i"));? 同時(shí)建議加上
"i"標(biāo)志實(shí)現(xiàn)不區(qū)分大小寫匹配。
4.分頁(yè)查詢總數(shù)不準(zhǔn)或性能差
問題現(xiàn)象:
分頁(yè)時(shí)需要同時(shí)獲取總條數(shù),但每次都要執(zhí)行一次 count 查詢,大數(shù)據(jù)量下響應(yīng)慢。
原因:PageRequest 本身不包含總數(shù),需額外調(diào)用 mongoTemplate.count(query, clazz)。
解決方案:
- 小數(shù)據(jù)量:直接 count。
- 大數(shù)據(jù)量/高并發(fā):
- 使用緩存(如 Redis 緩存總數(shù),定時(shí)刷新)。
- 改用“游標(biāo)分頁(yè)”(基于
_id或時(shí)間戳的gt/lt查詢),避免深度分頁(yè)。 - 示例(基于最后一條記錄的
_id下一頁(yè)):
Query query = new Query(
Criteria.where("_id").gt(lastId)
).limit(20).with(Sort.by(Sort.Direction.ASC, "_id"));5.字段名不一致導(dǎo)致查不到數(shù)據(jù)
問題現(xiàn)象:
Java 實(shí)體類字段為 userName,但數(shù)據(jù)庫(kù)中是 user_name,查詢返回 null 或空對(duì)象。
原因:
未使用 @Field 注解建立映射關(guān)系。
解決方案:
在實(shí)體類字段上顯式標(biāo)注:
public class User {
@Field("user_name")
private String userName;
@Field("created_at")
private Date createdAt;
}6.主鍵類型不匹配(String vs ObjectId)
問題現(xiàn)象:
實(shí)體類主鍵為 String id,但 MongoDB 自動(dòng)生成的是 ObjectId,導(dǎo)致 findById() 查不到數(shù)據(jù)。
原因:
MongoDB 默認(rèn) _id 為 ObjectId,而 Java 用 String 接收時(shí)格式不匹配。
解決方案:
- 統(tǒng)一使用
String(推薦):插入時(shí)手動(dòng)指定_id為字符串,或讓 Spring 自動(dòng)轉(zhuǎn)換。 - 或使用
ObjectId類型:
import org.bson.types.ObjectId; @Id private ObjectId id;
- 若堅(jiān)持用
String,確保插入和查詢都使用相同格式(如 hex 字符串)。
7.聚合查詢返回空或類型轉(zhuǎn)換異常
問題現(xiàn)象:
使用 aggregate() 后,getMappedResults() 返回空,或報(bào) ClassCastException。
原因:
聚合結(jié)果結(jié)構(gòu)與接收類型(如 Map.class)不匹配,或字段名錯(cuò)誤(如 _id 被重命名)。
解決方案:
- 打印原始結(jié)果調(diào)試:
AggregationResults<Document> raw = mongoTemplate.aggregate(agg, Document.class); System.out.println(raw.getRawResults());
- 確保
as("xxx")的字段名與接收對(duì)象一致。 - 若只取一個(gè)值,可用
getUniqueMappedResult();多個(gè)結(jié)果用getMappedResults()。
到此這篇關(guān)于Spring Data MongoDB的核心用法 附示例代碼的文章就介紹到這了,更多相關(guān)spring data mongodb用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot引入額外的YAML配置文件的代碼實(shí)現(xiàn)
在SpringBoot項(xiàng)目中,有時(shí)需要引入除application.yml之外的配置文件(例如在開發(fā)公共組件時(shí)),使用@PropertySource注解可以實(shí)現(xiàn)這一需求,但有一些細(xì)節(jié)點(diǎn)需要注意,在此記錄,感興趣的小伙伴跟著小編一起來看看吧2024-09-09
java實(shí)現(xiàn)List中對(duì)象排序的方法
這篇文章主要介紹了java實(shí)現(xiàn)List中對(duì)象排序的方法,涉及Java中的遍歷與對(duì)象操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
javaWeb使用驗(yàn)證碼實(shí)現(xiàn)簡(jiǎn)單登錄
這篇文章主要為大家詳細(xì)介紹了javaWeb使用驗(yàn)證碼實(shí)現(xiàn)簡(jiǎn)單登錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的操作方法
這篇文章主要介紹了Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問,需要在Connect操作、Connected操作、Read和Write操作中加入SSL相關(guān)的處理即可,需要的朋友可以參考下2021-08-08
Java zookeeper圖形化工具ZooInspector用法詳解
這篇文章主要介紹了Java zookeeper圖形化工具ZooInspector用法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
SpringBoot使用Mybatis-Generator配置過程詳解
這篇文章主要介紹了SpringBoot使用Mybatis-Generator配置過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
SpringBoot實(shí)現(xiàn)日志鏈路追蹤的項(xiàng)目實(shí)踐
在分布式系統(tǒng)中,由于請(qǐng)求的處理過程可能會(huì)跨越多個(gè)服務(wù),因此,對(duì)請(qǐng)求的追蹤變得尤為重要,本文主要介紹了SpringBoot實(shí)現(xiàn)日志鏈路追蹤的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-03-03
Java如何基于command調(diào)用openssl生成私鑰證書
這篇文章主要介紹了Java如何基于command調(diào)用openssl生成私鑰證書,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Java批量實(shí)現(xiàn)將Word文檔高效轉(zhuǎn)換為多頁(yè)TIFF圖片
在日常辦公或文檔管理中,我們經(jīng)常會(huì)遇到需要轉(zhuǎn)換 Word 文檔,本文將詳細(xì)講解如何使用 Java 將 Word 文檔轉(zhuǎn)換為 TIFF 圖片,包括單文件轉(zhuǎn)換、批量轉(zhuǎn)換以及轉(zhuǎn)換特定節(jié)的示例,有需要的小伙伴可以了解下2026-03-03

