查询

Mongoose 模型 提供多个用于 CRUD 操作 的静态辅助函数。每个函数都返回一个 mongoose Query 对象

Mongoose 查询可以通过两种方式执行。首先,如果传入一个 callback 函数,Mongoose 将异步执行查询并将结果传递给 callback

查询还具有 .then() 函数,因此可以用作 Promise。

执行

在执行查询时,您将查询指定为 JSON 文档。JSON 文档的语法与 MongoDB shell 相同。

const Person = mongoose.model('Person', yourSchema);

// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields
const person = await Person.findOne({ 'name.last': 'Ghost' }, 'name occupation');
// Prints "Space Ghost is a talk show host".
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation);

person 的具体内容取决于操作:对于 findOne(),它是一个 可能为 null 的单个文档find() 是一个 文档列表count()文档数量update()受影响的文档数量 等等。模型的 API 文档 提供了更多详细信息。

现在让我们看看当不使用 await 时会发生什么

// find each person with a last name matching 'Ghost'
const query = Person.findOne({ 'name.last': 'Ghost' });

// selecting the `name` and `occupation` fields
query.select('name occupation');

// execute the query at a later time
const person = await query.exec();
// Prints "Space Ghost is a talk show host."
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation);

在上面的代码中,query 变量的类型为 QueryQuery 使您可以使用链式语法构建查询,而不是指定 JSON 对象。以下两个示例等效。

// With a JSON doc
await Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  }).
  limit(10).
  sort({ occupation: -1 }).
  select({ name: 1, occupation: 1 }).
  exec();

// Using query builder
await Person.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec();

查询辅助函数的完整列表可以在 API 文档中找到

查询不是 Promise

Mongoose 查询不是 Promise。查询是 thenable,这意味着它们具有用于 async/await.then() 方法,以方便起见。但是,与 Promise 不同,调用查询的 .then() 会执行查询,因此多次调用 then() 会抛出错误。

const q = MyModel.updateMany({}, { isDeleted: true });

await q.then(() => console.log('Update 2'));
// Throws "Query was already executed: Test.updateMany({}, { isDeleted: true })"
await q.then(() => console.log('Update 3'));

对其他文档的引用

MongoDB 中没有连接,但有时我们仍然需要引用其他集合中的文档。这就是 填充 的用武之地。详细了解如何在查询结果中包含来自其他集合的文档 此处

流式处理

您可以从 MongoDB 流式处理 查询结果。您需要调用 Query#cursor() 函数以返回 QueryCursor 的实例。

const cursor = Person.find({ occupation: /host/ }).cursor();

for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
  console.log(doc); // Prints documents one at a time
}

使用 异步迭代器 遍历 Mongoose 查询也会创建一个游标。

for await (const doc of Person.find()) {
  console.log(doc); // Prints documents one at a time
}

游标受 游标超时 的影响。默认情况下,MongoDB 会在 10 分钟后关闭您的游标,随后 next() 调用会导致 MongoServerError: cursor id 123 not found 错误。要覆盖此行为,请在游标上设置 noCursorTimeout 选项。

// MongoDB won't automatically close this cursor after 10 minutes.
const cursor = Person.find().cursor().addCursorFlag('noCursorTimeout', true);

但是,由于 会话空闲超时 的原因,游标仍然会超时。因此,即使设置了 noCursorTimeout 的游标,在 30 分钟不活动后也会超时。您可以在 MongoDB 文档 中详细了解如何解决会话空闲超时。

与聚合相比

聚合 可以完成查询可以完成的许多相同的事情。例如,以下是如何使用 aggregate() 来查找 name.last = 'Ghost' 的文档

const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]);

但是,仅仅因为您可以使用 aggregate() 并不意味着您应该使用它。一般而言,您应该尽可能使用查询,只有在绝对需要时才使用 aggregate()

与查询结果不同,Mongoose 不会 hydrate() 聚合结果。聚合结果始终是 POJO,而不是 Mongoose 文档。

const docs = await Person.aggregate([{ $match: { 'name.last': 'Ghost' } }]);

docs[0] instanceof mongoose.Document; // false

此外,与查询过滤器不同,Mongoose 也不会 类型转换 聚合管道。这意味着您有责任确保传递给聚合管道的值的类型正确。

const doc = await Person.findOne();

const idString = doc._id.toString();

// Finds the `Person`, because Mongoose casts `idString` to an ObjectId
const queryRes = await Person.findOne({ _id: idString });

// Does **not** find the `Person`, because Mongoose doesn't cast aggregation
// pipelines.
const aggRes = await Person.aggregate([{ $match: { _id: idString } }]);

排序

排序 是确保查询结果以所需顺序返回的方式。

const personSchema = new mongoose.Schema({
  age: Number
});

const Person = mongoose.model('Person', personSchema);
for (let i = 0; i < 10; i++) {
  await Person.create({ age: i });
}

await Person.find().sort({ age: -1 }); // returns age starting from 10 as the first entry
await Person.find().sort({ age: 1 }); // returns age starting from 0 as the first entry

使用多个字段排序时,排序键的顺序决定了 MongoDB 服务器首先按哪个键排序。

const personSchema = new mongoose.Schema({
  age: Number,
  name: String,
  weight: Number
});

const Person = mongoose.model('Person', personSchema);
const iterations = 5;
for (let i = 0; i < iterations; i++) {
  await Person.create({
    age: Math.abs(2 - i),
    name: 'Test' + i,
    weight: Math.floor(Math.random() * 100) + 1
  });
}

await Person.find().sort({ age: 1, weight: -1 }); // returns age starting from 0, but while keeping that order will then sort by weight.

您可以在下面查看此代码块的单次运行的输出。如您所见,年龄按 0 到 2 排序,但当年龄相同时,按体重排序。

[
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb37'),
    age: 0,
    name: 'Test2',
    weight: 67,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb35'),
    age: 1,
    name: 'Test1',
    weight: 99,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb39'),
    age: 1,
    name: 'Test3',
    weight: 73,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb33'),
    age: 2,
    name: 'Test0',
    weight: 65,
    __v: 0
  },
  {
    _id: new ObjectId('63a335a6b9b6a7bfc186cb3b'),
    age: 2,
    name: 'Test4',
    weight: 62,
    __v: 0
  }
];

下一步

现在我们已经介绍了 查询,让我们来看看 验证