Spring Boot 整合——MongoDB整合4(MongoDB分组去重以及MongoDB联表查询)

文章前面

关于版本

依赖 版本
springboot 2.0.8.RELEASE
mongodb 4.0.14

本内容只是为了介绍mongodb最基础的使用以及配置,作为一个知名的数据库,其存在相当多的高级用法,展开来介绍内容会相当多,当然本人并非相关领域的大神,下面内容只不过整理了自己日常使用的一些积累。是对自己经验的积累,也希望能帮助后来的同学

关于项目

本内容也是我尝试整理工作中接触过各种工具在springboot中使用的方法。下面介绍的所有方法都已经提供了测试用例。因为每个例子涉及代码较多,所以文章中只贴出了部分代码。全部的代码在这里:https://gitee.com/daifyutils/springboot-samples。

联表查询

简单联表查询

mongodb多表查询使用Aggregation.lookupAPI进行操作。其方法lookup(String from, String localField, String foreignField, String as)四个参数分别为需要连接的表、主表中的连接字段、被连接表的字段、以及连接后的数据被映射到结果集中的字段。

下面是一个简单的联表查询,将Order表中的userIdUserInfo表中的id字段进行关联,查询出的UserInfo信息保存到结果集中的userInfo字段中。

    @Override
    public List<OrderVo> queryOrderAndUserInfo() {

        Criteria criteria = new Criteria();
        MatchOperation match = Aggregation.match(criteria);
        // 和另外一个集合进行关联,将其作为其属性
        LookupOperation lookup = Aggregation.lookup("UserInfo",// 连接表
            "userId",// 查询表中字段
            "_id",// 连接表中字段
            "userInfo");// 返回数据所在的属性名称
        TypedAggregation<Order> noRepeatAggregation2 =
            Aggregation.newAggregation(Order.class,match,lookup);

        AggregationResults<OrderVo> noRepeatDataInfoVos2 = mongoTemplate.aggregate(noRepeatAggregation2, OrderVo.class);
        List<OrderVo> noRepeatDataList2 = noRepeatDataInfoVos2.getMappedResults();
        return noRepeatDataList2;
    }

联表查询-对被联结的数据进行操作

当两个表数据进行连接之后,数据被映射到某个属性下面。其结构类似于下面格式。

{
    "field":"value",
    "field2":"value2",
    "field3":"value3",
    "被映射的属性" : {
        "field":"value",
        "field2":"value2"
    }
}

对于上面这种格式我们可以使用object.object方式直接访问被嵌套进来的数据,同时对其内容进行一些聚合或者排序或者筛选操作。

下面的例子就是在完成OrderUserInfo表关联后,UserInfo集合的数据被嵌套在了userInfo属性下,然后使用userInfo.type访问UserInfotype属性,并根据其内容进行排序。

    @Override
    public List<OrderVo> queryOrderAndUserInfoSort(Sort.Direction direction) {
        Criteria criteria = new Criteria();
        MatchOperation match = Aggregation.match(criteria);
        // 和另外一个集合进行关联,将其作为其属性
        LookupOperation lookup = Aggregation.lookup("UserInfo",
            "userId",// 查询表中字段
            "_id",// 连接表中字段
            "userInfo");// 返回数据所在的属性名称
        SortOperation sort =
            Aggregation.sort(new Sort(direction,"userInfo.type"));
        TypedAggregation<Order> noRepeatAggregation2 =
            Aggregation.newAggregation(Order.class,match,lookup,sort);

        AggregationResults<OrderVo> noRepeatDataInfoVos2 = mongoTemplate.aggregate(noRepeatAggregation2, OrderVo.class);
        List<OrderVo> noRepeatDataList2 = noRepeatDataInfoVos2.getMappedResults();
        return noRepeatDataList2;
    }

嵌套结构的展开

对于存在一对多关系的连接时,被连接的数据会被放在一个集合中,这个时候可以使用Aggregation.unwind将数据展开。上面的例子添加下面的语句就是讲UserInfos字段内容展开

Aggregation.unwind("UserInfos")
  • 展开前的结构
{
    "field":"value",
    "UserInfos" : [{"field":"value1"},{"field":"value2"}]
}
  • 展开后的结构
{
    "field":"value",
    "UserInfos" : {"field":"value1"}
}
{
    "field":"value",
    "UserInfos" : {"field":"value2"}
}

ps. 需要注意代码中我并没有实现其测试代码。假如需要测试其展开效果,需要重新定义其返回结构对象。

联表查询-调整被联结数据的名称

有些时候我们使用多表关联的时候使用的是单独一层数据的对象来接收数据,在代码层面上嵌套的数据结构可能并不是太方便我们使用。这个时候我们可以配合上篇介绍的Aggregation.project来改变其数据结构。上一篇中我介绍了其修改数据名称能使用,下面就是其修改数据结构的方式。下面例子中将userInfo.userName指向到userName中,这样操作后userName作为一个嵌套数据的属性在修改后会作为数据根节点展示出来。

    @Override
    public List<OrderAndUserVo> queryOrderUserInfo() {
        Criteria criteria = new Criteria();
        MatchOperation match = Aggregation.match(criteria);
        // 和另外一个集合进行关联,将其作为其属性
        LookupOperation lookup = Aggregation.lookup("UserInfo",// 连接表
            "userId",// 查询表中字段
            "_id",// 连接表中字段
            "userInfo");// 返回数据所在的属性名称
        Field userName = Fields.field("userName", "userInfo.userName");
        ProjectionOperation project = Aggregation.project("id","totalMoney","totalProduct","userId","type")
            .andInclude(Fields.from(userName));
        TypedAggregation<Order> noRepeatAggregation2 =
            Aggregation.newAggregation(Order.class,match,lookup,project);

        AggregationResults<OrderAndUserVo> noRepeatDataInfoVos2 = mongoTemplate.aggregate(noRepeatAggregation2, OrderAndUserVo.class);
        List<OrderAndUserVo> noRepeatDataList2 = noRepeatDataInfoVos2.getMappedResults();
        return noRepeatDataList2;
    }

分组去重

我们使用Aggregation.group可以实现数据的分组,但是有些时候我们想知道在存在多种重复数据的时候不同查询条件下重复数据去重后的内容。

比如我们将不同顾客(user)的订单(order)分为不同的类型(type),现在我们想知道不同顾客到底存在几种不同的类型时,通过将"userId"和"type"进行分组我们只能拿到不同用户不同订单类型的聚合结果。当然我们大可以在代码中循环其结果得到不同用户(user)到底包含几种(type)的数量。但这就导致逻辑被拆分到了数据查询和程序代码中。

之前介绍过聚合管道其实是一个管道,后一个管道处理的数据是经过之前管道处理后的结果。要解决分组去重还是要使用其管道的特性。比如上面描述中我们通过"userId"和"type"进行分得到的结果是下面

userId type count
1 1 此userId此type包含数据量
1 2 此userId此type包含数据量
2 1 此userId此type包含数据量
2 2 此userId此type包含数据量
2 3 此userId此type包含数据量

此时我们是无法知道每个userId到底存在多少个不同的type。但是我们后续的管道可以对上面的数据进行再次分组。

我们再次分组,此时使用userId进行数据的分组,最后拿到的count就是根据type值的总和,此时拿到的结果如下图就是最终的结果。

userId count
1 此userId包含的type数量
2 此userId包含的type数量

代码

上面的一套逻辑使用代码就是下面的内容。

    @Override
    public List<GroupVo> getAggGroupDeDuplication() {
        // 根据用户id和type进行分组
        GroupOperation operation1 = Aggregation.group( "userId","type");
        // 对上面的结果再次分组可以获得userId的type数据
        GroupOperation operation2 = Aggregation.group("userId").count().as("count");

        TypedAggregation<Order> noRepeatAggregation =
            Aggregation.newAggregation(Order.class,operation1,operation2);
        AggregationResults<GroupVo> noRepeatDataInfoVos = mongoTemplate.aggregate(noRepeatAggregation, GroupVo.class);
        List<GroupVo> noRepeatDataList = noRepeatDataInfoVos.getMappedResults();
        System.out.println(JSON.toJSONString(noRepeatDataList));
        return noRepeatDataList;
    }

个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容,也希望大家看在这个新春佳节只能宅到家中埋头苦逼的码代码的情况下,能给我点一个赞。你的点赞就是我前进的动力。在这里也祝大家新春快乐。

Spring Boot 整合——MongoDB整合4(MongoDB分组去重以及MongoDB联表查询)Spring Boot 整合——MongoDB整合4(MongoDB分组去重以及MongoDB联表查询) 大·风 发布了204 篇原创文章 · 获赞 15 · 访问量 1万+ 私信 关注
上一篇:Spring5源码分析(015)——IoC篇之解析bean标签:meta、lookup-method、replaced-method


下一篇:C#-EF6:将参考/查找数据与IQueryable一起使用