基于mongodb的聚合管道
此处忽略之前mongodb在eggjs中需要配置的步骤,前文已有说明,现在开始在controller中调用(应该封装到service中的,仅作为示例)
const result = await this.app.mongo.aggregate('order', {
pipeline: [
{
$lookup:
{
from: 'order_item',
localField: 'order_id',
foreignField: 'order_id',
as: 'items',
},
},
{
$match: { all_price: { $gte: 90 } },
},
{
$limit: 2,
},
],
options: {},
});
ctx.body = result;
基于mongoose的聚合管道
1 ) 首先封装必要的mongoose连接数据库模块
app/model/db.js
'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://eggadmin:123456@localhost:27017/eggcms', { useNewUrlParser: true }, function(err) {
if (err) {
console.log(err);
return;
}
console.log('conc suc');
});
module.exports = mongoose;
2 ) 通过Schema建立两张表的数据模型
order表的数据模型
// 配置schema和数据模型
'use strict';
const mongoose = require('./db.js');
const OrderSchema = new mongoose.Schema({
order_id: String,
uid: Number,
trade_no: String,
all_price: Number,
all_num: Number,
});
module.exports = mongoose.model('Order', OrderSchema, 'order');
order_item表的数据模型
// 配置schema和数据模型
'use strict';
const mongoose = require('./db.js');
const OrderItemSchema = new mongoose.Schema({
order_id: String,
title: String,
price: Number,
num: Number,
});
module.exports = mongoose.model('OrderItem', OrderItemSchema, 'order_item');
3 ) 在控制器中进行调用
3.1 示例:通过mongoose定义的Model来直接获取order表中的数据
'use strict';
const Controller = require('egg').Controller;
class OrderController extends Controller {
async index() {
const OrderModel = require('../model/order');
OrderModel.find({}, (err, docs) => {
if (err) {
console.log('err: ' + err);
return;
}
// console.log(docs);
});
}
}
module.exports = OrderController;
3.2 示例:通过mongoose的聚合函数来多张表联查数据
'use strict';
const Controller = require('egg').Controller;
class OrderController extends Controller {
async index() {
const OrderModel = require('../model/order');
OrderModel.aggregate([
{
$lookup:
{
from: 'order_item',
localField: 'order_id',
foreignField: 'order_id',
as: 'items',
},
},
{
$match:
{
all_price: { $gte: 100 },
},
},
], (err, docs) => {
if (err) {
console.log('err: ' + err);
return;
}
// console.log(docs);
console.log(JSON.stringify(docs));
});
}
}
module.exports = OrderController;
3.3 示例:查询order_item,找出商品名称是酸奶的商品,酸奶这个商品对应的订单的订单号以及订单的总价格 比较原始的用法,较为麻烦
'use strict';
const Controller = require('egg').Controller;
class OrderController extends Controller {
async index() {
const OrderModel = require('../model/order');
const OrderItemModel = require('../model/order_item.js');
// 此处是硬编码,从数据库中捞出来的一条酸奶id 仅作为演示
OrderItemModel.find({ _id: '5e81721f7835433a892342b5' }, (err, docs) => {
if (err) {
console.log('err: ' + err);
return;
}
// console.log(docs);
const order_id = docs[0].order_id;
const order_item = JSON.parse(JSON.stringify(docs)); // 处理数据 查出来的数据应该是只读的
OrderModel.find({ order_id }, (err, order) => {
if (err) {
console.log('err: ' + err);
return;
}
order_item[0].order_info = order[0];
console.log(order_item);
});
});
}
}
module.exports = OrderController;
3.4 示例:通过mongoose聚合函数实现多表查询: 找出商品名称是酸奶的商品,酸奶这个商品对应的订单的订单号以及订单的总价格
'use strict';
const Controller = require('egg').Controller;
class OrderController extends Controller {
async index() {
const OrderItemModel = require('../model/order_item.js');
const mongoose = require('mongoose');
OrderItemModel.aggregate([
{
$lookup:
{
from: 'order',
localField: 'order_id',
foreignField: 'order_id',
as: 'order_info',
},
},
{
$match:
{
_id: mongoose.Types.ObjectId('5e81721f7835433a892342b5'), // 此处是硬编码(演示), mongoose.Types.ObjectId在mongoose中获取ObjectId的方法
},
},
], (err, docs) => {
if (err) {
console.log(err);
return;
}
console.log(docs);
});
}
}
}
module.exports = OrderController;
mongoose使用聚合管道实现多个表的关联查询
- 这里现在有这样一个数据库表结构,文章分类表、文章表以及用户表,这三个表的关系是这样的
- 当用户增加文章的时候,首先要在这个文章里面保存文章所属的分类(就是首先在文章里关联分类)
- 其次的话,当我们的用户增加文章的时候,还要在这个文章表里面显示到底是哪个用户增加了这个文章(文章表还要关联用户表)
- 其他人访问文章的时候,显示文章的分类和用户信息
- 这样就涉及了三个表的关联查询

现在要先新建三个表的数据模型
1 ) user表
app/model/user.js
// 配置schema和数据模型
'use strict';
const mongoose = require('./db.js');
const UserSchema = new mongoose.Schema({
username: { type: String, unique: true },
password: String,
age: Number,
sex: String,
tel: Number,
status: {
type: Number,
default: 1,
},
});
module.exports = mongoose.model('User', UserSchema, 'user');
2 ) article表
app/model/article.js
'use strict';
const mongoose = require('./db.js');
const ArticleSchema = new mongoose.Schema({
// 一般id字段不用写,就是_id
title: {
type: String,
unique: true,
},
// cid是文章所属分类,固定写法,和String类型类似
cid: {
type: mongoose.Schema.Types.ObjectId
},
author_id: {
type: mongoose.Schema.Types.ObjectId
},
author_name: { type: String },
description: String,
content: String,
});
module.exports = mongoose.model('Article', ArticleSchema, 'article');
3 ) articlecate表
app/model/articlecate.js
'use strict';
const mongoose = require('./db.js');
const ArticleCateSchema = new mongoose.Schema({
// 一般id字段不用写,就是_id
title: {
type: String,
unique: true,
},
description: String,
addtime: {
type: Date,
},
});
module.exports = mongoose.model('ArticleCate', ArticleCateSchema, 'articlecate');
初始化user表和articlecate两张表的模拟数据数据
插入模拟数据
db.articlecate.insert({"title":"国际新闻","description":"国际新闻"})
db.articlecate.insert({"title":"国内新闻","description":"国内新闻"})
db.user.insert({"username":"zhangsan","password":"sss", "name":"张三", "age": 10, "sex": 'man', "tel": 12222})
db.user.insert({"username":"lisi","password":"sss", "name":"李四", "age": 20, "sex": 'man', "tel": 32222})
测试查询是否添加成功
# articlecate表
> db.articlecate.find()
{ "_id" : ObjectId("5e8424f44bea3e5563b2f37b"), "title" : "国际新闻", "description" : "国际新闻" }
{ "_id" : ObjectId("5e8424f44bea3e5563b2f37c"), "title" : "国内新闻", "description" : "国内新闻" }
# user表
db.user.find()
{ "_id" : ObjectId("5e8424f44bea3e5563b2f37d"), "username" : "zhangsan", "password" : "sss", "name" : "张三", "age" : 10, "sex" : "man", "tel" : 12222 }
{ "_id" : ObjectId("5e8424f54bea3e5563b2f37e"), "username" : "lisi", "password" : "sss", "name" : "李四", "age" : 20, "sex" : "man", "tel" : 32222 }
开始增加文章信息
- 目前主要来测试这一块的代码操作,来测试多表关联插入
- 新建一个/addarticle路由 $
router.get('/articleadd', controller.article.add);
用于访问后插入数据(模拟的方式来测试代码) - 在控制器 app/controller/article中编写add方法
// 一般模块在最上面引入 const ArticleModel = require('../model/article'); async add() { // 第一篇文章 const article = new ArticleModel(); article.title = '特朗普美国疫情'; article.cid = '5e8424f44bea3e5563b2f37b'; // 这里是从数据库捞出来的硬编码的国际新闻id article.author_id = '5e8424f44bea3e5563b2f37d'; // 同上,张三id 这些增加的时候是字符串,但是会转换成ObjectId article.author_name = '张三'; article.description = '美国疫情告急'; article.content = '美国疫情告急美国疫情告急'; article.save(); // 第二篇文章 const article1 = new ArticleModel(); article1.title = '武汉疫情离解封倒计时'; article1.cid = '5e8424f44bea3e5563b2f37c'; // 这里是从数据库捞出来的硬编码的国内新闻id article1.author_id = '5e8424f54bea3e5563b2f37e'; // 同上,李四id 这些增加的时候是字符串,但是会转换成ObjectId article1.author_name = '李四'; article1.description = '武汉解封倒计时'; article1.content = '武汉解封倒计时武汉解封倒计时'; article1.save(); }
- 访问该路由:
/articleadd
- 备注:这里只能访问一次,否则会报错,我们对文章做了unique的索引处理,我们只是测试数据,不做其他考虑
- 查看数据库,是否被正确插入数据
> db.article.find() { "_id" : ObjectId("5e842b3685db822147944b01"), "title" : "特朗普美国疫情", "cid" : ObjectId("5e8424f44bea3e5563b2f37b"), "author_id" : ObjectId("5e8424f44bea3e5563b2f37d"), "author_name" : "张三", "description" : "美国疫情告急", "content" : "美国疫情告急美国疫情告急", "__v" : 0 } { "_id" : ObjectId("5e842b3685db822147944b02"), "title" : "武汉疫情离解封倒计时", "cid" : ObjectId("5e8424f44bea3e5563b2f37c"), "author_id" : ObjectId("5e8424f54bea3e5563b2f37e"), "author_name" : "李四", "description" : "武汉解封倒计时", "content" : "武汉解封倒计时武汉解封倒计时", "__v" : 0 }
- 可见已经被正确插入,张三写的是国际新闻,李四写的是国内新闻
查询文章信息
- 新建文章路由: $
router.get('/article', controller.article.index);
- 编写app/controller/article中的index方法
async index() { /* 测试1: 这里是最简单的查询 ArticleModel.find({}, (err, docs) => { if (err) { console.log('err: ', err); return; } console.log(docs); }); */ // 测试2:查询文章信息并要关联分类和用户, 三张表关联 ArticleModel.aggregate([ { $lookup: { from: 'articlecate', localField: 'cid', foreignField: '_id', as: 'cate', }, }, { $lookup: { from: 'user', localField: 'author_id', foreignField: '_id', as: 'user', }, }, ], (err, docs) => { if (err) { console.log('err: ', err); return; } console.log(JSON.stringify(docs)); }); }
- 访问:/article ,查看控制台输出数据
[{"_id":"5e842b3685db822147944b01","title":"特朗普美国疫情","cid":"5e8424f44bea3e5563b2f37b","author_id":"5e8424f44bea3e5563b2f37d","author_name":"张三","description":"美国疫情告急","content":"美国疫情告急美国疫情告急","__v":0,"cate":[{"_id":"5e8424f44bea3e5563b2f37b","title":"国际新闻","description":"国际新闻"}],"user":[{"_id":"5e8424f44bea3e5563b2f37d","username":"zhangsan","password":"sss","name":"张三","age":10,"sex":"man","tel":12222}]},{"_id":"5e842b3685db822147944b02","title":"武汉疫情离解封倒计时","cid":"5e8424f44bea3e5563b2f37c","author_id":"5e8424f54bea3e5563b2f37e","author_name":"李四","description":"武汉解封倒计时","content":"武汉解封倒计时武汉解封倒计时","__v":0,"cate":[{"_id":"5e8424f44bea3e5563b2f37c","title":"国内新闻","description":"国内新闻"}],"user":[{"_id":"5e8424f54bea3e5563b2f37e","username":"lisi","password":"sss","name":"李四","age":20,"sex":"man","tel":32222}]}]
- 格式化后可以看到,张三的国际新闻文章,李四的国内新闻文章,任务完成
Mongoose中的popluate
- 官方建议还是用
$lookup
聚合管道来实现多表查询,但是这个要求mongodb的版本要>= 3.2 popluate()
函数同样可以实现多表的查询- 文档:https://mongoosejs.com/docs/populate.html
Mongoose populate 关联查询
1 ) 三表关联

- 还是用这张图来说明,具体参考上一篇文章
- 在article中,主键是id,关联articlecate的外键是cid, 关联user的外键是author_id
2 ) 定义 ref
ref 表示关联的意思, 在 app/model/article.js中改写如下
const ArticleSchema = new Schema({
title: {
type: String,
unique: true
},
/*分类 id*/
cid : {
type: Schema.Types.ObjectId,
ref: 'ArticleCate' // model 的名称
},
/*用户的 id*/
author_id:{
type: Schema.Types.ObjectId,
ref: 'User'
},
author_name:{
type: String
},
descripton: String,
content: String
});
3 ) 关联查询
在app/controller/article.js中的index方法改写如下
// 注意使用 populate需要引入用到的model
require('../model/articlecate.js');
require('../model/user.js');
ArticleModel.find({}).populate('cid').populate('author_id')
.exec(function(err, docs) {
if (err) {
console.log('err: ' + err);
return;
}
console.log(JSON.stringify(docs));
});
输出结果如下:
[{"_id":"5e842b3685db822147944b01","title":"特朗普美国疫情","cid":{"_id":"5e8424f44bea3e5563b2f37b","title":"国际新闻","description":"国际新闻"},"author_id":{"status":1,"_id":"5e8424f44bea3e5563b2f37d","username":"zhangsan","password":"sss","name":"张三","age":10,"sex":"man","tel":12222},"author_name":"张三","description":"美国疫情告急","content":"美国疫情告急美国疫情告急","__v":0},{"_id":"5e842b3685db822147944b02","title":"武汉疫情离解封倒计时","cid":{"_id":"5e8424f44bea3e5563b2f37c","title":"国内新闻","description":"国内新闻"},"author_id":{"status":1,"_id":"5e8424f54bea3e5563b2f37e","username":"lisi","password":"sss","name":"李四","age":20,"sex":"man","tel":32222},"author_name":"李四","description":"武汉解封倒计时","content":"武汉解封倒计时武汉解封倒计时","__v":0}]
可以看到cid下挂载了articlecate对应的信息,author_id下挂载了user对应的信息,当然数据结构还可以优化下,此处仅作举例!