【翻譯】Spring Data Mongo: 投影和聚合

原文地址:https://www.baeldung.com/spring-data-mongodb-projections-aggregations

1. 概覽

Spring Data MongoDB提供了對MongoDB原生查詢語言的簡單的高層抽象兆衅。在這篇文章中接校,我們將探索其對投影和聚合框架的支持

如果你對這個(gè)主題不了解,請先閱讀我們的介紹文章[Spring Data MongoDB簡介【譯者注:原文】(https://www.baeldung.com/spring-data-mongodb-tutorial)。

2. 投影

在MongoDB中费就,投影是一種從數(shù)據(jù)庫的文檔中过蹂,獲取需要的字段的方式。這樣會(huì)縮減很多從數(shù)據(jù)庫服務(wù)器傳輸?shù)娇蛻舳说臄?shù)據(jù)歪赢,因此可以提高性能。

在Spring Data MongDB中单料,可以通過MongoTemplateMongoRepository來使用投影埋凯。

在繼續(xù)深入之前,讓我們先來看下即將用到的數(shù)據(jù)模型:

@Document
public class User {
    @Id
    private String id;
    private String name;
    private Integer age;
     
    // standard getters and setters
}

2.1 使用MongoTemplate進(jìn)行投影

Field類的include()exclude()方法用于包含或排除某個(gè)字段扫尖。

Query query = new Query();
query.fields().include("name").exclude("id");
List<User> john = mongoTemplate.find(query, User.class);

這些方法可以連在一起以包含或排除多個(gè)字段白对。除非顯式的說明排除,用@id(數(shù)據(jù)庫中的_id)標(biāo)記的字段總是會(huì)顯示在結(jié)果中换怖。

當(dāng)通過投影獲取數(shù)據(jù)時(shí)甩恼,在結(jié)果的類實(shí)例中,被排除的字段是null狰域。在這個(gè)例子中媳拴,字段是一個(gè)原生類型或者他們的包裝類,所以這些被排除的字段的值就是原生類型的缺省值兆览。

例如屈溉,Stringnullint/Integer是0抬探,而boolean/Booleanfalse子巾。

因此在上面的例子中,name字段是John小压,idnull线梗,而age0

2.2 使用MongoRepository進(jìn)行投影

如果使用MongoRepository怠益,需要用Json格式定義@Query注解中的fields字段:

@Query(value="{}", fields="{name : 1, _id : 0}")
List<User> findNameAndExcludeId();

結(jié)果和使用MongoTemplate一樣仪搔。value="{}"表示沒有過濾器,所以會(huì)查詢到所有的文檔蜻牢。

3. 聚合

MongoDB中的聚合是建立在處理數(shù)據(jù)和返回計(jì)算過的結(jié)果的過程中烤咧。數(shù)據(jù)在各個(gè)環(huán)節(jié)中被處理,一個(gè)環(huán)節(jié)的輸出抢呆,就是下一個(gè)環(huán)節(jié)的輸入煮嫌。這種在每個(gè)處理環(huán)節(jié)中應(yīng)用轉(zhuǎn)換和計(jì)算的能力,使得聚合成為一種非常強(qiáng)力的分析工具抱虐。

Spring Data MongoDB使用3個(gè)類對原生的聚合查詢進(jìn)行抽象昌阿。Aggregation類封裝聚合查詢,AggregationOperation類封裝每個(gè)獨(dú)立的pipeline節(jié)點(diǎn),AggregationResults是聚合后產(chǎn)生的結(jié)果的容器懦冰。

為了實(shí)現(xiàn)聚合灶轰,首先使用Aggregation類的靜態(tài)構(gòu)造器方法創(chuàng)建一個(gè)聚合管道(pipeline)。然后使用Aggregation類的newAggregation()方法創(chuàng)建Aggregation類的實(shí)例刷钢,最后使用MongoTemplate做聚合框往。

MatchOperation matchStage = Aggregation.match(new Criteria("foo").is("bar"));
ProjectionOperation projectStage = Aggregation.project("foo", "bar.baz");
         
Aggregation aggregation 
  = Aggregation.newAggregation(matchStage, projectStage);
 
AggregationResults<OutType> output 
  = mongoTemplate.aggregate(aggregation, "foobar", OutType.class);

請注意,MatchOperationProjectionOperation都實(shí)現(xiàn)了* AggregationOperation*闯捎。其他聚合管道還有一些類似的實(shí)現(xiàn)。 OutType是期望的結(jié)果的數(shù)據(jù)模型许溅。

現(xiàn)在瓤鼻,我們要看幾個(gè)具體的例子。這些例子涵蓋了主要的聚合管道和操作符贤重。

這篇文章中使用的數(shù)據(jù)集茬祷,列出了美國所有的郵政編碼,可以從這里下載到全部數(shù)據(jù)并蝗。

test數(shù)據(jù)庫中引入這個(gè)數(shù)據(jù)集祭犯,collection的名字為zips,讓我們看看其中一個(gè)document滚停。

{
    "_id" : "01001",
    "city" : "AGAWAM",
    "loc" : [
        -72.622739,
        42.070206
    ],
    "pop" : 15338,
    "state" : "MA"
}

為了簡化和代碼整潔沃粗,下面的代碼片段中,我們都假定Aggregation類的所有靜態(tài)方法都已經(jīng)靜態(tài)的引入了键畴。

3.1 獲取所有人口大于1000萬的州最盅,并按人口數(shù)降序排列

這個(gè)例子中,我們有3個(gè)管道

  1. $group環(huán)節(jié)按郵政編碼對人口求和
  2. $match環(huán)節(jié)找出那些人口超過1000萬的州
  3. $sort環(huán)節(jié)按人口的降序排列所有的document

期望的輸出會(huì)有一個(gè)_id字段代表州名字起惕,statePop字段代表整個(gè)州的人口涡贱。讓我們創(chuàng)建數(shù)據(jù)模型,并運(yùn)行這個(gè)聚合:

public class StatePoulation {
  
    @Id
    private String state;
    private Integer statePop;
  
    // standard getters and setters
}

@id注解會(huì)把結(jié)果中的_id映射為模型中的state

GroupOperation groupByStateAndSumPop = group("state")
  .sum("pop").as("statePop");
MatchOperation filterStates = match(new Criteria("statePop").gt(10000000));
SortOperation sortByPopDesc = sort(new Sort(Direction.DESC, "statePop"));
 
Aggregation aggregation = newAggregation(
  groupByStateAndSumPop, filterStates, sortByPopDesc);
AggregationResults<StatePopulation> result = mongoTemplate.aggregate(
  aggregation, "zips", StatePopulation.class);

【此處代碼有誤惹想,謹(jǐn)記new一個(gè)Aggregation對象時(shí)问词,一定要先放篩選條件,再放group部分嘀粱。這是因?yàn)閙ongo底層是一個(gè)pipeline激挪,先篩選,再聚合草穆,如果反過來的話灌灾,就查不到相關(guān)的數(shù)據(jù)了”】

AggregationResults類實(shí)現(xiàn)了Iterable接口锋喜,所以我們可以迭代它,并打印結(jié)果。

3.2 獲取平均城市人口最少的州

對于這個(gè)問題嘿般,我們需要四個(gè)環(huán)節(jié):

  1. $group求出每個(gè)城市的人口總和
  2. $group計(jì)算每個(gè)州的平均城市人口數(shù)
  3. $sort環(huán)節(jié)按州的平均城市人口數(shù)段标,升序排列每個(gè)州
  4. $limit取第一個(gè)州,即為平均城市人口數(shù)最少的州

盡管不是必須的炉奴,我們還是使用一個(gè)額外的$project環(huán)節(jié)把結(jié)果格式化為StatePopulation數(shù)據(jù)模型逼庞。

GroupOperation sumTotalCityPop = group("state", "city")
  .sum("pop").as("cityPop");
GroupOperation averageStatePop = group("_id.state")
  .avg("cityPop").as("avgCityPop");
SortOperation sortByAvgPopAsc = sort(new Sort(Direction.ASC, "avgCityPop"));
LimitOperation limitToOnlyFirstDoc = limit(1);
ProjectionOperation projectToMatchModel = project()
  .andExpression("_id").as("state")
  .andExpression("avgCityPop").as("statePop");
 
Aggregation aggregation = newAggregation(
  sumTotalCityPop, averageStatePop, sortByAvgPopAsc,
  limitToOnlyFirstDoc, projectToMatchModel);
 
AggregationResults<StatePopulation> result = mongoTemplate
  .aggregate(aggregation, "zips", StatePopulation.class);
StatePopulation smallestState = result.getUniqueMappedResult();

在這個(gè)例子中,我們已經(jīng)知道結(jié)果中只會(huì)有一個(gè)document瞻赶,因?yàn)槲覀円呀?jīng)在最后一個(gè)環(huán)節(jié)限制了輸出document的個(gè)數(shù)赛糟。因此,我們可以調(diào)用getUniqueMappedResult()方法獲得StatePopulation的實(shí)例砸逊。

另一個(gè)需要注意的地方是璧南,我們在投影的環(huán)節(jié),顯式的把_id轉(zhuǎn)換為州师逸,而不是使用@id注解司倚。

3.3 獲取郵政編碼個(gè)數(shù)最多和最少的州

這個(gè)例子中,我們需要三個(gè)環(huán)節(jié):

  1. $group計(jì)算每個(gè)州的郵政編碼個(gè)數(shù)
  2. $sort按郵政編碼個(gè)數(shù)對州進(jìn)行排序
  3. $group使用了操作符$first$last查找郵政編碼最多和最少的州
GroupOperation sumZips = group("state").count().as("zipCount");
SortOperation sortByCount = sort(Direction.ASC, "zipCount");
GroupOperation groupFirstAndLast = group().first("_id").as("minZipState")
  .first("zipCount").as("minZipCount").last("_id").as("maxZipState")
  .last("zipCount").as("maxZipCount");
 
Aggregation aggregation = newAggregation(sumZips, sortByCount, groupFirstAndLast);
 
AggregationResults<DBObject> result = mongoTemplate
  .aggregate(aggregation, "zips", DBObject.class);
DBObject dbObject = result.getUniqueMappedResult();

這次我們沒有使用任何模型篓像,而是使用MongoDB驅(qū)動(dòng)中已經(jīng)提供的DBObject动知。

4. 結(jié)論

在這篇文章中,我們學(xué)習(xí)了如何使用Spring Data MongoDB中的投影方式獲取數(shù)據(jù)庫中document的特定字段员辩。

我們也學(xué)習(xí)了Spring Data如何支持MongoDB的聚合框架盒粮。我們涉及到了主要的聚合方式——分組、投影屈暗、排序拆讯、數(shù)量和匹配,以及這些方式的具體的例子养叛。完整的代碼在github上种呐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弃甥,隨后出現(xiàn)的幾起案子爽室,更是在濱河造成了極大的恐慌,老刑警劉巖淆攻,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阔墩,死亡現(xiàn)場離奇詭異,居然都是意外死亡瓶珊,警方通過查閱死者的電腦和手機(jī)啸箫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞芹,“玉大人忘苛,你說我怎么就攤上這事蝉娜。” “怎么了扎唾?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵召川,是天一觀的道長。 經(jīng)常有香客問我胸遇,道長荧呐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任纸镊,我火速辦了婚禮倍阐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逗威。我一直安慰自己收捣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布庵楷。 她就那樣靜靜地躺著,像睡著了一般楣颠。 火紅的嫁衣襯著肌膚如雪尽纽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天童漩,我揣著相機(jī)與錄音弄贿,去河邊找鬼。 笑死矫膨,一個(gè)胖子當(dāng)著我的面吹牛差凹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侧馅,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼危尿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馁痴?” 一聲冷哼從身側(cè)響起谊娇,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罗晕,沒想到半個(gè)月后济欢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡小渊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年法褥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酬屉。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡半等,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酱鸭,我是刑警寧澤吗垮,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站凹髓,受9級(jí)特大地震影響烁登,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔚舀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一饵沧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赌躺,春花似錦狼牺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缅叠,卻和暖如春悄泥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肤粱。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工弹囚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人领曼。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓鸥鹉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庶骄。 傳聞我的和親對象是個(gè)殘疾皇子毁渗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355