Faster Mongoose Queries With Lean使用精益实现更快的Mongoose查询
The lean option tells Mongoose to skip hydrating the result documents. lean
选项告诉Mongoose跳过补水结果文档。This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents. 这使得查询速度更快,内存占用更少,但结果文档是普通的旧JavaScript对象(POJO),而不是Mongoose文档。In this tutorial, you'll learn more about the tradeoffs of using 在本教程中,您将了解更多关于使用lean()
.lean()
的权衡。
Using 使用Lean
By default, Mongoose queries return an instance of the Mongoose 默认情况下,Mongoose查询返回Mongoose Document类的一个实例。Document
class.Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. 文档比普通的JavaScript对象要重得多,因为它们有很多用于更改跟踪的内部状态。Enabling the 启用lean
option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.lean
选项告诉Mongoose跳过实例化完整的Mongooser文档,只需提供POJO。
const leanDoc = await MyModel.findOne().lean();
How much smaller are lean documents? Here's a comparison.精简文档要小多少?这是一个比较。
const schema = new mongoose.Schema({ name: String });
const MyModel = mongoose.model('Test', schema);
await MyModel.create({ name: 'test' });
const normalDoc = await MyModel.findOne();
// To enable the `lean` option for a query, use the `lean()` function.
const leanDoc = await MyModel.findOne().lean();
v8Serialize(normalDoc).length; // approximately 180
v8Serialize(leanDoc).length; // 32, about 5x smaller!
// In case you were wondering, the JSON form of a Mongoose doc is the same as the POJO. 如果您想知道,Mongoose文档的JSON形式与POJO相同。This additional memory only affects how much memory your这个额外的内存只会影响你的内存
// Node.js process uses, not how much data is sent over the network.Node.js进程使用,而不是通过网络发送多少数据。
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true
Under the hood, after executing a query, Mongoose converts the query results from POJOs to Mongoose documents. 在后台,在执行查询后,Mongoose将POJO的查询结果转换为Mongoose文档。If you turn on the 如果你打开lean
option, Mongoose skips this step.lean
选项,Mongoose会跳过这一步。
const normalDoc = await MyModel.findOne();
const leanDoc = await MyModel.findOne().lean();
normalDoc instanceof mongoose.Document; // true
normalDoc.constructor.name; // 'model'
leanDoc instanceof mongoose.Document; // false
leanDoc.constructor.name; // 'Object'
The downside of enabling 启用lean
is that lean docs don't have:lean
的缺点是精益文档没有:
Change tracking更改跟踪Casting and validation铸造和验证Getters and setterssetter和setterVirtuals虚拟机save()
For example, the following code sample shows that the 例如,下面的代码示例显示,如果启用Person
model's getters and virtuals don't run if you enable lean
.lean
,Person
模型的getter和virtual就不会运行。
// Define a `Person` model. Schema has 2 custom getters and a `fullName`
// virtual. Neither the getters nor the virtuals will run if lean is enabled.
const personSchema = new mongoose.Schema({
firstName: {
type: String,
get: capitalizeFirstLetter
},
lastName: {
type: String,
get: capitalizeFirstLetter
}
});
personSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
function capitalizeFirstLetter(v) {
// Convert 'bob' -> 'Bob'
return v.charAt(0).toUpperCase() + v.substring(1);
}
const Person = mongoose.model('Person', personSchema);
// Create a doc and load it as a lean doc
await Person.create({ firstName: 'benjamin', lastName: 'sisko' });
const normalDoc = await Person.findOne();
const leanDoc = await Person.findOne().lean();
normalDoc.fullName; // 'Benjamin Sisko'
normalDoc.firstName; // 'Benjamin', because of `capitalizeFirstLetter()`
normalDoc.lastName; // 'Sisko', because of `capitalizeFirstLetter()`
leanDoc.fullName; // undefined
leanDoc.firstName; // 'benjamin', custom getter doesn't run
leanDoc.lastName; // 'sisko', custom getter doesn't run
Lean and Populate精益和大众化
Populate works with Populate使用lean()
. lean()
进行工作。If you use both 如果同时使用populate()
and lean()
, the lean
option propagates to the populated documents as well. populate()
和lean()
,则lean
选项也会传播到已填充的文档。In the below example, both the top-level 'Group' documents and the populated 'Person' documents will be lean.在下面的示例中,顶层的“Group”文档和填充的“Person”文档都将是精简的。
// Create models
const Group = mongoose.model('Group', new mongoose.Schema({
name: String,
members: [{ type: mongoose.ObjectId, ref: 'Person' }]
}));
const Person = mongoose.model('Person', new mongoose.Schema({
name: String
}));
// Initialize data
const people = await Person.create([
{ name: 'Benjamin Sisko' },
{ name: 'Kira Nerys' }
]);
await Group.create({
name: 'Star Trek: Deep Space Nine Characters',
members: people.map(p => p._id)
});
// Execute a lean query
const group = await Group.findOne().lean().populate('members');
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'
// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false
Virtual populate虚拟填充 also works with lean.也适用于精益。
// Create models
const groupSchema = new mongoose.Schema({ name: String });
groupSchema.virtual('members', {
ref: 'Person',
localField: '_id',
foreignField: 'groupId'
});
const Group = mongoose.model('Group', groupSchema);
const Person = mongoose.model('Person', new mongoose.Schema({
name: String,
groupId: mongoose.ObjectId
}));
// Initialize data
const g = await Group.create({ name: 'DS9 Characters' });
await Person.create([
{ name: 'Benjamin Sisko', groupId: g._id },
{ name: 'Kira Nerys', groupId: g._id }
]);
// Execute a lean query
const group = await Group.findOne().lean().populate({
path: 'members',
options: { sort: { name: 1 } }
});
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'
// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false
When to Use Lean何时使用Lean
If you're executing a query and sending the results without modification to, say, an Express response, you should use lean. 如果您正在执行查询并在不修改的情况下将结果发送到Express响应,那么您应该使用lean。In general, if you do not modify the query results and do not use custom getters, you should use 通常,如果您不修改查询结果,也不使用自定义getter,那么应该使用lean()
. lean()
。If you modify the query results or rely on features like getters or transforms, you should not use 如果修改查询结果或依赖getter或transforms等功能,则不应使用lean()
.lean()
。
Below is an example of an Express route that is a good candidate for 下面是一个快速路线的例子,它是lean()
. lean()
的一个很好的候选者。This route does not modify the 这条路线不修改person
doc and doesn't rely on any Mongoose-specific functionality.person
文档,也不依赖于任何Mongoose特定的功能。
// As long as you don't need any of the Person model's virtuals or getters,
// you can use `lean()`.
app.get('/person/:id', function(req, res) {
Person.findOne({ _id: req.params.id }).lean().
then(person => res.json({ person })).
catch(error => res.json({ error: error.message }));
});
Below is an example of an Express route that should not use 下面是一个不应该使用lean()
. lean()
的Express路由示例。As a general rule of thumb, 一般来说,GET
routes are good candidates for lean()
in a RESTful API. GET
路由是RESTful API中lean()
的理想候选。On the other hand, 另一方面,PUT
, POST
, etc. routes generally should not use lean()
.PUT
、POST
等路由通常不应该使用lean()
。
// This route should **not** use `lean()`, because lean means no `save()`.
app.put('/person/:id', function(req, res) {
Person.findOne({ _id: req.params.id }).
then(person => {
assert.ok(person);
Object.assign(person, req.body);
return person.save();
}).
then(person => res.json({ person })).
catch(error => res.json({ error: error.message }));
});
Remember that virtuals do not end up in 请记住,虚拟机最终不会出现在lean()
query results. lean()
查询结果中。Use the mongoose-lean-virtuals plugin to add virtuals to your lean query results.使用mongoose精益虚拟机插件将虚拟机添加到精益查询结果中。
Plugins插件
Using 使用lean()
bypasses all Mongoose features, including virtuals, getters/setters, and defaults. lean()
可以绕过Mongoose的所有特性,包括虚拟特性、getters/ssetter和默认特性。If you want to use these features with 如果你想在lean()
, you need to use the corresponding plugin:lean()
中使用这些功能,你需要使用相应的插件:
However, you need to keep in mind that Mongoose does not hydrate lean documents, so 然而,您需要记住,Mongoose不会对精简文档进行水合,因此this
will be a POJO in virtuals, getters, and default functions.this
将是虚拟、getter和默认函数中的POJO。
const schema = new Schema({ name: String });
schema.plugin(require('mongoose-lean-virtuals'));
schema.virtual('lowercase', function() {
this instanceof mongoose.Document; // false
this.name; // Works
this.get('name'); // Crashes because `this` is not a Mongoose document.
});