Spring Data MongoDB的核心用法 附示例代碼
在使用 Spring Data MongoDB 操作 MongoDB 時,掌握實體映射規(guī)則與常用 CRUD 方法是開發(fā)高效應用的基礎。本文將系統(tǒng)梳理 Spring Data MongoDB 的核心用法,涵蓋實體類注解、增刪改查、條件查詢、分頁排序等常見場景,并提供可直接復用的代碼示例。
一、實體類與集合映射規(guī)則
- 集合命名規(guī)則
- 若實體類未使用
@Document注解,則默認生成的集合名稱為首字母小寫的駝峰格式,且區(qū)分大小寫。 - 例如:實體類名為
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); // 返回帶主鍵的對象- 若集合不存在,會自動創(chuàng)建。
- 自動注入
_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 統(tǒng)計總數
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)計
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 過程中,開發(fā)者常會遇到一些“坑”。以下是幾個高頻問題及其解決方法:
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() 是全量替換操作,不是局部更新。它會用傳入對象的當前狀態(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.模糊查詢因特殊字符報錯或結果異常
問題現象:
用戶輸入如 張*三、李.四 等包含正則元字符的內容進行模糊查詢時,拋出異常或匹配不到預期結果。
原因:regex() 直接將字符串當作正則表達式處理,而 *、.、? 等是正則特殊字符。
解決方案:
使用 Pattern.quote() 對輸入進行轉義:
String keyword = "張*三";
String escapedKeyword = Pattern.quote(keyword); // 轉義為字面量
Query query = Query.query(Criteria.where("name").regex(escapedKeyword, "i"));? 同時建議加上
"i"標志實現不區(qū)分大小寫匹配。
4.分頁查詢總數不準或性能差
問題現象:
分頁時需要同時獲取總條數,但每次都要執(zhí)行一次 count 查詢,大數據量下響應慢。
原因:PageRequest 本身不包含總數,需額外調用 mongoTemplate.count(query, clazz)。
解決方案:
- 小數據量:直接 count。
- 大數據量/高并發(fā):
- 使用緩存(如 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 接收時格式不匹配。
解決方案:
- 統(tǒng)一使用
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用法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java通過SSLEngine與NIO實現HTTPS訪問的操作方法
這篇文章主要介紹了Java通過SSLEngine與NIO實現HTTPS訪問,需要在Connect操作、Connected操作、Read和Write操作中加入SSL相關的處理即可,需要的朋友可以參考下2021-08-08
Java zookeeper圖形化工具ZooInspector用法詳解
這篇文章主要介紹了Java zookeeper圖形化工具ZooInspector用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07
SpringBoot使用Mybatis-Generator配置過程詳解
這篇文章主要介紹了SpringBoot使用Mybatis-Generator配置過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02
Java如何基于command調用openssl生成私鑰證書
這篇文章主要介紹了Java如何基于command調用openssl生成私鑰證書,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-08-08

