MongoTemplate聚合(一)$lookup
mongodb
最近入职了新的公司,新公司统一使用的mongodb,es等非关系型数据库。以前对es有一些了解,其实就是灵活的文档类型结构,不受限于关系型数据库的那种字段唯一确定的”死板“,但是无论是关系型数据库还是非关系型数据库,目前使用了一段时间来说,我认为各有优劣,具体选择要结合业务场景来进行选择。
有关mongo的快速学习文档可以参照以下资料来学习:
聚合查询
在我这种习惯了mysql这种关系型数据的结构设计中,来处理mongo集合(数据表)的一些操作票,或多或少还是受到关系型数据库思想的影响与约束,毕竟还是使用了这么多年了。。。
比如在下面这种场景下:A对象集合与B对象集合之间有关联关系,此时,针对于上级关系修改操作较少的可以将他们之间的关系映射成嵌入式的子文档,但他们的数据都在经常性的发生相互变化,这种情况很显然不能将数据作为嵌入式文档保存,应该要实时的查询关联的数据。
mongo中早期的一些版本又没有left join,right join的概念,后来在3.2版本开始,增加了$lookup操作。
在介绍$lookup前简单了解一下mongo中的一些聚合管道操作:
| 聚合指令 | 功能描述 |
|---|---|
| $match | 筛选,选择要处理的文档 |
| $project | 指定输出文档中的字段,映射别名等 |
| $group | 顾名思义,分组 根据指定内容来分组 |
| $limit | 限制传递到下一步的文档数量 |
| $skip | 跳过当前顺序的一定数量的文档 |
| $unwind | 扩展数组,为每一个3数组入口生成一个输出文档 |
| $sort | 文档排序 |
| $lookup | 多表关联(since 3.2+) |
| $geoNear | 选择某个地理位置附近的的文档 |
| $out | 把管道的结果写入某个集合 |
| $redact | 控制特定数据的访问 |
上面的指定都属于聚合管道中的操作,(官方解释)聚合管道是用于数据聚合的框架,其模型基于数据处理管道的概念。文档进入多阶段管道,将文档转换为聚合结果。MongoDB 聚合管道由多个阶段组成。每个阶段在文档通过管道时转换文档。管道阶段不需要为每个输入文档生成一个输出文档; 如:某些阶段可能会生成新文档或过滤掉文档。后边有时间写一篇文章来记录一下聚合管道。
特别说明 - 局限性
mongodb的官方文档说明:$lookup: Performs a left outer join to an unsharded collection in the same database to filter in documents from the “joined” collection for processing. The $lookup stage does an equality match between a field from the input documents with a field from the documents of the “joined” collection.
简单点说就是: $lookup只能在同一个数据库中, 且这个collection不能有分片. 如果你的集合设计不在一个库中, 且设置了分片的话, 那下面的连表操作都是无效的,请不用浪费时间浏览了。
$lookup关联
$lookup是管道中的一个阶段,在这个阶段,可以做连表操作,具有如下语法:
{
$lookup:
{
from: <collection to join 需要左连接的集合>,
localField: <主集合中与该左连集合关联的字段>,
foreignField: <左连集合中对应的字段>,
as: <output array field 指定新输入数组字段的名称:该处会处理成数组>
}
}
该操作等效于如下sql释义:
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN
(SELECT * FROM <collection to join>
WHERE <foreignField>= <collection.localField>);
mongoTemplate中使用
说了那么多,是想介绍一下简单的概念,言归正传,开始讲在springboot中使用mongoTemplate中该如何使用。
假设当前有两个集合,一个company,一个product,产品隶属于公司下,他们之间存在关联关系。
1. CompanyMongoPO.java
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Accessors(chain = true)
@Document("company")
public class CompanyMongoPO {
@Id
private String id;
private String name;
}
2. productMongoPO.java
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Accessors(chain = true)
@Document("product")
public class ProductMongoPO {
@Id
private String id;
private String name;
private String companyId;
}
3. 在Dao层进行查询操作:
......
Criteria criteria = new Criteria();
criteria.and("companyId").is("companyId");
// 构造聚合管道操作
List<AggregationOperation> operationList = Lists.newArrayList();
// 这一步很重要,将product中的companyId字段转化为ObjectId类型,因为String类型和ObjectId类型不一样,会导致连接失效
AddFieldsOperation addFieldsOperation = AddFieldsOperation.addField("companyId").withValue(ConvertOperators.ToObjectId.toObjectId("$companyId")).build();
LookupOperation companyLookupOperation = LookupOperation.newLookup()
.from("company")
.localField("companyId")
.foreignField("_id")
.as("companyJoin");
AggregationOperation match = Aggregation.match(criteria);
ProjectionOperation project = Aggregation.project("id","name","companyId")
.and("companyJoin.name").as("companyName");
// 分页与排序操作,字段未在上面体现出来
SkipOperation skip = Aggregation.skip((long)param.getOffset());
LimitOperation limit = Aggregation.limit(param.getPageSize());
SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "createTime");
// 封装条件:此处的顺序可以调整 将match放到前面可以避免因为AddFiled引起的companyId字段类型变化
operationList.add(match);
operationList.add(addFieldsOperation);
operationList.add(companyLookupOperation);
operationList.add(project);
operationList.add(sort);
operationList.add(skip);
operationList.add(limit);
Aggregation agg = Aggregation.newAggregation(operationList);
AggregationResults<ProductItemVO> aggregationResults = this.getMongoTemplate().aggregate(agg, "product", ProductItemVO.class);
List<ProductItemVO> dataList = aggregationResults.getMappedResults();
......
如上伪代码,就是一个简单的聚合操作,当然有几个地方需要注意一下:
连表时的字段类型要一致:比如上面的companyId和id字段,一个String,一个是ObjectId类型,需要将companyId转化为ObjectId类型,再进行连接,当然这个地方我理解也可以将_id转化为String类型,但是经过我的测试,发现没有成功,还需要找下原因。
在将companyId转化为ObjectId类型后,如果后面有使用到companyId作为match的筛选条件字段,这个地方要注意一下,在聚合管道中,有一定的顺序性,如果将AddFieldsOperation操作放在match之前,那么会导致match这个字段的条件失效,需要调整一下顺序,将match放在前面,先查找出符合条件的数据再进行连表查询,这样既可以提高查询效率,又可以避免字段类型问题。
总结
目前用到的就是这样的操作,往后还有更复杂的操作时再继续更新记录内容。
MongoTemplate聚合(一)$lookup的更多相关文章
- MongoTemplate聚合操作
Aggregation简单来说,就是提供数据统计.分析.分类的方法,这与mapreduce有异曲同工之处,只不过mongodb做了更多的封装与优化,让数据操作更加便捷和易用.Aggregation操作 ...
- mongoTemplate聚合操作Demo
package com.tangzhe.mongodb.mongotemplate; import com.mongodb.BasicDBObject; import com.mongodb.DBOb ...
- mongodb mongotemplate聚合
1.group by并且计算总数 @Test public void insertTest() { //测试数据 //insertTestData(); Aggregation agg = Aggre ...
- 基于 MongoDB 动态字段设计的探索 (二) 聚合操作
业务需求及设计见前文:基于 MongoDB 动态字段设计的探索 根据专业计算各科平均分 (总分.最高分.最低分) public Object avg(String major){ Aggregatio ...
- MongoDB学习记录
一.操作符 "$lt" :"<""$lte" :"<=""$gt" :"> ...
- SpringBoot整合MongoDB JPA,测试MongoRepository与MongoTemplate用法,简单增删改查+高级聚合
源码 地址 -> https://github.com/TaoPanfeng/case/tree/master/04-mongo/springboot-mongo 一 引入依赖 <depe ...
- 开发中使用mongoTemplate进行Aggregation聚合查询
笔记:使用mongo聚合查询(一开始根本没接触过mongo,一点一点慢慢的查资料完成了工作需求) 需求:在订单表中,根据buyerNick分组,统计每个buyerNick的电话.地址.支付总金额以及总 ...
- mongoTemplate.aggregate()聚合查询
一.概述 1. 聚合的表达式 MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果.有点类似sql语句中的 count(*). 下表展示了一些聚 ...
- 【翻译】MongoDB指南/聚合——聚合管道
[原文地址]https://docs.mongodb.com/manual/ 聚合 聚合操作处理数据记录并返回计算后的结果.聚合操作将多个文档分组,并能对已分组的数据执行一系列操作而返回单一结果.Mo ...
随机推荐
- [leetcode]297. Serialize and Deserialize Binary Tree一般二叉树的编解码
由于一般的前序遍历不能唯一的还原出原本你的二叉树,所以要改变一下: 记录二叉树的结构信息,也就是空节点用符号表示 一般的前序遍历只是记录了节点的前后顺序,通过记录空节点,每一层的结构就可以记录下来 解 ...
- JUC包-原子类(AtomicInteger为例)
目录 JUC包-原子类 为什么需要JUC包中的原子类 原子类原理(AtomicInteger为例) volatile CAS CAS的缺点 ABA问题 什么是ABA问题 ABA问题的解决办法 JUC包 ...
- 1001 害死人不偿命的(3n+1)猜想 (15分)
卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把 (3n+1) 砍掉一半.这样一直反复砍下去,最后一定在某一步得到 n=1.卡拉兹在 1950 ...
- Unraid修改docker镜像地址&默认启动
起源 由于Unraid系统每次启动都会清空Docker的镜像地址配置,故需要默认配置镜像地址 方法 添加修改镜像脚本到开机文件中实现 先找一个镜像加速地址,我使用的是阿里云的容器镜像服务 形如 :ht ...
- 辅助调用函数【call,apply,bind】
函数也是对象,每个函数都有自己的方法. e.g. var jane = { name:'Jane', sayHelloTo:function(name) { 'use strict'; console ...
- 基于LDAP&&Role-based Authorization Strategy实现Jenkins团队权限管理
在实际工作中,存在多个团队都需要Jenkins来实现持续交付,但是又希望不同团队之间进行隔离,每个项目有自己的view, 只能看到自己项目的jenkins job. 但是,jenkins默认的权限管理 ...
- 数组的方法some和includes
some() 方法用于检测数组中的元素是否满足指定条件(函数提供). some() 方法会依次执行数组的每个元素: 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测. 如果没 ...
- 【JavaWeb】AJAX 请求
AJAX 请求 什么是 AJAX AJAX(Asynchronous JavaScript And XMl),即异步 JS 和 XML.是指一种创建交互式网页应用的网页开发技术. AJAX 是一种浏览 ...
- LeetCode700 二叉搜索树中搜索
给定二叉搜索树(BST)的根节点和一个值. 你需要在BST中找到节点值等于给定值的节点. 返回以该节点为根的子树. 如果节点不存在,则返回 NULL. 例如, 给定二叉搜索树: 4 / \ 2 7 / ...
- 阿里面试官:什么是MySQL索引,为什么要有索引?
一.什么是索引? 索引就好比字典的目录一样 我们通常都会先去目录查找关键偏旁或者字母再去查找 要比直接翻查字典查询要快很多 二.为什么要有索引? 然而我们在使用mysql数据库的时候也像字典一样有索引 ...