查询

Mongoose models增删改查操作 提供了几个静态辅助函数。 这些函数中的每一个都返回一个 Mongoose Query 对象

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

查询也有 .then() 函数,因此可以用作 promise。

执行

执行查询时,你将查询指定为 JSON 文档。 JSON 文档的语法与 MongoDB 外壳.1 相同。

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(),它是 可能为空的单个文档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 变量的类型是 查询Query 使你能够使用链接语法构建查询,而不是指定 JSON 对象。 以下 2 个示例是等效的。

// 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 查询是 not promise。 查询是 thenables,这意味着为了方便起见,它们有 .then() 方法用于 async/await。 但是,与 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 中没有联接,但有时我们仍然希望引用其他集合中的文档。 这就是 population 发挥作用的地方。 详细了解如何在查询结果中包含其他集合中的文档 here

流式

你可以从 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 会对结果进行 not hydrate() 聚合。 聚合结果始终是 POJO,而不是 Mongoose 文档。

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

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

此外,与查询过滤器不同,Mongoose 也没有 cast 聚合管道。 这意味着你有责任确保传入聚合管道的值具有正确的类型。

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
  }
];

下一步

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