摘要
Mongo支持与Mysql类似的关联表查询,但比较麻烦。
通过与Mysql实现进行对比,便于理解Mongo如何实现。
Mysql
Order表关联Product表的id, 实现查询订单支持搜索产品名称。
# Order
CREATE TABLE `order` (
`id` bigint(20) DEFAULT NULL,
`no` varchar(255) DEFAULT NULL,
`amount` int(11) DEFAULT NULL COMMENT '单位- 分\n',
`productId` bigint(20) DEFAULT NULL,
`count` int(11) DEFAULT NULL,
`userId` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `order` (`id`, `no`, `amount`, `productId`, `count`, `userId`) VALUES (1, '2024062701', 10000, 1, 1, 1);
INSERT INTO `order` (`id`, `no`, `amount`, `productId`, `count`, `userId`) VALUES (1, '2024062702', 20000, 1, 2, 1);
# Product
CREATE TABLE `product` (
`id` bigint(20) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`price` int(11) DEFAULT NULL COMMENT '单位-分'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` (`id`, `name`, `price`) VALUES (1, '测试产品', 10000);
关联查询
查询订单、产品名称,通过left join
搜索产品名称
SELECT o,p.name from `order` as o left JOIN `product` as p on o.productId = p.id where p.name like '%测试%';
## 结果
#id no amount productId count userId name
1 2024062701 10000 1 1 1 测试产品
1 2024062702 20000 1 2 1 测试产品
Mongo
同样在mongo中准备Order和Product的集合
## insert to product
db.product.insertOne({
name: '测试产品',
price: 10000
});
## result
{
"_id" : ObjectId("667d7fbe04cfea555206351f"),
"name" : "测试产品",
"price" : 10000
}
## insert to orders
db.order.insertMany([
{
no: '2024062701',
amount: 10000,
productId: "667d7fbe04cfea555206351f",
count: 1,
userId: 1
},
{
no: '2024062702',
amount: 20000,
productId: "667d7fbe04cfea555206351f",
count: 2,
userId: 1
}
]);
# result
{
"_id" : ObjectId("667d80b804cfea5552063520"),
"no" : "2024062701",
"amount" : 10000,
"productId" : "667d7fbe04cfea555206351f",
"count" : 1,
"userId" : 1
}
{
"_id" : ObjectId("667d80b804cfea5552063521"),
"no" : "2024062702",
"amount" : 20000,
"productId" : "667d7fbe04cfea555206351f",
"count" : 2,
"userId" : 1
}
关联查询
使用Mongo提供的$lookup
来实现关联查询
{
"$lookup": {
"from": {
"connectionName": "<registered-atlas-connection>",
"db": "<registered-database-name>",
"coll": "<atlas-collection-name>"
},
"localField": "<field-in-source-messages>",
"foreignField": "<field-in-from-collection>",
"as": "<output-array-field>"
}
}
那么我们使用Mongo查询,应该这样写
db.order.aggregate(
{
$lookup: {
from: "product", # 需要关联的product表
localField: "proudctId", # order中的productId
foreignField: '_id', # 与proudct中的_id关联
as: "p" # product的别名,出现在查询结果中,默认是一个数组
}
},
{ $unwind: "$p" }, # 将p拉平,如果p是多个,那么会出现多条记录
# 此时得到 order + p:{}
{
$match: {
"p.name": { # 过滤p.name
$regex: ".*测试.*", # 使用正则,模糊查询= '%测试%'
$options: "i" # 忽略大小写
}
}
})
但是,没有查询到结果!?
因为 在order
中productId
是String,而product._id
是ObjectId,他俩不是同一个类型,所以是不等的。
解决
他俩类型不一样,那么把他俩类型弄成一样再比较!
db.order.aggregate(
{
$lookup: {
from: "product",
let: { pid: "$productId" }, ## 取order.productId到变量pid中
pipeline: [ ## 在product表操作,将pid变成ObjectId后与order._id进行等于比较
{
$match: {
$expr: {
$eq: ["$_id", { $toObjectId: "$$pid" }]
}
}
}
],
as: "p"
}
},
{ $unwind: "$p" },
{
$match: {
"p.name": {
$regex: ".*测试.*",
$options: "i"
}
}
})
## Result
// collection: order
{
"_id" : ObjectId("667d80b804cfea5552063520"),
"no" : "2024062701",
"amount" : 10000,
"productId" : "667d7fbe04cfea555206351f",
"count" : 1,
"userId" : 1,
"p" : {
"_id" : ObjectId("667d7fbe04cfea555206351f"),
"name" : "测试产品",
"price" : 10000
}
}
// collection: order
{
"_id" : ObjectId("667d80b804cfea5552063521"),
"no" : "2024062702",
"amount" : 20000,
"productId" : "667d7fbe04cfea555206351f",
"count" : 2,
"userId" : 1,
"p" : {
"_id" : ObjectId("667d7fbe04cfea555206351f"),
"name" : "测试产品",
"price" : 10000
}
}
lookup 解释
1、将order.productId声明一个新的变量:pid
2、将order._id与 pid比较,此时的pid转换成了obejctId
AI 解释
这段代码是MongoDB聚合管道($aggregate)中的一部分,使用了$lookup阶段来实现集合之间的关联查询。这里是将当前集合(假设为订单或其他与产品相关的集合)与"product"集合进行关联,以获取与每个文档中productId字段对应的产品详细信息。我将逐步解释每一部分的含义:
$lookup: 这是MongoDB聚合管道的一个操作符,用于在当前集合的文档中连接来自另一个集合的文档。它基于指定的键执行类似SQL中的左外连接。
- from: "product": 指定要连接的另一个集合名为"product"。这是你想要获取额外数据的集合。
- let: { pid: "$productId" }: 定义了一个变量pid,其值为当前文档中的productId字段。这在后续的pipeline中作为查找依据。
- pipeline: 这是一个数组,包含了在"product"集合上执行的聚合管道操作。这里仅有一个操作:
- $match: 用于过滤"product"集合中的文档。它只选择那些满足特定条件的文档。
- $expr: 允许在$match中使用聚合表达式。
- $eq: 检查两个表达式是否相等。在这个例子中,检查"product"集合文档的_id字段是否等于通过$toObjectId转换后的$$pid(即来自外部文档的productId,已经转换为ObjectId类型)。
- as: "p": 指定将连接结果存储在当前文档的新字段p中,作为数组形式存在,包含匹配到的所有"product"集合的文档。
Spring Data Mongo Tempalte 实现
Pageable pageable= "Spring data Pageable';
String name= "测试"
// Template不支持let,所以自己实现
AggregationOperation l = context -> new Document("$lookup",
new Document("from", "product")
.append("let", new Document("pid", "$projectId"))
.append("pipeline", Arrays.asList(
new Document("$match",
new Document("$expr",
new Document("$eq", Arrays.asList("$_id", new Document("$toObjectId", "$$pid"))))
)
))
.append("as", "p"));
UnwindOperation unwindOperation = Aggregation.unwind("p");
Criteria c = new Criteria();
if (StringUtils.isNotBlank(name)) {
c.and("p.name").regex(".*" + name + ".*", "i");
}
Aggregation aggregation = Aggregation.newAggregation(
l, unwindOperation,
Aggregation.match(c),
Aggregation.count().as("c")
);
Document count = MongoUtils.template(dbName).aggregate(aggregation, Order.class, Document.class)
.getUniqueMappedResult();
// 没有搜索到结果,这里的count会是null
if (count == null) {
return PageResult.empty();
}
Aggregation pageAggregation = Aggregation.newAggregation(
// 这有顺序关联: 先跳过,在limit
l, unwindOperation,
Aggregation.match(c),
Aggregation.skip(pageable.getOffset()),
Aggregation.limit(pageable.getPageSize())
);
List<Order> data = MongoUtils.template(dbName).aggregate(pageAggregation, Order.class, Order.class)
.getMappedResults();
return Page.of(count.getInteger("c"), data);
评论 (0)