本文阅读前提:MongoDB数据库已安装、Node环境已配置、初始化项目并安装 ‘mongoose’ 插件。
预计花费时间:20Min;
本篇文章以“文章”和“分类”为例子,来讲解MongoDB的基础用法。跟随本文代码讲解,你将学会MongoDB的模型建立和关联方法/技巧,
此篇教程学习自 -B站-全站之巅-乔尼老师- 再次感谢!真的学到很多东西。
PS1:建议MongoDB中对数据库的操作都采用await方法。
PS2:插入数据的代码只需要执行一次,否则会出现很多重复数据。
0. 连接MongoDB数据库
mongoose.connect("mongodb://localhost:27017/mongo-relation", { // mongo-relation 为数据库名称
useUnifiedTopology: true, // 参数的固定写法
useNewUrlParser: true, // 参数的固定写法
});
1. mongoose.model()
// 建立文章模型 Post
const Post = mongoose.model(
"Post",
new mongoose.Schema({
title: { type: String },
body: { type: String }
})
);
// 建立分类模型 Category
const Category = mongoose.model('Category', new mongoose.Schema({
name: { type: String },
}))
mongoose.model()函数,传入一个参数时,代表查询模型;传入两个参数时,代表定义模型。
其中,第一个参数代表模型名称(模型建议首字母大写),第二个参数代表定义模型的结构(Schema类型,用来定义表结构)。
2. {$modelName}.insertMany()
// 向文章中插入多条数据
await Post.insertMany([
{title: '第一篇文章', body: '内容1'},
{title: '第二篇文章', body: '内容2'}
])
// 向分类中插入多条数据
await Category.insertMany([
{name: 'nodejs'},
{name: 'vuejs'}
])
{$modelName}.insertMany()函数,向数据库中插入多条数据。
3. 添加关联定义
在文章模型中添加一条属性(关联属性)。
const Post = mongoose.model(
"Post",
new mongoose.Schema({
title: { type: String },
body: { type: String },
category: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }, // 新添加的关联属性
})
);
新添加的属性中:
ref为与之相关联的模型,用字符串表示模型名称;
type为对应模型(Category分类模型)中的元素id,其中的 “mongoose.SchemaTypes.ObjectId” 是固定写法,不用去理解,记住就好。
4. 将文章和分类做关联(并查询文章对应的分类)
const cat1 = await Category.findOne({ name: 'nodejs' }) // 找出第一个分类模型
const post1 = await Post.findOne({ title: '第一篇文章' }) // 找出第一篇文章模型
const post2 = await Post.findOne({ title: '第二篇文章' }) // 找出第二篇文章模型
// 设置第一篇文章的分类
post1.category = cat1._id // 第一种写法(mongodb会默认生成"_id"字段)
post1.category = cat1 // 第二种写法
// 设置第二篇文章的分类
post2.category = cat1
await post1.save() // 保存
await post2.save()
此时我们用console.log打印出文章的内容就可以看到,已经成功关联到对应分类的id。
const posts = await Post.find()
console.log(await Post.find()) // 输出文章内容
但是现在只能查出关联分类的id,如果想要查出该关联分类的详细信息,就需要使用populate()关联查询方法。
const posts = await Post.find().populate('category') // 括号内的参数为字段名 String类型
console.log(await Post.find()) // 输出文章内容
这样就查出关联数据 ‘category’ 所有信息(返回一个对象)。
然后就可以用 对象+点+key 的形式拿到分类的name属性。
但到目前位置分类对文章还是一对多的关系,也就是说一篇文章只能对应一个分类,一个分类对应多个文章。可是在现实生活中,一篇文章很可能有多个分类,那么这个时候,我们就需要修改文章模型的定义。
const Post = mongoose.model(
"Post",
new mongoose.Schema({
title: { type: String },
body: { type: String },
// 之前的定义方法 一篇文章只能对应一个分类
// category: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category' },
// 现在的定义方法 一篇文章能对应多个分类
categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }], // 数组包裹
})
);
这里用了一个数组将这个字段包裹起来,就实现了一篇文章对应多个分类的功能。
那么我们在做分类关联时候的代码,也要发生相应的变化。
const cat1 = await Category.findOne({ name: 'nodejs' }) // 找出第一个分类模型
const cat2 = await Category.findOne({ name: 'vuejs' }) // 找出第二个分类模型
const post1 = await Post.findOne({ title: '第一篇文章' }) // 找出第一篇文章模型
const post2 = await Post.findOne({ title: '第二篇文章' }) // 找出第二篇文章模型
//配置分类
post1.categories = [cat1, cat2]
post2.categories = [cat2]
await post1.save()
await post2.save()
const posts = await Post.find().populate('categories')
console.log(posts[0], posts[1]) // 为了方便显示 这里分别查出两个数据
打印结果后发现,多对多的关联查询已经实现了,每篇文章可以关联多个分类,也可以关联单个分类,根据传入数组的元素个数而定。
5. 查询每个分类下的文章(难点)
反过来查询每个分类下的文章,如果依旧使用上述方法查询,我们会发现分类模型中并没有Post字段,无法做到关联查询。所以我们需要在分类模型中定义一个 虚拟字段。
为了方便阅读,我们将之前定义Category模型时的第二个参数拆分出来,命名为 CategorySchema。
并使用 virtual 添加一个虚拟字段,其中四个参数的意义可以用一句话简单说明:
第一次可能读不懂,没关系,多读几遍。
将本模型(Category)的 ‘_id’ 字段关联到外部模型(Post)的 ‘categories’ 字段上
// 定义Category模型时的第二个参数
const CategorySchema = new mongoose.Schema({
name: { type: String },
})
// 添加一个虚拟字段 posts
CategorySchema.virtual('posts', {
localField: '_id', // 本地键
ref: 'Post', // 参考模型
foreignField: 'categories', // 外部键
justOne: false // 是否返回单条数据
})
const Category = mongoose.model('Category', CategorySchema)
此时已经关联成功了,我们输出结果。
但是在输出时会遇到一个问题,因为mongodb的机制,在输出时候会默认隐藏虚拟属性,所以输出语句也要稍作更改。
const cats = await Category.find().populate('posts')
console.log(cats[0].posts) // 输出时同时输出虚拟字段
console.log(cats[1].posts) // 输出时同时输出虚拟字段
查询成功,第一个分类的查询结果:
第二个分类的查询结果:
当然,还有其他办法来避免mongo的隐藏机制,但并不是本篇文章的重点,就不在这里讨论了。
至此,mongodb的基本关联查询就完成了。
你学会了吗~
最后,当然要附上源码啦!
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/mongo-relation", {
useUnifiedTopology: true,
useNewUrlParser: true,
});
// 建立文章模型 Post
const Post = mongoose.model(
"Post",
new mongoose.Schema({
title: { type: String },
body: { type: String },
// category: { type: mongoose.SchemaTypes.ObjectId, ref: 'Category' },
categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }],
})
);
// 建立分类模型 Category
const CategorySchema = new mongoose.Schema({
name: { type: String },
})
// 添加一个虚拟字段 posts
CategorySchema.virtual('posts', {
localField: '_id', // 本地键
ref: 'Post', // 参考模型
foreignField: 'categories', // 外键
justOne: false // 是否返回单条数据
})
const Category = mongoose.model('Category', CategorySchema)
async function main() {
// 向文章中插入多条数据
// await Post.insertMany([
// {title: '第一篇文章', body: '内容1'},
// {title: '第二篇文章', body: '内容2'}
// ])
// 向分类中插入多条数据
// await Category.insertMany([
// {name: 'nodejs'},
// {name: 'vuejs'}
// ])
// const cat1 = await Category.findOne({ name: 'nodejs' }) // 找出第一个分类模型
// const cat2 = await Category.findOne({ name: 'vuejs' }) // 找出第二个分类模型
// const post1 = await Post.findOne({ title: '第一篇文章' }) // 找出第一篇文章模型
// const post2 = await Post.findOne({ title: '第二篇文章' }) // 找出第二篇文章模型
// //配置分类
// post1.categories = [cat1, cat2]
// post2.categories = [cat2]
// await post1.save()
// await post2.save()
// const posts = await Post.find().populate('categories')
// console.log(posts[0], posts[1]) // 为了方便显示 这里分别查出两个数据
const cats = await Category.find().populate('posts')
console.log(cats[0].posts)
}
main();
共勉!