查询
Mongoose models 为 增删改查操作 提供了几个静态辅助函数。
这些函数中的每一个都返回一个 Mongoose Query
对象。
Model.deleteMany()
Model.deleteOne()
Model.find()
Model.findById()
Model.findByIdAndDelete()
Model.findByIdAndRemove()
Model.findByIdAndUpdate()
Model.findOne()
Model.findOneAndDelete()
Model.findOneAndReplace()
Model.findOneAndUpdate()
Model.replaceOne()
Model.updateMany()
Model.updateOne()
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
,让我们来看看 验证。