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 连表操作有两种模式
如果两表没有主表内部直接存储子表的字段 而是字段的外键模式,则使用aggregate
如果是子文档 例如存储在主文档的对象或者数组中则使用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?