지금까지 Document들에 대해서 공부를 했는데 이제는 Document를 찾는 방법인 Query를 다루는 방법에 대해서 공부를 하려고 한다.
우리가 Callback 함수가 있는 쿼리를 실행할 때 JSON document 처럼 쿼리문을 구체적으로 작성하면 된다.
const Person = mongoose.model('Person', yourSchema);
Person.findOne({'name.last': 'Ghost}, 'name occupation', function (err, person) {
if (err) return handleError(err);
console.log('$s $s is a %s.', person.name.first, person.name.last, person.occupation);
});
Mongoose는 쿼리를 실행 한 후 결과를 callback으로 전달을 하는데 Mongoose에서는 주로 callback(error, result) 라는 형식을 사용한다.
예시) callback이 전달되지 않았을 때
const query = Person.findOne({'name.last': 'Ghost'});
query.select('name occupation');
query.exec(function (err, person) {
if (err) return handleError(err);
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation);
});
예시) JSON doc과 query builder의 구조
// JSON doc일 경우
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(callback);
// query builder일 경우
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(callback);
mongoose 쿼리는 promises가 아니다. 쿼리들은 .then 함수, async/await 함수들을 가지고 있지만 promises 함수와 다르게 .then 함수를 여러번 실행할 수 있다.
const q = MyModel.updateMany({}, {idDeleted: true}, function {
console.log('Update 1');
});
q.then(() => console.log('Update 2'));
q.then(() => console.log('Update 3'));
쿼리에서 callback 및 promise를 혼합해서 사용하면 안된다. 왜냐하면 callback을 넘기고 .then() 이 또다시 실행될 수 있기에 중복해서 작동이 된다. 아래 예시는 await와 callback 함수를 동시에 실행한 경우로 중복해서 작동이 된다.
const BlogPost = mongoose.model('BlogPose', new Schema({
title: String,
tags: [String]
}));
const update = {$push: {tags: ['javascript']}};
await Blogpost.updateOne({title: 'Introduction to Promises'}, update, (err, res) => {
console.log(res);
});
쿼리 실행 결과를 스트림하게 가져올 때에는 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);
}
or
for await (const doc of Person.find()) {
console.log(doc);
}
Cursor는 시간이 지나면 자동적으로 실행이 꺼지게 된다. 그래서 noCursorTimeout
옵션을 사용해 시간이 지나서도 계속 실행시킬 수 있다.
const cursor = Person.find().cursor().addCursorFlag('noCursorTimeout', true);
Aggregation은 쿼리와 똑같은 역할을 할 수 있다. 예를 들어, 아래처럼 aggregate()를 사용해 docs를 찾을 수 있다.
const docs = await Person.aggregate([{$match: {'name.last': 'Ghost'}}]);
그러나, aggregate를 사용할 수 있다는 것이 무조건 사용하라는 것은 아니다. 일반적으로, 가능하다면 쿼리문을 쓰는게 낫고 필요한 경우에만 aggregate를 사용하는 것이다.
쿼리문 결과와는 다르게, Aggregation은 POJOs의 형태로 결과가 나오고 Mongoose는 hydrate() 한 결과를 나타내지 못하고 있다.
const docs = await Person.aggregate([{$match: {'name.last': 'Ghost'}}]);
docs[0] instanceof mongoose.Document; // false
또한, 쿼리문 필터와 다르게 올바른 타입으로 값들이 전달하는 파이프라인을 Mongoose에서 지원해주지 않는다. 아래 예시처럼 쿼리문은 Person이라는 객체를 찾을 수 있지만 Aggregate는 파이프라인 지원이 없어서 Person을 찾을 수가 없다.
const doc = await Person.findOne();
const idString = doc._id.toString();
const queryRes = await Person.findOne({_id: idString});
const aggRes = await Person.aggregate([{$match: {_id: idString}}])