查询
Mongoose 模型 提供多个用于 CRUD 操作 的静态辅助函数。每个函数都返回一个 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 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
变量的类型为 Query。Query
使您可以使用链式语法构建查询,而不是指定 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();
查询不是 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
}
];
下一步
现在我们已经介绍了 查询
,让我们来看看 验证。