SpringBoot之MongoTemplate的查詢可以怎麼耍

一灰灰發表於2019-01-13

學習一個新的資料庫,一般怎麼下手呢?基本的CURD沒跑了,當可以熟練的增、刪、改、查一個資料庫時,可以說對這個資料庫算是入門了,如果需要更進一步的話,就需要了解下資料庫的特性,比如索引、事物、鎖、分散式支援等

本篇博文為mongodb的入門篇,將介紹一下基本的查詢操作,在Spring中可以怎麼玩

原文可參看: 190113-SpringBoot高階篇MongoDB之查詢基本使用姿勢

I. 基本使用

0. 環境準備

在正式開始之前,先準備好環境,搭建好工程,對於這一步的詳細資訊,可以參考博文: 181213-SpringBoot高階篇MongoDB之基本環境搭建與使用

接下來,在一個集合中,準備一下資料如下,我們的基本查詢範圍就是這些資料

data

1. 根據欄位進行查詢

最常見的查詢場景,比如我們根據查詢user=一灰灰blog的資料,這裡主要會使用Query + Criteria 來完成

@Component
public class MongoReadWrapper {
    private static final String COLLECTION_NAME = "demo";

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 指定field查詢
     */
    public void specialFieldQuery() {
        Query query = new Query(Criteria.where("user").is("一灰灰blog"));
        // 查詢一條滿足條件的資料
        Map result = mongoTemplate.findOne(query, Map.class, COLLECTION_NAME);
        System.out.println("query: " + query + " | specialFieldQueryOne: " + result);

        // 滿足所有條件的資料
        List<Map> ans = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
        System.out.println("query: " + query + " | specialFieldQueryAll: " + ans);
    }
}
複製程式碼

上面是一個實際的case,從中可以知道一般的查詢方式為:

  • Criteria.where(xxx).is(xxx)來指定具體的查詢條件
  • 封裝Query物件 new Query(criteria)
  • 藉助mongoTemplate執行查詢 mongoTemplate.findOne(query, resultType, collectionName)

其中findOne表示只獲取一條滿足條件的資料;find則會將所有滿足條件的返回;上面執行之後,刪除結果如

query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { } | specialFieldQueryOne: {_id=5c2368b258f984a4fda63cee, user=一灰灰blog, desc=帥氣逼人的碼農界老秀}
query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { } | specialFieldQueryAll: [{_id=5c2368b258f984a4fda63cee, user=一灰灰blog, desc=帥氣逼人的碼農界老秀}, {_id=5c3afaf4e3ac8e8d2d39238a, user=一灰灰blog, desc=帥氣逼人的碼農界老秀3}, {_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}, {_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}]
複製程式碼

2. and多條件查詢

前面是隻有一個條件滿足,現在如果是要求同時滿足多個條件,則利用org.springframework.data.mongodb.core.query.Criteria#and來斜街多個查詢條件

/**
 * 多個查詢條件同時滿足
 */
public void andQuery() {
    Query query = new Query(Criteria.where("user").is("一灰灰blog").and("age").is(18));
    Map result = mongoTemplate.findOne(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | andQuery: " + result);
}
複製程式碼

刪除結果如下

query: Query: { "user" : "一灰灰blog", "age" : 18 }, Fields: { }, Sort: { } | andQuery: {_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}
複製程式碼

3. or或查詢

and對應的就是or,多個條件中只要一個滿足即可,這個與and的使用有些區別, 藉助org.springframework.data.mongodb.core.query.Criteria#orOperator來實現,傳參為多個Criteria物件,其中每一個表示一種查詢條件

/**
 * 或查詢
 */
public void orQuery() {
    // 等同於 db.getCollection('demo').find({"user": "一灰灰blog", $or: [{ "age": 18}, { "sign": {$exists: true}}]})
    Query query = new Query(Criteria.where("user").is("一灰灰blog")
            .orOperator(Criteria.where("age").is(18), Criteria.where("sign").exists(true)));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | orQuery: " + result);

    // 單獨的or查詢
    // 等同於Query: { "$or" : [{ "age" : 18 }, { "sign" : { "$exists" : true } }] }, Fields: { }, Sort: { }
    query = new Query(new Criteria().orOperator(Criteria.where("age").is(18), Criteria.where("sign").exists(true)));
    result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | orQuery: " + result);
}
複製程式碼

執行後輸出結果為

query: Query: { "user" : "一灰灰blog", "$or" : [{ "age" : 18 }, { "sign" : { "$exists" : true } }] }, Fields: { }, Sort: { } | orQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}]
query: Query: { "$or" : [{ "age" : 18 }, { "sign" : { "$exists" : true } }] }, Fields: { }, Sort: { } | orQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}, {_id=5c3b0538e3ac8e8d2d392390, user=二灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}]
複製程式碼

4. in查詢

標準的in查詢case

/**
 * in查詢
 */
public void inQuery() {
    // 相當於:
    Query query = new Query(Criteria.where("age").in(Arrays.asList(18, 20, 30)));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | inQuery: " + result);
}
複製程式碼

輸出

query: Query: { "age" : { "$in" : [18, 20, 30] } }, Fields: { }, Sort: { } | inQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
複製程式碼

5. 數值比較

數值的比較大小,主要使用的是 get, gt, lt, let

/**
 * 數字型別,比較查詢 >
 */
public void compareBigQuery() {
    // age > 18
    Query query = new Query(Criteria.where("age").gt(18));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | compareBigQuery: " + result);

    // age >= 18
    query = new Query(Criteria.where("age").gte(18));
    result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | compareBigQuery: " + result);
}

/**
 * 數字型別,比較查詢 <
 */
public void compareSmallQuery() {
    // age < 20
    Query query = new Query(Criteria.where("age").lt(20));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | compareSmallQuery: " + result);

    // age <= 20
    query = new Query(Criteria.where("age").lte(20));
    result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | compareSmallQuery: " + result);
}
複製程式碼

輸出

query: Query: { "age" : { "$gt" : 18 } }, Fields: { }, Sort: { } | compareBigQuery: [{_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
query: Query: { "age" : { "$gte" : 18 } }, Fields: { }, Sort: { } | compareBigQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
query: Query: { "age" : { "$lt" : 20 } }, Fields: { }, Sort: { } | compareSmallQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}]
query: Query: { "age" : { "$lte" : 20 } }, Fields: { }, Sort: { } | compareSmallQuery: [{_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
複製程式碼

6. 正則查詢

牛逼高大上的功能

/**
 * 正則查詢
 */
public void regexQuery() {
    Query query = new Query(Criteria.where("user").regex("^一灰灰blog"));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | regexQuery: " + result);
}
複製程式碼

輸出

query: Query: { "user" : { "$regex" : "^一灰灰blog", "$options" : "" } }, Fields: { }, Sort: { } | regexQuery: [{_id=5c2368b258f984a4fda63cee, user=一灰灰blog, desc=帥氣逼人的碼農界老秀}, {_id=5c3afacde3ac8e8d2d392389, user=一灰灰blog2, desc=帥氣逼人的碼農界老秀2}, {_id=5c3afaf4e3ac8e8d2d39238a, user=一灰灰blog, desc=帥氣逼人的碼農界老秀3}, {_id=5c3afafbe3ac8e8d2d39238b, user=一灰灰blog4, desc=帥氣逼人的碼農界老秀4}, {_id=5c3afb0ae3ac8e8d2d39238c, user=一灰灰blog5, desc=帥氣逼人的碼農界老秀5}, {_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}, {_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}]
複製程式碼

7. 查詢總數

統計常用,這個主要利用的是mongoTemplate.count方法

/**
 * 查詢總數
 */
public void countQuery() {
    Query query = new Query(Criteria.where("user").is("一灰灰blog"));
    long cnt = mongoTemplate.count(query, COLLECTION_NAME);
    System.out.println("query: " + query + " | cnt " + cnt);
}
複製程式碼

輸出

query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { } | cnt 5
複製程式碼

8. 分組查詢

這個對應的是mysql中的group查詢,但是在mongodb中,更多的是通過聚合查詢,可以完成很多類似的操作,下面藉助聚合,來看一下分組計算總數怎麼玩

/*
 * 分組查詢
 */
public void groupQuery() {
    // 根據使用者名稱進行分組統計,每個使用者名稱對應的數量
    // aggregate([ { "$group" : { "_id" : "user" , "userCount" : { "$sum" : 1}}}] )
    Aggregation aggregation = Aggregation.newAggregation(Aggregation.group("user").count().as("userCount"));
    AggregationResults<Map> ans = mongoTemplate.aggregate(aggregation, COLLECTION_NAME, Map.class);
    System.out.println("query: " + aggregation + " | groupQuery " + ans.getMappedResults());
}
複製程式碼

注意下,這裡用Aggregation而不是前面的QueryCriteria,輸出如下

query: { "aggregate" : "__collection__", "pipeline" : [{ "$group" : { "_id" : "$user", "userCount" : { "$sum" : 1 } } }] } | groupQuery [{_id=一灰灰blog, userCount=5}, {_id=一灰灰blog2, userCount=1}, {_id=一灰灰blog4, userCount=1}, {_id=二灰灰blog, userCount=1}, {_id=一灰灰blog5, userCount=1}]
複製程式碼

9. 排序

sort,比較常見的了,在mongodb中有個有意思的地方在於某個欄位,document中並不一定存在,這是會怎樣呢?

/**
 * 排序查詢
 */
public void sortQuery() {
    // sort查詢條件,需要用with來銜接
    Query query = Query.query(Criteria.where("user").is("一灰灰blog")).with(Sort.by("age"));
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | sortQuery " + result);
}
複製程式碼

輸出結果如下,對於沒有這個欄位的document也被查出來了

query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { "age" : 1 } | sortQuery [{_id=5c2368b258f984a4fda63cee, user=一灰灰blog, desc=帥氣逼人的碼農界老秀}, {_id=5c3afaf4e3ac8e8d2d39238a, user=一灰灰blog, desc=帥氣逼人的碼農界老秀3}, {_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}, {_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
複製程式碼

10. 分頁

資料量多的時候,分頁查詢比較常見,用得多就是limit和skip了

/**
 * 分頁查詢
 */
public void pageQuery() {
    // limit限定查詢2條
    Query query = Query.query(Criteria.where("user").is("一灰灰blog")).with(Sort.by("age")).limit(2);
    List<Map> result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | limitPageQuery " + result);


    // skip()方法來跳過指定數量的資料
    query = Query.query(Criteria.where("user").is("一灰灰blog")).with(Sort.by("age")).skip(2);
    result = mongoTemplate.find(query, Map.class, COLLECTION_NAME);
    System.out.println("query: " + query + " | skipPageQuery " + result);
}
複製程式碼

輸出結果表明,limit用來限制查詢多少條資料,skip則表示跳過前面多少條資料

query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { "age" : 1 } | limitPageQuery [{_id=5c2368b258f984a4fda63cee, user=一灰灰blog, desc=帥氣逼人的碼農界老秀}, {_id=5c3afaf4e3ac8e8d2d39238a, user=一灰灰blog, desc=帥氣逼人的碼農界老秀3}]
query: Query: { "user" : "一灰灰blog" }, Fields: { }, Sort: { "age" : 1 } | skipPageQuery [{_id=5c3b003ee3ac8e8d2d39238f, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, sign=hello world}, {_id=5c3afb1ce3ac8e8d2d39238d, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=18.0}, {_id=5c3b0031e3ac8e8d2d39238e, user=一灰灰blog, desc=帥氣逼人的碼農界老秀6, age=20.0}]
複製程式碼

11. 小結

上面給出的一些常見的查詢姿勢,當然並不全面,比如我們如果需要查詢document中的部分欄位怎麼辦?比如document內部結果比較複雜,有內嵌的物件或者陣列時,巢狀查詢可以怎麼玩?索引什麼的又可以怎麼利用起來,從而優化查詢效率?如何通過傳說中自動生成的_id來獲取文件建立的時間戳?

先留著這些疑問,後面再補上

II. 其他

0. 專案

1. 一灰灰Blog

一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

2. 宣告

盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

一灰灰blog

QrCode

知識星球

goals

相關文章