原文地址: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中单料,可以通過MongoTemplate和MongoRepository來使用投影埋凯。
在繼續(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è)原生類型或者他們的包裝類,所以這些被排除的字段的值就是原生類型的缺省值兆览。
例如屈溉,String是null,int/Integer是0抬探,而boolean/Boolean是false子巾。
因此在上面的例子中,name字段是John小压,id是null线梗,而age是0。
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);
請注意,MatchOperation和ProjectionOperation都實(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è)管道
- $group環(huán)節(jié)按郵政編碼對人口求和
- $match環(huán)節(jié)找出那些人口超過1000萬的州
- $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é):
- $group求出每個(gè)城市的人口總和
- $group計(jì)算每個(gè)州的平均城市人口數(shù)
- $sort環(huán)節(jié)按州的平均城市人口數(shù)段标,升序排列每個(gè)州
- $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é):
- $group計(jì)算每個(gè)州的郵政編碼個(gè)數(shù)
- $sort按郵政編碼個(gè)數(shù)對州進(jìn)行排序
- $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上种呐。