Eggjs笔记:详解Mongoose的聚合管道以及populate实现关联查询

基于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对应的信息,当然数据结构还可以优化下,此处仅作举例!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值