Быстрые запросы Mongoose с Lean
Опция lean сообщает Mongoose, что нужно пропустить гидратацию документов результата. Это ускоряет запросы и снижает потребление памяти, но документы результата будут обычными объектами JavaScript (POJO), а не документами Mongoose. В этом руководстве вы узнаете больше о tradeoffs использования lean().
Использование Lean
По умолчанию запросы Mongoose возвращают экземпляр класса Mongoose Document. Документы значительно больше, чем обычные объекты JavaScript, потому что они содержат много внутренней информации для отслеживания изменений. Включение опции lean сообщает Mongoose о пропуске создания полного документа Mongoose и возвращает вам просто POJO.
const leanDoc = await MyModel.findOne().lean();
Насколько меньше документы lean? Вот сравнение.
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. This additional memory only affects how much memory your
// Node.js process uses, not how much data is sent over the network.
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true
Под капотом, после выполнения запроса, Mongoose преобразует результаты запроса из POJO в документы Mongoose. Если вы включите опцию 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'
Недостатком включения lean является то, что у документов lean отсутствуют:
- Отслеживание изменений
- Преобразование и валидация
- Геттеры и сеттеры
- Виртуальные поля
save()
Например, следующий фрагмент кода демонстрирует, что геттеры и виртуальные поля модели Person не выполняются, если вы включили lean.
// 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 и Populate
Populate работает с lean(). Если вы используете и populate() и lean(), опция lean распространяется и на популированные документы. В примере ниже, как верхнеуровневые документы 'Group', так и популированные документы 'Person' будут lean.
// 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
Виртуальная популяция также работает с 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
Когда использовать Lean
Если вы выполняете запрос и отправляете результаты без изменений, например, в ответ Express, вы должны использовать lean. В целом, если вы не изменяете результаты запроса и не используете пользовательские геттеры, вы должны использовать lean(). Если вы изменяете результаты запроса или полагаетесь на такие функции, как геттеры или преобразования, вам не следует использовать lean().
Ниже приведен пример маршрута Express, который является хорошим кандидатом для lean(). Этот маршрут не изменяет документ 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 }));
});
Ниже приведен пример маршрута Express, который не должен использовать lean(). Как общее правило, маршруты GET являются хорошими кандидатами для lean() в RESTful API. С другой стороны, маршруты 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 }));
});
Помните, что виртуальные поля не попадают в результаты запроса lean(). Используйте плагин mongoose-lean-virtuals, чтобы добавить виртуальные поля в результаты lean запросов.
Плагины
Использование lean() обходит все функции Mongoose, включая виртуальные поля, геттеры/сеттеры и значения по умолчанию. Если вы хотите использовать эти функции с lean(), вам нужно использовать соответствующий плагин:
Однако, необходимо помнить, что Mongoose не гидратирует lean документы, поэтому this будет 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.
});
BigInt
По умолчанию, драйвер MongoDB Node преобразует long, сохранённые в MongoDB, в числа JavaScript, а не BigInt. Установите опцию useBigInt64 в ваших запросах lean(), чтобы преобразовать long в BigInt.
const Person = mongoose.model('Person', new mongoose.Schema({
name: String,
age: BigInt
}));
// Mongoose will convert `age` to a BigInt
const { age } = await Person.create({ name: 'Benjamin Sisko', age: 37 });
typeof age; // 'bigint'
// By default, if you store a document with a BigInt property in MongoDB and you
// load the document with `lean()`, the BigInt property will be a number
let person = await Person.findOne({ name: 'Benjamin Sisko' }).lean();
typeof person.age; // 'number'
// Set the `useBigInt64` option to opt in to converting MongoDB longs to BigInts.
person = await Person.findOne({ name: 'Benjamin Sisko' }).
setOptions({ useBigInt64: true }).
lean();
typeof person.age; // 'bigint'
© 2010 LearnBoost
Licensed under the MIT License.
https://mongoosejs.com/docs/tutorials/lean.html