在进行 Web 应用开发时,我们常常需要使用到数据库,而数据库中的关联关系则是非常常见的一种数据结构。在 MongoDB 中,我们可以使用 Mongoose 这个 Node.js 的 ORM(对象关系映射)库来定义和使用数据库的关联关系。本文将详细介绍 Mongoose 中如何定义和使用数据库的关联关系,并提供示例代码和实际应用场景。
一、定义关联关系
Mongoose 中有三种类型的关联关系,分别是一对一、一对多和多对多。在定义关联关系之前,我们需要先安装并导入 Mongoose 库。
const mongoose = require('mongoose');1. 一对一关联
一对一关联是指一个文档与另一个文档之间存在唯一的关联关系。在 Mongoose 中,我们可以使用 populate 方法来查询关联的文档。
首先,我们需要定义一个 Schema(模式),并在其中定义一个 ref 属性,用于指定关联的模型。
// www.javascriptcn.com code example
const userSchema = new mongoose.Schema({
name: String,
email: String,
address: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Address'
}
});
const addressSchema = new mongoose.Schema({
street: String,
city: String,
state: String
});
const User = mongoose.model('User', userSchema);
const Address = mongoose.model('Address', addressSchema);在上面的代码中,我们定义了两个模型,分别是 User 和 Address。User 模型中有一个 address 字段,类型为 mongoose.Schema.Types.ObjectId,用于存储关联的 Address 文档的 _id。Address 模型中没有任何关联字段。
接下来,我们可以使用 populate 方法查询关联的文档。
// www.javascriptcn.com code example
User.findOne({ name: 'Alice' })
.populate('address')
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
console.log(user.address.street);
});在上面的代码中,我们使用 populate 方法查询了 User 模型中名字为 Alice 的用户,并将其关联的 Address 文档一并查询出来。查询结果中的 user.address 就是关联的 Address 文档,我们可以直接访问其属性。
2. 一对多关联
一对多关联是指一个文档与多个文档之间存在关联关系。在 Mongoose 中,我们可以使用 populate 方法和虚拟字段(Virtual)来查询关联的多个文档。
首先,我们需要定义两个 Schema,分别为 User 和 Post。
// www.javascriptcn.com code example
const userSchema = new mongoose.Schema({
name: String
});
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);在上面的代码中,我们定义了两个模型,分别是 User 和 Post。User 模型中没有任何关联字段,Post 模型中有一个 author 字段,类型为 mongoose.Schema.Types.ObjectId,用于存储关联的 User 文档的 _id。
接下来,我们可以使用虚拟字段来查询关联的多个文档。
// www.javascriptcn.com code example
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'author'
});
User.findOne({ name: 'Alice' })
.populate('posts')
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
console.log(user.posts[0].title);
});在上面的代码中,我们使用虚拟字段 posts 查询了 User 模型中名字为 Alice 的用户,并将其关联的 Post 文档一并查询出来。查询结果中的 user.posts 就是关联的 Post 文档数组,我们可以直接访问其属性。
3. 多对多关联
多对多关联是指多个文档之间存在多对多的关联关系。在 Mongoose 中,我们可以使用 populate 方法和中间模型(Intermediate Model)来查询关联的多个文档。
首先,我们需要定义三个 Schema,分别为 User、Group 和 Membership。
// www.javascriptcn.com code example
const userSchema = new mongoose.Schema({
name: String
});
const groupSchema = new mongoose.Schema({
name: String
});
const membershipSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
group: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group'
}
});
const User = mongoose.model('User', userSchema);
const Group = mongoose.model('Group', groupSchema);
const Membership = mongoose.model('Membership', membershipSchema);在上面的代码中,我们定义了三个模型,分别是 User、Group 和 Membership。User 和 Group 模型中没有任何关联字段,Membership 模型中有两个字段 user 和 group,分别用于存储关联的 User 和 Group 文档的 _id。
接下来,我们可以使用中间模型来查询关联的多个文档。
// www.javascriptcn.com code example
User.findOne({ name: 'Alice' })
.populate({
path: 'memberships',
populate: {
path: 'group'
}
})
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
console.log(user.memberships[0].group.name);
});在上面的代码中,我们使用中间模型 Membership 查询了 User 模型中名字为 Alice 的用户,并将其关联的 Group 文档一并查询出来。查询结果中的 user.memberships 就是关联的 Membership 文档数组,其中每个 Membership 文档中的 group 属性就是关联的 Group 文档,我们可以直接访问其属性。
二、使用关联关系
在定义关联关系之后,我们可以使用 Mongoose 提供的 API 来操作关联的文档。
1. 创建关联文档
在创建关联文档时,我们需要先创建主文档,然后在从文档中引用主文档的 _id。
// www.javascriptcn.com code example
const alice = new User({ name: 'Alice' });
const address = new Address({ street: '123 Main St', city: 'Anytown', state: 'CA' });
address.save((err) => {
if (err) {
console.error(err);
return;
}
alice.address = address._id;
alice.save((err) => {
if (err) {
console.error(err);
return;
}
console.log('User created with address:', alice);
});
});在上面的代码中,我们先创建了一个 Address 文档,然后将其 _id 赋值给 User 文档的 address 字段,并保存 User 文档。
2. 更新关联文档
在更新关联文档时,我们可以直接更新主文档中的关联字段。
// www.javascriptcn.com code example
User.findOne({ name: 'Alice' })
.populate('address')
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
user.address.street = '456 Second St';
user.address.save((err) => {
if (err) {
console.error(err);
return;
}
console.log('User address updated:', user);
});
});在上面的代码中,我们先查询了名字为 Alice 的 User 文档,并将其关联的 Address 文档一并查询出来。然后,我们直接更新了 Address 文档的 street 属性,并保存 Address 文档。
3. 删除关联文档
在删除关联文档时,我们需要先解除主文档中的关联,然后再删除从文档。
// www.javascriptcn.com code example
User.findOne({ name: 'Alice' })
.populate('address')
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
user.address = null;
user.save((err) => {
if (err) {
console.error(err);
return;
}
Address.findOneAndDelete({ _id: user.address._id }, (err) => {
if (err) {
console.error(err);
return;
}
console.log('User address deleted:', user);
});
});
});在上面的代码中,我们先查询了名字为 Alice 的 User 文档,并将其关联的 Address 文档一并查询出来。然后,我们将 User 文档的 address 字段赋值为 null,并保存 User 文档。最后,我们删除了关联的 Address 文档。
三、实际应用场景
关联关系在实际应用中非常常见,下面是一个示例应用场景:
假设我们正在开发一个博客应用,其中有两个模型 User 和 Post,User 模型用于存储用户信息,Post 模型用于存储文章信息,并与 User 模型存在一对多的关联关系。我们需要实现以下功能:
- 创建文章时,需要指定作者。
- 查询文章时,需要将作者信息一并查询出来。
- 查询用户时,需要将其关联的所有文章一并查询出来。
以下是相应的代码实现:
// www.javascriptcn.com code example
const userSchema = new mongoose.Schema({
name: String
});
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'author'
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);
const john = new User({ name: 'John' });
const alice = new User({ name: 'Alice' });
john.save((err) => {
if (err) {
console.error(err);
return;
}
alice.save((err) => {
if (err) {
console.error(err);
return;
}
const post = new Post({ title: 'Hello World', content: 'This is my first post.', author: john._id });
post.save((err) => {
if (err) {
console.error(err);
return;
}
Post.findOne({ title: 'Hello World' })
.populate('author')
.exec((err, post) => {
if (err) {
console.error(err);
return;
}
console.log(post.author.name);
});
User.findOne({ name: 'John' })
.populate('posts')
.exec((err, user) => {
if (err) {
console.error(err);
return;
}
console.log(user.posts[0].title);
});
});
});
});在上面的代码中,我们先创建了两个 User 文档 john 和 alice,然后创建了一个 Post 文档,并将其关联到 john 文档。最后,我们查询了名为 Hello World 的 Post 文档,并将其关联的 User 文档一并查询出来,以及查询了名为 John 的 User 文档,并将其关联的所有 Post 文档一并查询出来。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d0e272a941bf71341dc2b9