3.13 Mongoose的复杂操作

Mongoose 内部设置表字段的嵌套

互相嵌套

// 对象嵌套
const postSchema = new Schema({
    name: String,
    // 在这里引用的User 定义type  为ObjectId  ref 为User
    postedBy: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, //这里和Model同名
    dateCreated: Date,
    comments: [{body:"string", by: mongoose.Schema.Types.ObjectId}],
});
mongoose.model("Post",postSchema)
// 在User  中
// 数组嵌套
const UserSchema =new Schema({
    name:String,
    userID:String,
    posts:[{   // 在USer 中这样定义Post
        type: Schema.Types.ObjectId, ref: 'Post'  //这里和Model同名
    }]
})

互相关联对应

const postSchema = new Schema({
    name: String,
    userID: {
        type:String
    }, // 于另外一张表User 字段关联
    dateCreated: Date,
    comments: [{body:"string", by: mongoose.Schema.Types.ObjectId}],
});
const UserSchema =new Schema({
    name:String,
    userID:String,// 与另外一张表关联
})

Mongoose 连表操作有两种模式

  1. 如果两表没有主表内部直接存储子表的字段 而是字段的外键模式,则使用aggregate

  2. 如果是子文档 例如存储在主文档的对象或者数组中则使用populate

Mongoose内的管道查询 aggregate

类似于mongoDB的管道查询,管道查询对应夺标查询,应用在两张表没有文档内引用但是有对应关系,schema 对应互相关联对应,例如我有两张表 user 和context 两张表 其中 user和context 是一对多关系。

// 在user中
const mongoose = reuqire('mongoose')
const Schema =mongoose.Schema
const UserSchema = new Schema({
    username:String,
    userID:String
})
// contextSchema
const ContextSchema  = new Schema({
    context:String,
    userID:String
})
// 管道查询
const userModel = reuqire('./user')
userModel.aggregate([{ //这里 进行管道查询
    $lookup:{ //进行 lookup
        form:"context",
        localField:"userID",
        foreignField:"userID",
        as:"items"
    }
}],function(err,doc){
    if(err){
        throw new Error(err)
    }
    console.log(doc)
})

mongoose 的其他管道查询参数类似于mongoDB

联表操作population

联表操作population对应 互相嵌套

以类别category和文章post之间的关联为例

category的model如下所示

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const CategorySchema = new Schema(
  {
    number: 
      { type: Number, 
       required: true, 
       index: true, 
       unique: true, 
       min:[1000000000, '位数不足'],
       max: [9999999999, '位数过长'] },
    name: { 
        type: String,
        required: true, 
        validate: { validator: (v) => v.trim().length, message: '名称不能为空'} },
    description: { type: String },
    posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }],// 子表直接嵌入在数组中
    recommend: { type: Boolean },
    index: { type: Number }
  },
  { timestamps: true }
)

module.exports = mongoose.model('Category', CategorySchema)

post的model

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const PostSchema = new Schema(
  {
    title: { type: String, required: true, unique: true },
    description: { type: String },
    content: { type: String },
    category: { type: Schema.Types.ObjectId, ref: 'Category', index: true },//对象模式
    comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }], //数组模式子文档
    likes: [{ type: Schema.Types.ObjectId, ref: 'Like' }],//数组模式子文档
    imgUrl: { type: String },
    recommend: { type: Boolean },
    index: { type: Number }
  },
  {
    timestamps: true
  }
)

module.exports = mongoose.model('Post', PostSchema)

在对类别的操作中, 都需要使用populate操作符显示出所包括的posts中的title

  /* 加载所有类别 */
  app.get('/categories', (req, res) => {
      // 父 表主查寻  populate 子表的字段 
      // select 限制返回显示的字段
    Category.find().populate('posts','title').select("number name description recommend index").exec((err, docs) => {
      if (err) return res.status(500).json({code: 0, message: err.message, err})
      return res.status(200).json({code: 1, message: '获取类别成功', result: {docs}})
    })
  })

  /* 新增一个类别 */
  app.post('/categories', adminAuth, (req, res) => {
      //创建一个新的父表对象实体
    new Category(req.body).save((err, doc) => {
      if (err) return res.status(500).json({code: 0, message: err.message, err})
        // doc 连接到子表 path 子表的表名 ,select 表明连接的字段
      doc.populate({path:'posts',select:'title'}, (err, doc) => {
        if (err) return res.status(500).json({code:0, message: err.message, err})
        return res.status(200).json({code: 1, message: '新增成功', result: {doc}})
      })      
    })
  })
...

在对文章的操作中,则需要显示出类别category的number属性

  /* 按照id加载一篇文章 */
  app.get('/posts/:id', (req, res) => {
      // 主表post 连接到 category显示number
    Post.findById(req.params.id).populate('category','number').exec((err, doc) => {
      if (err) return res.status(500).json({code:0, message:err.message, err})
      if (doc === null) return res.status(404).json({code:0, message:'文章不存在'})
      return res.status(200).json({code:1, message:'获取文章成功', result:{doc}})
    })
  })

  /* 加载所有文章 */
  app.get('/posts', (req, res) => {
    Post.find().select("title likes comments recommend imgUrl index").populate('category','number').sort("-createdAt").exec((err, docs) => {
      if (err) return res.status(500).json({code: 0, message: err.message, err})
      return res.status(200).json({code: 1, message: '获取文章成功', result: {docs}})
    })

在新增、更新和删除文章的操作中,都需要重建与category的关联

// 关联category的posts数组
fnRelatedCategory = _id => {
  Category.findById(_id).exec((err, categoryDoc) => {
    if (err) return res.status(500).json({ code: 0, message: err.message, err })
    if (categoryDoc === null) return res.status(404).json({code:0, message:'该类别不存在,请刷新后再试'})
    Post.find({ category: _id }).exec((err, postsDocs) => {
      if (err) return res.status(500).json({ code: 0, message: err.message, err })
      categoryDoc.posts = postsDocs.map(t => t._id)
      categoryDoc.save(err => {
        if (err) return res.status(500).json({ code: 0, message: err.message, err })
      })
    })
  })
}

  /* 按照id更新一篇文章 */
  app.put('/posts/:id', adminAuth, (req, res) => {
    Post.findById(req.params.id).exec((err, doc) => {
      if (err) return res.status(500).json({code: 0, message: err.message, err})
      if (doc === null) return res.status(404).json({code: 0, message: '文章不存在,请刷新后再试'})
      for (prop in req.body) {
        doc[prop] = req.body[prop]
      }
      doc.save((err) => {
        if (err) return res.status(500).json({code: 0, message: err.message, err})
        doc.populate({path:'category',select:'number'}, (err, doc) => {
          if (err) return res.status(500).json({code:0, message: err.message, err})
          fnRelatedCategory(doc.category._id)        
          return res.status(200).json({code: 1, message: '更新成功', result: {doc}})
        })
      })
    })
  })
...

数组内部嵌套查询

const UserSchema = new Schema({
    userName:{
        type:String,
        required:true
    },
    password:{
        type:String,
        required
    },
    post:[{
        type:Schema.Types.ObjectID,
        ref:"Post
    }]
})
const PostSchema = new Schema({
    title:{
        type:String,
        required:true
    },
    conetxt:{
        type:String,
        required
    },
   auther:{
       type:Schema.Types.ObjectID,
       ref:'User'
   }
})
const User = mongoose.model('User',UserSchema)
const Post = mongoose.model('Post',PostSchema)
const findUserPost = async (username,title)=>{
 return  User.findOne({
    username,
    "post.title":title  // post.title 数组的字段名称 和 查询的对应字段 
     // 注意这里的字段名最好使用 双引号 要不然可能会出现意向不到的错误
    }).exec()
}
 //当然你也可以这样  分步查询
const findUserPost = async (username,title)=>{
    const newUser =await User.findOne({
        username
    }).exec()
    const posts =await  Post.find({
        auther:newUser._id,
        title
    }).exec()
    return posts
}

数组内部的更新

//还是以上面的Schema 为例  
// 当需要更新用户的文章的时候  1 需要先找到对应用户  2 然后再更新其文章修改
const UpdateUserPost = async (username,title,newPostContent) =>{
  return  User.update({
      // 查询到文章
      username,
      "post.title":title
  },{ $set: // 进行更新  使用 $ 占位表示对数组内部的字段进行更新
     { "post.$.conetxt":newPostContent}
  }).exec()
}
// 当然你也可以使用如下的分布式操作
const UpdateUserPost = async (username,title,newPostContent) =>{
   const user =await User.findOne({
       username
   }).exec()
return  Post.update({
      auther:user._id,
      title
  },{  $set:
   { conetxt:newPostContent}
  }).exec()
}

数组内部的添加和删除

添加

// 添加User的同时 即可 添加post
const inserUser = async (newUser) =>{
 const user = new User(newUser)
 user.save()
}

删除

const delUser = async (userID)=>{
    // 先删除User  对应的所有文章  然后再删除User
const result =    await Post.remove({
        auther:userID
    })
const result = await User.findByidAndRemove(userID)
}

注意 :上面的逻辑对于一般性的操作还可以,可是对于严格操作数据就可能不会有一致性了

事物的支持

mongoDB 再4.0 后正式支持事物,mongoose 也开始支持事物了

//仍然使用上面的 Schema
  const demo = async () => {
      // 获取事物
    const session = mongoose.startSession()
    //操作前开启事物
    session.startTransaction()
    try {
      const user = new User({firstName: 'alfieri'})
      const result = await user.save().session(session)  //保存操作 
      await User.findOneAndUpdate({_id: result._id}, { $inc: { lastName: 'chou'}}).session(session) //更新操作
        // 完成全部操作后 提交事物
      await session.commitTransaction() 
        //关闭事物
      session.endSession()
      return user // 返回结果
    } catch (err) {
        // 事物异常回滚
      await session.abortTransaction()
        //关闭事物
      session.endSession()
        //提示错误操作
      throw new Error('something went wrong')
    }
  }

其他例子请看官方示例

对象内部的CRUD

对象内部的crud 使用 字段名.内部字段名的方式

Post.findOne({"user.username":'haha'}) //查询
Post.update({"user.username":'haha'},{$set{"user.username":"zhangsan"}}) //更新

Last updated

Was this helpful?