MongoDB 中的多对多关系。Nodejs | Express | Mongoose

2025-06-10

MongoDB 中的多对多关系。Nodejs | Express | Mongoose

最近我正在做一个项目(Node、Express、MongoDB、Mongoose),我需要创建产品和类别之间的多对多关系,其中类别可以有多个产品,产品可以属于多个类别。

所以我开始着手解决这个问题,我使得添加、删除或更新产品也会自动更新类别,反之亦然。

然后我想看看其他人是怎么做的。于是我搜索了一下,却找不到类似的方法。这让我想到,为什么不分享我的方法,让其他人也能从中受益,或者有人能纠正我。

这就是我的做法,这是我的完整代码的简化版本。

模型

首先我创建了产品和类别模型。

产品型号
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const productSchema = new Schema({
    name:           { type: String, required: true},
    price:          { type: Number, required: true, min: 0 },
    categories:     [{ type: mongoose.Types.ObjectId, ref: 'Category' }],
});

module.exports = new mongoose.model('Product', productSchema);
Enter fullscreen mode Exit fullscreen mode

在这里您可以看到我的产品模型有三个简单的字段,第一个是产品名称,第二个是产品价格,第三个是该产品所属的类别数组。

类别模型
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const categorySchema = new Schema({
    name:       { type: String, required: true },
    products:   [{ type: mongoose.Types.ObjectId, ref: 'Product' }],
});
module.exports = new mongoose.model('Category', categorySchema);
Enter fullscreen mode Exit fullscreen mode

类别模型也很简单,有两个字段:第一个是类别的名称,第二个是此类别中所有产品的数组。

路线

为了简单起见,我将仅向您展示三条路线:创建、更新和删除路线。

1. 创建路线

假设我们要添加一款新产品 iPhone 12,并将其添加到“移动设备”、“智能手机”和“电子产品”三个类别中。因此,我们将使用以下数据发送请求。

{
    product: {
        "name": "iPhone 12",
        "price": "1200",
        "categories": ["606563248d90d1833f3cda0b", "606563334294906c9a9b9dfe", "6065633736dee94dfae613d7"]
    }
}
Enter fullscreen mode Exit fullscreen mode

这里的 categories 是我们要添加产品的类别的 ID。
为了处理这个请求,我创建了以下路由。

router.post('/', async function (req, res) {
  const { product } = req.body;

  const newProduct = await Product.create(product);

  await Category.updateMany({ '_id': newProduct.categories }, { $push: { products: newProduct._id } });

  return res.send(newProduct);
});
Enter fullscreen mode Exit fullscreen mode

首先,我从 req.body 中提取产品数据。
const { product } = req.body;

然后我根据这些数据创建了一个新产品。
const newProduct = await Product.create(product);

创建产品后,我使用updateMany()查找该产品中的所有类别并更新它们以包含该产品。
await Category.updateMany({ '_id': newProduct.categories }, { $push: { products: newProduct._id } });

删除路线
router.delete('/:id', async function (req, res) {
  const _id = req.params.id;
  const product = await Product.findOne({ _id });

  await product.remove();

  await Category.updateMany({ '_id': product.categories }, { $pull: { products: product._id } });

  return res.redirect(product);

});
Enter fullscreen mode Exit fullscreen mode

删除路线和创建路线一样简单。
首先,我通过 ID 找到产品,然后将其删除。
await Product.findOne({ _id });
await product.remove();

然后更新该产品中的所有类别以删除该产品。
await Category.updateMany({ '_id': product.categories }, { $pull: { products: product._id } });

更新路线

更新路径有点棘手。因为产品类别可能会更改,所以我们必须知道哪些类别被添加了,哪些被移除了。为此,我创建了这个函数。

function difference(A, B) {
  const arrA = Array.isArray(A) ? A.map(x => x.toString()) : [A.toString()];
  const arrB = Array.isArray(B) ? B.map(x => x.toString()) : [B.toString()];

  const result = [];
  for (const p of arrA) {
    if (arrB.indexOf(p) === -1) {
      result.push(p);
    }
  }

  return result;
}
Enter fullscreen mode Exit fullscreen mode

这个函数基本上接受两个数组 A 和 B,并返回一个新数组,该数组包含数组 A 中存在但不在数组 B 中的项目。类似于这样。

const arrA = ['a', 'b', 'c', 'd'];
const arrB = ['c', 'd', 'e', 'f'];

difference(arrA, arrB);
// Returns: ['a', 'b'] present in arrA but not in arrB

difference(arrB, arrA);
// Returns: ['e', 'f'] present in arrB but not in arrA
Enter fullscreen mode Exit fullscreen mode

我用它来比较新旧类别,以了解哪些类别被删除,哪些类别被添加。

因此为了更新产品我创建了这条路线。

router.put('/:id', async function (req, res) {
  const _id = req.params.id;
  const { product } = req.body;
  const newCategories = product.categories || [];

  const oldProduct = await Product.findOne({ _id });
  const oldCategories = oldProduct.categories;

  Object.assign(oldProduct, product);
  const newProduct = await oldProduct.save();

  const added = difference(newCategories, oldCategories);
  const removed = difference(oldCategories, newCategories);
  await Category.updateMany({ '_id': added }, { $addToSet: { products: foundProduct._id } });
  await Category.updateMany({ '_id': removed }, { $pull: { products: foundProduct._id } });

  return res.send(newProduct);
});
Enter fullscreen mode Exit fullscreen mode

在这里我首先从中获取 IDreq.params.id和产品更新日期,然后从产品中req.body获取。newCategories

之后,我通过 ID 找到产品并oldCategories从中提取数据,以便将它们与新类别进行比较,并确定哪些类别被添加了,哪些类别被删除了。
然后,我将该产品的所有属性赋值给旧产品并保存。

更新产品后,我使用difference()函数和newCategoriesoldCategories获取已添加和已删除的类别,然后我updateMany对类别使用了两个操作将该产品添加到added类别中并将其从类别中删除removed

分类 路线

类别路线与产品路线相同,我只是使用类别代替产品,反之亦然。

鏂囩珷鏉ユ簮锛�https://dev.to/nehalahmadkhan/many-to-many-relationship-in-mongodb-nodejs-express-mongoose-4djm
PREV
25+ JavaScript 项目源代码助你提升技能
NEXT
Kubernetes 集群分步指南