Spring Data MongoDB 教程

聚合框架示例2

该示例是基于 MongoDB 聚合框架按省查询省中人口最多和最少的城市。下面增加了额外的排序(sort),以便在不同的 MongoDB 版本中产生稳定的结果。并且演示了分组、排序和投影操作。

下面是示例的关键代码,如下:

(1)映射给定的输入集合的结构

public class ZipInfo {
   // 城市
   private String city;
   // 省
   private String state;
   // 人口数
   private int population;
}

(2)投影结果映射的实体

public class ZipInfoStats {
   // 唯一ID
   private String id;
   // 州名称
   private String state;
   // 人口最多城市信息
   private City biggestCity;
   // 人口最少城市信息
   private City smallestCity;
}

(3)投影相关的关键代码

// 静态导入
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class,
   // 根据 state 和 city 进行分组,统计 population 人口数,并且别名为 pop
   group("state", "city")
           .sum("population").as("pop"),
   // 使用人口数进行排序
   sort(ASC, "pop", "state", "city"),
   // 根据 state(省)分组,取出第一行和最后一行数据,并进行取别名
   group("state")
           .last("city").as("biggestCity")
           .last("pop").as("biggestPop")
           .first("city").as("smallestCity")
           .first("pop").as("smallestPop"),
   // 对分组结构再次进行投影,结构见 ZipInfoStats
   project()
           .and("state").previousOperation()
           .and("biggestCity")
           .nested(bind("city", "biggestCity").and("population", "biggestPop"))
           .and("smallestCity")
           .nested(bind("city", "smallestCity").and("population", "smallestPop")),
   sort(ASC, "state")
);

AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, City.class, ZipInfoStats.class);
List<ZipInfoStats> zipInfoStatsList = result.getMappedResults();
for(ZipInfoStats zipInfoStats : zipInfoStatsList) {
   System.out.println(JSONObject.toJSONString(zipInfoStats));
}

注意,ZipInfo 类映射了给定的输入集合的结构,ZipInfoStats 类以所需的输出格式定义了该结构。

上面的代码使用了以下算法:

(1)使用 group 操作从输入集合中定义一个组。分组的标准是 state 和 city 字段的组合,这形成了组的 ID 结构。我们通过使用 sum 运算从分组的元素中汇总 population 属性的值,并将结果保存在 pop 字段中。

(2)使用 sort 操作按 pop、state 和 city 字段对中间结果进行升序排序,这样人口最少的城市位于结果的顶部,人口最多的城市位于底部。请注意,对 state 和 city 的排序是隐含地针对组 ID 字段进行的(由 Spring Data MongoDB 处理)。

(3)再次使用 group 操作,将中间结果按 state 分组。请注意,state 再次隐含地引用了一个组 ID 字段。我们在项目操作中分别调用 last(...) 和 first(...) 操作符来选择人口数最多和最少的城市名称。

(4)选择前一个 group 操作中的 state 字段。请注意,state 再次隐含地引用了一个组 ID 字段。因为我们不希望出现一个隐含生成的 ID,所以我们通过使用and(previousOperation()).exclude() 将 ID 从之前的操作中排除。因为我们想在我们的输出类中填充嵌套的城市结构,我们必须通过使用嵌套方法来发出适当的子文档。

(5)在 sort 操作中按 state 对结果列表进行排序。

注意:我们从 newAggregation() 方法的第一个参数 ZipInfo 类派生出输入集合的名称,即调用 mongoTemplate.aggregate() 方法时不需要指定输入集合类型。

完整示例

(1)application.properties 配置

# Log
logging.level.root=debug

# MongoDB
spring.data.mongodb.uri=mongodb://localhost:27017/test

(2)AppConfig.java 配置类

package com.hxstrive.springdata.mongodb.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;

/**
* 配置 MongoTemplate
* @author hxstrive.com 2022/12/23
*/
@Slf4j
@Configuration
public class AppConfig {

   @Bean
   public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
       log.info("mongoTemplate({}, {})", mongoDatabaseFactory);
       return new MongoTemplate(mongoDatabaseFactory);
   }

}

(3)映射给定的输入集合的结构

import lombok.Builder;
import lombok.Data;

/**
* city 集合分组后的中间结果实体类型
* @author hxstrive.com
*/
@Builder
@Data
public class ZipInfo {
   // 州名称
   private String state;
   // 城市名称
   private String city;
   // 人口数量
   private int population;
}

(4)投影结果映射的实体

import lombok.Data;

/**
* 聚合统计信息实体
* @author hxstrive.com
*/
@Data
public class ZipInfoStats {
   // 唯一ID
   private String id;
   // 州名称
   private String state;
   // 人口最多城市信息
   private City biggestCity;
   // 人口最少城市信息
   private City smallestCity;
}

(5)客户端代码,通过 MongoTemplate 实现分组和投影。

package com.hxstrive.springdata.mongodb;

import com.alibaba.fastjson.JSONObject;
import com.hxstrive.springdata.mongodb.entity.demo2.ZipInfo;
import com.hxstrive.springdata.mongodb.entity.demo2.ZipInfoStats;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import java.util.List;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

/**
* 聚合框架示例2
* @author hxstrive.com
*/
@SpringBootTest
public class AggregationFrameworkDemo2 {

   @Autowired
   private MongoTemplate mongoTemplate;

   @BeforeEach
   public void init() {
       mongoTemplate.dropCollection(ZipInfo.class);

       // 准备数据
       mongoTemplate.insert(ZipInfo.builder().state("SiChuan").city("ChengDu").population(10000).build());
       mongoTemplate.insert(ZipInfo.builder().state("SiChuan").city("DaZhou").population(15000).build());
       mongoTemplate.insert(ZipInfo.builder().state("GuangZhou").city("ShangHai").population(11000).build());
       mongoTemplate.insert(ZipInfo.builder().state("GuangZhou").city("ShenZhen").population(8000).build());
       mongoTemplate.insert(ZipInfo.builder().state("JiangSu").city("NanJing").population(10900).build());
   }

   @Test
   public void contextLoads() {
       TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class,
               group("state", "city")
                       .sum("population").as("pop"),
               sort(ASC, "pop", "state", "city"),
               group("state")
                       .last("city").as("biggestCity")
                       .last("pop").as("biggestPop")
                       .first("city").as("smallestCity")
                       .first("pop").as("smallestPop"),
               project()
                       .and("state").previousOperation()
                       .and("biggestCity")
                       .nested(bind("city", "biggestCity").and("population", "biggestPop"))
                       .and("smallestCity")
                       .nested(bind("city", "smallestCity").and("population", "smallestPop")),
               sort(ASC, "state")
       );

       AggregationResults<ZipInfoStats> result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class);
       List<ZipInfoStats> zipInfoStatsList = result.getMappedResults();
       for(ZipInfoStats zipInfoStats : zipInfoStatsList) {
           System.out.println(JSONObject.toJSONString(zipInfoStats));
       }
       // 结果:
       // {"biggestCity":{"city":"ShangHai","population":11000},"smallestCity":{"city":"ShenZhen","population":8000},"state":"GuangZhou"}
       // {"biggestCity":{"city":"NanJing","population":10900},"smallestCity":{"city":"NanJing","population":10900},"state":"JiangSu"}
       // {"biggestCity":{"city":"DaZhou","population":15000},"smallestCity":{"city":"ChengDu","population":10000},"state":"SiChuan"}

       // 执行的聚合语句如下:
       // [
       //    { "$group" : { "_id" : { "state" : "$state", "city" : "$city"}, "pop" : { "$sum" : "$population"}}},
       //    { "$sort" : { "pop" : 1, "_id.state" : 1, "_id.city" : 1}},
       //    { "$group" : { "_id" : "$_id.state",
       //        "biggestCity" : { "$last" : "$_id.city"}, "biggestPop" : { "$last" : "$pop"},
       //        "smallestCity" : { "$first" : "$_id.city"}, "smallestPop" : { "$first" : "$pop"}}},
       //    { "$project" : { "_id" : 0, "state" : "$_id",
       //        "biggestCity" : { "city" : "$biggestCity", "population" : "$biggestPop"},
       //        "smallestCity" : { "city" : "$smallestCity", "population" : "$smallestPop"}}},
       //    { "$sort" : { "state" : 1}}
       // ]
   }

}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号