数据库最多操作的便是查询,本文将来看看如何使用 Mongoose 查询文档。
Mongoose Model.find(filter, callback)
方法允许你查询具有给定键/值的文档,它将返回与给定过滤器匹配的文档数组。
假设你有一个 mongoose 模型 User
,其中包含应用程序的所有用户。要获取集合中所有用户的列表,可以调用 User.find()
将空对象作为第一个参数:
const User = mongoose.model(
'User',
new mongoose.Schema({
name: String,
age: String
})
)
await User.create([
{ name: 'D.O', age: 20 },
{ name: 'O.O', age: 19 },
{ name: 'K.O', age: 18 },
{ name: 'O.K', age: 17 },
{ name: 'O.O', age: 22 }
])
// 空的 filter 表示匹配所有文档
const filter = {}
const all = await User.find(filter)
同样地,你可以调用 User.find()
不带参数,也就是省略 filter
,你将得到相同的结果。
await User.find() // 返回上面带有 _id 属性和 __v 属性的数组
Model.find()
的默认行为是返回模型中的所有文档,因此如果传递的属性都不存在,它将所有文档。
但需要注意的是,不要直接查询字符串 req.query
直接传递给 find
方法,
// 不要这样做,req.query 可能是空对象
// 在这种情况下,查询将返回每个文档。
await Model.find(req.query)
Mongoose 6 引入了一个新 sanitizeFilter
选项,可以防御查询选择器注入攻击。它只是将过滤器包装在 $eq
标签中,从而防止查询选择器注入攻击。
// 使用 sanitizeFilter,Mongoose 将下面的查询转换为 { age, hashedPassword: { $eq: { $ne: null } } }
const user = await User.find({
email: '[email protected]',
hashedPassword: { $ne: null }
}).setOptions({ sanitizeFilter: true })
假设你的应用程序很受欢迎,你有数百万用户。一次将所有用户加载到内存是行不通的。要一次遍历所有用户,而不将其全部加载到内存中,请使用 cursor
。
const User = mongoose.model(
'User',
new mongoose.Schema({
name: String,
email: String
})
)
// 注意此处没有 await
const cursor = User.find().cursor()
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {}
或者,你可以使用异步迭代器。
for await (const doc of User.find()) {
}
要过滤 mongoose 中的对象属性,可以对查询使用 select()
方法。其作用是:选择要返回的字段。
//将返回仅包含文档的年龄、名称和id属性的所有文档
await Model.find({}).select('name age')
默认情况下,MongoDB 包含 _id
。要在选择字段时排除 _id
,可以使用 .find().select({ name: 1, _id: 0 })
或 .find().select('name -_id')
。0
和 -
告诉 Mongoose 和 MongoDB 服务器显式排除 _id
。
await Model.find().select({ name: 1, _id: 0 });
// or
await Model.find().select({'name -_id'});
在 Mongoose 中,Model.findById()
方法用于根据文档的 _id
查找一个文档。findById()
方法接受单个参数,即文档 id
。如果 MongoDB 找到具有给定 id
的文档,则返回解析为 Mongoose 文档的 Promise;如果未找到任何文档,则返回 null
。
const schema = new mongoose.Schema({ _id: Number }, { versionKey: false })
const Model = mongoose.model('MyModel', schema)
await Model.create({ _id: 1 })
await Model.findById(1) // { _id: 1 }
await Model.findById(2) // null,因为找不到任何文档
当你调用 findById(_id)
时,Mongoose 会在后台调用 findOne({ _id })
。这意味着 findById()
触发 findOne()
中间件。
const schema = new mongoose.Schema({ _id: Number }, { versionKey: false })
schema.pre('findOne', function () {
console.log('调用 findOne()')
})
const Model = mongoose.model('MyModel', schema)
await Model.create({ _id: 1 })
// 打印 findOne(),因为 findById() 调用 findOne()
await Model.findById(1)
Mongoose 会强制转换查询以匹配你的模式。这意味着如果你的 _id
是一个 MongoDB ObjectId,你可以将 _id
作为字符串传递,Mongoose 将为你将其转换为 ObjectId。
const _id = '9d641f2ed75f4e2513b90abc'
const schema = new mongoose.Schema(
{ _id: mongoose.ObjectId },
{ versionKey: false }
)
const Model = mongoose.model('MyModel', schema)
await Model.create({ _id: new mongoose.Types.ObjectId(_id) })
typeof _id // 'string'
// { _id: '9d641f2ed75f4e2513b90abc' }
const doc = await Model.findById(_id)
typeof doc._id // 'object'
doc._id instanceof mongoose.Types.ObjectId // true
许多 Mongoose 模型函数,例如 find()
,返回一个 Mongoose Query。Mongoose Query 类提供了一个用于查找、更新和删除文档的链式接口。
Model.find()
的第一个参数称为查询过滤器。当你调用 find()
时,MongoDB 将返回与查询过滤器匹配的所有文档。你可以使用 Mongoose 的众多查询来构建查询过滤器。只需确保使用 where()
指定要添加到过滤器中的属性名即可。
let docs = await User.find()
// where 指定属性的名称,in() 指定 name 必须是数组中的两个值之一
.where('name')
.in(['D.O', 'O.K'])
// 相同的查询,但过滤器表示为对象,而不是使用链式
docs = await User.find({
name: { $in: ['D.O', 'O.K'] }
})
可链式操作允许添加到当前查询筛选器。可以使用 query.getFilter()
方法获取查询的当前筛选器。
const query = User.find().where('name').in(['D.O', 'O.K'])
// { name: { $in: ['D.O', 'O.K'] } }
query.getFilter()
以下是几个有用的查询方法列表:
lt(value)
和gt(value)
— 指定一个属性必须小于(lt()
)或大于(gt()
)一个值。value
可以是数字、字符串或日期。lte(value), gte(value)
— 指定一个属性必须大于或等于(gte()
)或小于或等于(gte()
)一个值。in(arr)
— 指定一个属性必须等于arr
中指定的值之一。nin(arr)
— 指定一个属性不能等于arr
中指定的任何值。eq(val)
— 指定一个属性必须等于val
。ne(val)
— 指定一个属性不能等于val
。regex(re)
— 指定一个属性必须是re
匹配的字符串。
你可以链式调用任意多个 where()
和查询方法来建立查询。例如:
const docs = await User.find()
// name 必须与正则表达式匹配,age 必须在 29 到 59 岁之间
.where('name')
.regex(/o/i)
.where('age')
.gte(29)
.lte(59)
docs.map((doc) => doc.name) // [ 'D.O', 'O.O', 'O.K' ]
使用 SQL LIKE 运算符允许我们搜索带有通配符的字符串。MongoDB 没有类似的运算符,$text
运算符执行更复杂的文本搜索。但是 MongoDB 确实支持与 LIKE 类似的正则表达式查询。
例如,假设你想要查找 email
包含 gmail
的所有用户。你可以简单地通过 JavaScript 正则表达式 /gmail/
进行搜索:
const User = mongoose.model(
'User',
new mongoose.Schema({
email: String
})
)
await User.create([
{ email: '[email protected]' },
{ email: '[email protected]' },
{ email: '[email protected]' },
{ email: '[email protected]' }
])
const docs = await User.find({ email: /163/ })
docs.length // 3
docs.map((doc) => doc.email).sort() // ['[email protected]', '[email protected]', '[email protected]']
同样,你可以使用 $regex
运算符。
const docs = await User.find({ email: { $regex: '163' } })
需要注意的是 mongoose 不会为你转义 regexp 中的特殊字符。如果要对用户输入的数据使用 $regexp
,应首先使用 escape-string-regexp 或用于转义正则表达式特殊字符的类似库来清理字符串。
const escapeStringRegexp = require('escape-string-regexp')
const User = mongoose.model(
'User',
new mongoose.Schema({
email: String
})
)
await User.create([
{ email: '[email protected]' },
{ email: '[email protected]' },
{ email: '[email protected]' }
])
const $regex = escapeStringRegexp('+foo')
const docs = await User.find({ email: { $regex } })
docs.length // 1
docs[0].email // '[email protected]'
// Throws: MongoError: Regular expression is invalid: nothing to repeat
await User.find({ email: { $regex: '+foo' } })
在 Mongoose 中,Model.find() 函数是查询数据库的主要工具。find()的第一个参数是一个筛选器对象。MongoDB 将搜索与过滤器匹配的所有文档。如果传入一个空过滤器,MongoDB 将返回所有文档。
我们可以使用 MongoDB 查询运算符构造过滤器对象,从而在 Mongoose 中执行常见查询。
const User = mongoose.model(
'User',
new mongoose.Schema({
name: String,
age: String
})
)
await User.create([
{ name: 'D.O', age: 30 },
{ name: 'O.O', age: 29 },
{ name: 'K.O', age: 18 },
{ name: 'O.K', age: 40 },
{ name: 'O.O', age: 22 }
])
假设你要查找所有 name
为 O.O 的用户。可以将 { age: 'O.O' }
作为 filter
传递。
const docs = await User.find({ name: 'O.O' })
// MongoDB 可以按任何顺序返回文档,除非你明确排序
docs.map((doc) => doc.age).sort() // [29, 22]
你还可以按年龄查询。例如,下面的查询将查找 age
为 29 岁的所有字符。
const docs = await User.find({ age: 29 })
docs.map((doc) => doc.name).sort() // ['O.O', 'O.K']
以上示例不使用任何查询运算符。如果将 name
的值设置为具有 $eq
属性的对象,则会得到一个等效的查询,但需要使用查询运算符。
const docs = await User.find({ name: { $eq: 'O.O' } })
docs.map((doc) => doc.age).sort() // [29, 22]
$eq
查询运算符检查完全相等。还有一些比较查询运算符,比如 $gt
和 $lt
。例如,假设你想查找年龄严格小于 29 岁的所有字符。你可以使用 $lt
查询运算符,如下所示。
const docs = await User.find({ age: { $lt: 29 } })
docs.map((doc) => doc.name).sort() // ['K.O', 'O.O']
假设你想找到所有年龄至少为 29 岁的用户。你可以使用 $gte
查询运算符。
const docs = await User.find({ age: { $gte: 29 } })
docs.map((doc) => doc.name).sort() // ['D.O', 'O.K', 'O.O']
比较运算符 $lt
、$gt
、$lte
和 $gte
不仅可以处理数字。你还可以在字符串、日期和其他类型上使用它们。MongoDB 使用 unicode 顺序比较字符串。如果该顺序不适用于你,你可以使用 MongoDB collations 对其进行配置。
const docs = await User.find({ name: { $lte: 'K.O' } })
docs.map((doc) => doc.name).sort() // [ 'D.O', 'K.O' ]
假设你要查找 name
包含 K
的用户。在 SQL 中,可以使用 LIKE
运算符。在 Mongoose 中,你可以简单地通过正则表达式进行查询,如下所示。
const docs = await User.find({ name: /K/ })
docs.map((doc) => doc.name).sort() // ['K.O', 'O.K']
同样,你可以使用 $regex
查询运算符。这使你能够将正则表达式作为字符串传递,如果你是从 HTTP 请求中获取查询,这很方便。
const docs = await User.find({ name: { $regex: 'K' } })
docs.map((doc) => doc.name).sort() // ['K.O', 'O.K']
如果设置了多个 filter
属性,MongoDB 将查找与所有过滤器属性匹配的文档。例如,以下的查询将查找 age
至少为 29 岁且 name
等于 'K.O'
的所有用户。
const docs = await User.find({
name: 'K.O',
age: { $gte: 29 }
})
docs.map((doc) => doc.name) // ['O.O']
假设你要查找 age
至少为 29 岁或 name
等于 O.O
的用户。你需要 $or
查询运算符。
const docs = await User.find({
$or: [{ age: { $gte: 29 } }, { name: 'O.O' }]
})
docs.map((doc) => doc.name).sort() // [ 'D.O', 'O.O', 'O.K', 'O.O' ]
还有一个 $and
查询运算符。你很少需要使用 $and
查询运算符。$and
的主要用例是组合多个 $or
运算符。例如,假设你要查找满足以下两个条件的字符:
age
至少 29 或name
等于'O.O'
name
以字母开头,在O
之前或O
之后。
const docs = await User.find({
$and: [
{
$or: [{ age: { $gte: 29 } }, { rank: 'O.O' }]
},
{
$or: [{ name: { $lte: 'O' } }, { name: { $gte: 'K' } }]
}
]
})
docs.map((doc) => doc.name).sort() // [ 'D.O', 'O.O', 'O.K' ]