본문 바로가기
Database/MongoDB

Mongoose Document 생성과 CRUD 구현 # NodeJS mongoDB

by Developer88 2021. 5. 26.
반응형

오늘은 MongoDB로 CRUD를 구현하는 방법에 대해서 정리해 보도록 하겠습니다.

 

 

MongoDB의 설치는 MongoDB Atlas설치에 관한 글을 참조해주세요.

신용카드 등록없이 무료로 용량을 제공하는 클라우드 서비스에 접속해서 테스트할 수 있습니다.

로컬에 설치해서 사용할수도 있겠지만, 이 방법이 훨씬 효율적인 것 같습니다.

이것에 대한 글은 아래 글을 참조해 주시구요.

>> MongoDB Atlas 무료 로 사용하기

 

로컬로 설치하는 예전방식에 대한 글은 아래 글을 참조해 주세요.

>> MongoDB 설치 및 실행 총정리 # MongoDB NodeJS

 

1.  MongoDB의 Database 와 Collection 그리고 Document 의 개념

나무를 보기전에 숲을 한번 보면 더욱 큰 도움이 될텐데요.

Mongoose에 대해서 알아보기전에,

MongoDB의 Database, Collection 그리고 Document의 관계에 대해서 간단히 보고 가겠습니다.

 

구분  
Database MongoDb의 가장 큰 단위.
MySQL이 여러개의 Table을 가질 수 있는 것처럼, Document도 여러개의 Collection을 가질 수 있다.
Collection MySQL의 Table과 유사함.
Binary Json인 Bson Documents 가 저장되어 있음.
Collection안에 Docuent가 존재함.

Collection안의 document들은 서로 다른 필드를 가질 수 있다.

Document 키와 밸류로 이루어진 BSON 데이터.

 

참고로 Document는 BSON으로 되어 있다고 하였는데요.

BSON의 단어 자체도 Binary JSON이기도 하구요.

JSON과 크게 다른 점은 없습니다.

Binary로 Serialize한 document로서 아래와 같은 추가적인 기본 타입들을 가지고 있으며,

Binary 타입으로 인코딩하여 빠르고 효율적이라고 합니다.

 

 

API문서를 볼 때나 MongoDB에 대해서 이해하고자 할 때, 이 세 가지의 관계에 대해서 만큼은 알고 있어야 합니다.

 

Database > Collections > Documents

 

MongoDB Shell을 이용해서 실제로 CRUD를 해보면서 감을 익히고 싶은 분들은 아래 글을 참조해 주세요.

>> MongoDB Database 와 Collection 그리고 Document CRUD 하기

 

2. Mongoose

Mongoose는 NodeJS에서 사용하는 MongoDB의 ODM(Object Data Mapping) 모듈입니다.

ODM은 개발언어의 Object와 MongoDB의 데이터를 Mapping 해 주는 역할을 해 주는데요.

 

MongoDB에서 직접 사용하는 Query문의 형태가 아니라, Mongoose API의 함수들을 이용하게 됩니다.

이해하기도 쉽고, Query문 오타에 의한 에러도 방지할 수 있으며, 디버깅도 훨씬 쉽게 할 수 있겠지요.

이제 아래에서 Mongoose를 설치해보고, CRUD를 해 보도록 하겠습니다.

 

3. Mongoose 설치 와 require

가장 먼저 할 것은 Module의 설치인데요.

아래 명령어를 통해서, Module을 설치해 줍니다.

 

$ npm install mongoose --save

 

먼저 아래와 같이 require해서 사용을 하게 되는데요.

 

const mongoose = require('mongoose');

 

이제 준비가 다 되었으니, DB에 Connect해 보도록 하겠습니다.

 

4.  MongoDB 연결

가장 먼저 해야할 것은 MongoDB에 연결하는 것 인데요.

connect()함수에 몽고DB의 연결 커넥션을 사용해서 연결하면 됩니다.

"몽고DB 커넥션 URL"에는 아이디와 비번, 그리고 Database명이 포함되어 있습니다.

 

const mongoose = require('mongoose');
mongoose.connect('몽고DB URL');

 

제가 실제로 사용할 때는 코드에 ID와 비번 등을 넣어서 하는 것이 아니라,

dotenv라는 모듈을 이용해서,

환경변수에 아이디와 패스워드를 저장해서 아래와 같이 사용하고 있습니다.

참고로 connect의 url 중 회색으로 가려진 부분은,

위에서 본 MongoDB의 가장 큰 단위인 데이터베이스의 이름입니다.

 

예전의 로컬방식으로 연결할 경우는, 아래와 같이 할 수 있습니다.

첫번째 인자로 들어가는 값은 db의 주소구요.

27017은 포트 값이며, mongo_test는 데이터베이스의 이름입니다.

여기서는 mongo_test를 사용하였습니다.

 

mongoose.connect(
	'mongodb://127.0.0.1:27017/mongo_test', 
	{ useNewUrlParser: true, useUnifiedTopology: true }
);

 

MongoDB는 다행히도 Promise를 지원하므로, 아래와 같이, 연결시와 error시의 코드를 작성해 줄 수 있습니다.

 

mongoose
    .connect("<몽고DB URL>")
    .then(() => {
        console.log("we're connected!");
    })
    .catch((err) => {
        console.error(`connection error: ${err.message}`)
    });

 

 

저는 dotenv를 이용해서 환경변수에 id와 비번을 저장해서 사용하는데요.

최종적으로는 아래와 같이 구성할 수 있습니다.

 

 

NodeJS를 실행해 보면, "connected"를 보게 됩니다.

 

5. Schema 와 Model

원래 MongoDB에는 MySQL같은 곳에서 사용하던 Schema가 존재하지 않는데요.

전통 RDBMS인 MySQL에서는 미리 Schema의 구조를 짜놓고, 그에 맞게 데이터를 들어가도록 하고 있었고,

NoSQL의 MongoDB는 Schema없이 Document를 저장할 수 있도록 하고 있었습니다.

하지만 Mongoose의 경우는 Schema를 정해놓고 데이터를 입력하도록 하고 있는데요.

이것의 이유에 대해서는 또 다른 글의 주제가 될 것이므로 여기서는 넘어가도록 하겠습니다.

 

다만, MongoDB자체는 원래 Schema가 없어도 되는데, 

NodeJS모듈인 Mongoose는 그것을 반드시 필요로 한다는 부분을 알고 있으면 되겠지요.

 

Mongoose에는 모든 것의 시작점이 Schema라고 공식문서는 말하고 있습니다.

실제로 Mongoose는, 정의된 Schema 객체로부터, Model생성자를 이용하여서 객체를 만들어 내는데요.

Model에 의해 생성된 객체를 이용해, Document를 Read 또는 Write 하게 됩니다.

Schema -> Model -> Document의 순서를 갖게 되는 것 입니다.

 

5-1. Schema Type 들

아래는 Mongoose에서 사용할 수 있는 데이터타입들 입니다.

Schema를 정의할 때 알아두어야 겠지요.

  • String
  • Number
  • Boolean
  • Date
  • Buffer
  • Mixed(모든 타입의 데이터)
  • ObjectId
  • Array
  • Decimal128
  • Map
  • Schema

 

5-2. ID

Mongoose는 _id프로퍼티를 자동으로 스키마에 추가해 줍니다.

따라서 생성된 모든 document의 _id에 접근할 수 있습니다.

 

5-3. Schema의 생성

Schema는 다음과 같이 정의해서 생성할 수 있습니다.

아래에서는 다큐먼트의 name은 String타입으로, score는 Number으로 정의하였습니다.

 

const studentSchema = new mongoose.Schema({
    name: String,
    score: Number
})

 

Schema들을 다음과 같이 TypeOption들을 추가할 수 있습니다.

아래 movieTitle의 type은 String이고,

typeOption인 lowercase를 true로 해 주어서, 소문자로만 저장되도록 하였습니다.

 

const movieSchema = new Schema({
  movieTitle: { 
    type: String,
    lowercase: true 
  } 
});

 

 

자주사용하는 있는 typeOption들은 다음과 같습니다.

 

Type Option들 의미
required boolean 또는 function true 일경우, 값이 필수적으로 들어가지 않은면 error를 발생시킵니다.
default Any 또는 function 디폴트 값을 설정
function일 경우, return값이 default 값으로 설정된다.
lowercase boolean 소문자로 변환되도록 함
uppercase boolean 대문자로 변환되도록 함
enum Array validator를 생성해서 값이 정해진 array안에 들어있는지 확인
minLength Number 최소길이 설정
maxLength Number 최대길이 설정
timestamps Date createdAt이나 updatedAt필드를 자동으로 추가해 줍니다.
여기에 들어가는 값은 new Date()를 통해 얻어냅니다.

timestamps.createdAt 이나 timestamps.updatedAt 에 접근해서,
아래와 같이 필드이름을 변경할 수도 있습니다.

 { timestamps: { createdAt: 'created_at' } }

 

위 옵션들 중 timestamps 옵션의 경우는 아래와 같이 사용할 수 있습니다.

 

 

이 밖에도 많은 option들이 있는데요.

이에대한 공식문서의 링크는 아래와 같습니다.

>> https://mongoosejs.com/docs/schematypes.html

 

이제, 기본적인 부분들을 알았으니, CRUD를 실제 구현해 보도록 하겠습니다.

 

5-4. Model 생성

위에서 언급했던, Schema -> Model -> Document를 기억하시나요?

Schema를 생성해 보았으니, Model을 생성해 볼 차례입니다.

 

Model은 Mongoose객체의 model() 함수를 사용해서 아래 코드와 같이 생성을 하는데요.

첫번째 인자로 들어간 "Movie"는 Collection의 이름입니다.

위에서 정리하였던 "Database > Collection > Document"가 기억나시지요.

Collectoin은 MySQL에서의 Table과 유사합니다.

Mongoose에서 이러한 Collection의 이름은 대문자 단수를 사용하여 생성하구요.

두번째 인자로 들어가는 것이, 위에서 생성한 Schema입니다.

 

const Movie = mongoose.model("Movie", movieSchema);

 

 

한가지 참고할 것은, Collection명칭은 mongoose에서는 단수형태로 사용하지만,

MongoDB에서 아래와 같이 조회해 보면, 소문자복수로 저장한다는 것을 알 수 있습니다.

"db.movies"로 조회해야 정상적인 값을 얻어울 수 있습니다.

Mongoose와는 다르게, MongoDB에서는 "db.Movie"로 조회하면 아무값도 얻어올 수 없습니다.

 

 

6.  Document 생성 (Create)

Model을 구현하였으므로, 이제 객체를 생성할 준비가 다 되었네요.

먼저 Save API를 이용해서 Document를 생성(Create)해 보겠습니다.

 

6-1. Save()

다음과 같은 순서로 Document를 생성해 보는데요.

  1. student Schema를 작성
  2. Document를 생성할, Student model생성자를 작성해 줍니다.
  3. Student생성자를 이용해서 student1을 생성
  4. save()메소드를 이용하여 저장

위와 같은 순서로 아래와 같이 코드를 작성해 보았습니다.

 

 

 

위 코드를 실행결과를 MongoDB의 shell에서 조회해 보면, 아래와 같이 잘 생성된 것을 볼 수 있습니다.

 

 

 

공식문서의 설명을 보면,

save() API는 document.isNew가 true를 반환하면 새로운 document를 insert 해 주구요.

false라면, updateOne()을 합니다.

 

 

6-2. insertMany()

이것은 여러개의 document들을 생성하도록 해 주는 Model의 API입니다.

document들의 array가 유효할(validated) 경우, 문서들을 insert 해 줍니다.

사용방법도 아래와 같이 간단합니다

insertMany의 return 타입은 Promise입니다.

 

const arr = [{ movieTitle: 'Movie1' }, { movieTitle: 'Movie2' }];
Movie.insertMany(arr, function(error, docs) {});

 

아래와 같이 async, await를 활용해서 생성할 수 있습니다.

 

 

 

6-3. ObjectId

MongoDB에서는 document가 생성될 때마다 ObjectId를 생성시킵니다.

MongoDB에서는 총12byte의 길이로(24개의 16진수로 구성되어 있는) 아래와 같은 값을 가지고 있으며, Object입니다.

정수로 되어 있으면, 생성순서등을 알아보기 편하겠지만, sharding등을 하는데 있어서 문제가 발생할 수 있기 때문에,

이러한 방식을 취하고 있습니다.

 

5e1a0651741b255ddda996c4

 

임으로 ID를 설정할 때는 12byte, 즉 24개의 16진수로 구성된 값을 넘겨주면,

cast해서 ObjectId로 변환해 줄수 있는데요.

이를 이용해서 아래와 같이 임의의값으로 설정할 수도 있겠지요.

 

const movieSchema = mongoose.Schema({ movieId: mongoose.ObjectId });
const Movie = mongoose.model('Movie, movieSchema);
const doc = new Movie({ movieId: '5e1a0651741b255ddda996c4' });

 

참고로 ObjectId객체의 getTimeStamp() 함수를 이용하면 생성된 시간을 얻어올 수도 있습니다.

 

const movieSchema = mongoose.Schema({ movieId: mongoose.ObjectId });
const Movie = mongoose.model('Movie, movieSchema);
const doc = new Movie({ movieId: '5e1a0651741b255ddda996c4' });
doc.movieId.getTimestamp();

 

7.  Query (Read)

이번에는 위에서 생성한 것을 mongoose로 조회해 보도록 하겠습니다.

mongoose는 function을 인자로 전달하면, 비동기로 쿼리를 실행해서,

결과값으로 callback(error, result)을 전달해 줍니다.

 

이에대해 알아보기전에, Mongoose의 Query와 Promise에 대해 알아볼 필요가 있는데요.

이야기가 조금 다른 줄기로 흘러가는 것 같지만, 필요한 부분이니 보고 가도록 하겠습니다.

 

7-1.  Mongoose의 Query

Query를 하기 위해서 mongoose에서는 Model에 find()함수를 다음과 같은 형식으로 사용해 주면 되는데요.

각각의 인자들은 생략할 수 있습니다.

 

Model.find(filter, projection, options, callback)

 

아래와 같이 사용할 수 있는 것 이지요.

q1은  name이 '김' 인 document이면서 score가 50이상인 문서를 찾는 것 입니다.

(gte는 옵션 설정시 이상의 값을 말하구요. lte이는 이하가 됩니다.)

q2는 결과값으로 name과 score 필드만 select 해 준 것입니다.

q3는 10개를 skip한 값만 조회하였습니다.

 

const q0 = MyModel.find();
const q1 = MyModel.find({ name: '김', score: { $gte: 50 } });
const q2 = MyModel.find({ name: "김" }, 'name score');
const q3 = MyModel.find({ name: "김" }, null, { skip: 10 });

 

 

7-2.  Mongoose의 Query와 Promise

find()함수를 호출하면, Query타입을 return해 줍니다.

이 Query타입에는 Promise에서 쓰던 then()을 사용할 수 있구요. async와 await도 사용할 수 있는데요.

그래서, Promise가 아닐까 생각하게 되는데요. 그렇지 않구요.

오히려, querypromise가 아니라는 점에 주의를 해주어야 합니다.

 

 

promise와는 다르게, query에 사용하는 then()은 query를 여러번 실행시킬 수 있습니다.

아래와 같이, then()을 여러번 사용하면 조회도 query로 여러번 실행되게 됩니다.

 

const q = MyModel.find();
q.then(() => console.log('query 2'));
q.then(() => console.log('query 3'));

 

이외에도, query에 콜백함수를 인자로 넣어주는 것도 주의를 해야합니다.

콜백함수를 전달하는 순간 query가 실행되게 되구요.

그 뒤에, then()을 실행하면 또다시 query를 중복으로 실행하게 되기 때문입니다.

 

복잡하게 여러번 호출하게 되지 않을 때는,

query타입으로 return받아서 사용하면 되구요.

Promise타입으로 return받고 싶다면, 아래의 exec() API를 사용해 주면 됩니다.

 

 

7-3. exec()

".exec()" 함수를 사용하면 Promise타입으로 return 받아서, 아래 공식문서 설명과 같이 사용할 수 있습니다.

async와 await를 사용하면서 거의 습관적으로 사용하게 되버리는데요.

이것을 사용함으로서 생기는 문제들에 대해서는 문서에 나와있지 않습니다.

 

 

아래와 같이, return되는 Query타입에 exec()를 사용해 주기만 하면 됩니다.

 

const movies = await Movie.find().exec()

 

 

아래와 같이 find에 filter를 넣거나,  원하는 필드만 선택할 수도 있습니다.

 

await MyModel.find({ name: '김', score: { $gte: 50 } }).exec();
await MyModel.find({ name: "김" }, 'name score').exec();
await MyModel.find({ name: "김" }, null, { skip: 10 }).exec();

 

7-4. 다양한  Query API들

Query를 할 수 있는 API에는 아래와 같이 다양하게 있습니다.

 

  • Model.find()
  • Model.findById()
  • Model.findByIdAndDelete()
  • Model.findByIdAndRemove()
  • Model.findByIdAndUpdate()
  • Model.findOne()
  • Model.findOneAndDelete()
  • Model.findOneAndRemove()
  • Model.findOneAndReplace()
  • Model.findOneAndUpdate()

 

7-5. sort()

Query의 API인 sort는 아래와 같이 오름차순과 내림차순을 값으로 넣어질 수 있습니다.

asc로 하건, ascending 으로 하건, 1로 넣던 상관은 없습니다.

  • 오름차순
    • asc, ascending, 1
  • 내림차순
    • desc, descending, -1

 

아래와 같이 두가지 필드에 각각 다른 sorting을 적용할 수도 있습니다.

 

await Model.find().sort({name:'asc', score:'desc'})

 

 

위의 명령은 아래와 같이 간단하게 할 수도 있습니다.

 

await Model.find().sort('name -score')

 

참고로, min이나 max를 MongoDB에 사용할수는 있지만, 권장하고 있지는 않습니다.

document를 find해서 sorting한 다음에 그 값을 사용하기를 권장하고 있네요.

아래와 같이 내림차순으로 하나의 document를 찾아서 사용하면 되겠네요.

 

 

8. FindAll 과 Cursor

한꺼번에 많은 데이터를 조회하거나 해야할 때, 

많은 데이터를 조회해서 메모리에 올리는 것은 서버에 악영향을 끼치게 되겠지요.

모든 Document를 전부 다 조회해야할 때는 비동기 API인 Cursor를 사용하면,

메모리에 모든 Document를 한번에 올리지 않고 조회할 수 있습니다.

return 타입은 QueryCursor인데요. 

 

 

QueryCursor는 아래와 같이 나와있는데요, 

NodeJS의 stream3 API스펙을 준수하는 비동기 조회 API입니다.

 

 

그래서 따로 await를 붙여줄 필요도 없습니다.

Model의 find API로 query를 불러와서, cursor() 를 호출해주면, QueryCursor타입의 객체를 받아오구요.

이것을 아래와 같이 for문과 next()를 이용해서 조회해주면 됩니다.

 

 

 

Cursor API말고, NodeJS의 async iterator라는 비동기 API를 사용하는 것도 가능한데요.

그럼, 아래와 같이 매우 simple하게 사용할수도 있습니다.

 

 

9. update

이번에는 Mongoose를 이용해 DB를 업데이트 하는 방법에 대해서 알아보도록 하겠습니다.

 

9-1. save()

위에서 생성할 때 사용했던 save()는 업데이트 할 때에도 같이 사용할 수 있습니다.

save() 가 Document의 API이므로, 먼저 document를 생성하거나 find()로 찾아주어야 합니다.

document를 찾거나 생성하였다면, 아래와 같이 간단하게 save()를 이용하여 업데이트할 수 있습니다.

 

movie.movieTitle = 'testMovie';
await doc.save();

 

save()가 validation과 middleware를 모두 지원하므로, 가능한 경우 우선순위에 두고 사용하라고 공식문서에 나와있습니다.

save()실행시 update된 document를 return값으로 돌려줍니다.

chage값을 tracking하는 것도 save() API만 제공해 줍니다.

 

9-2. updateOne과 updateMany

Model의 API인 updateOne과 updateMany를 이용하면,

굳이 document를 조회하지 않고도 바로 update가 가능합니다.

만약, params로 id를 받았다면, 아래와 같이 조회없이 한줄로 업데이트가 가능하겠지요.

return되는 값에 n과 nModified로 매칭된 문서수와 변경된 문서수 정보를 받아올 수 있습니다.

 

const res = await Movie.updateOne({_id: ctx.params.id},{movieTitle: ctx.request.body.movieTitle})
res.n; // Number of documents matched
res.nModified; // Number of documents modified

 

updateOne의 큰 장점은 atomic 하다는데 있습니다.

save()를 사용할 경우, 중간에 데이터가 바뀌는 것에 대해서 대응을 할 수 없지만,

updateOne은 그러한 것이 가능하다는 것 이지요.

 

 

updateMany도 다르지 않습니다.

공식문서에서는 아래와 같이 이름이 정규식 "/Stark$/"에 해당하는 문서들의 isDeleted값을 모두 true로 변경해주고,

n과 nModified에서 매칭된 문서수와 변경된 분서의 수를 읽어올 수 있습니다.

 

const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
res.n; // Number of documents matched
res.nModified; // Number of documents modified

 

save()와 큰 차이라고 할 수 있는 점은, save()실행시 update된 document를 return값으로 돌려주는데요.

updateOne과 updateMany는 그렇지 못합니다.

 

이외에도 Document API에서도 updateOne()을 제공해주지만,  뚜렷한 장점이 있는 것 같지는 않습니다.

Model API의 updateOne()에서 제공해주는 것같이, atomic한 장점도 없구요.

document가 있는 상태라면, save()를 사용하는 것이 대부분의 경우에 좋을 것 같습니다.

 

9-3. findOneAndUpdate() 와 findByIdAndUpdate

A. findOneAndUpdate

save()와 updateOne()을 합쳐놓은 것 같은 API가 바로 Model의 findOneAndUpdate()입니다.

updateOne과 같이 Atomic한 장점을 유지하면서도, return값으로 update된 문서를 제공해줍니다.

따로, document를 찾아놓거나 생성할 필요가 없으므로, updateOne()가 마찬가지로 사용방법도 간단합니다.

 

사용할 수 있는 옵션값들은 다음과 같습니다.

 

  • new: bool - 디폴트값은 false, 만약 true이면, 수정된 document를 return 해 줍니다.
  • upsert: bool - 디폴트값은 false, true일 경우, 데이터가 없으면 생성해 줍니다.
  • overwrite: bool - true일 경우, 전체 document를 overwrite해 줍니다.
  • fields: {Object|String} - Field selection. Equivalent to .select(fields).findOneAndUpdate()
  • maxTimeMS: query에 걸리는 최대시간을 정합니다. - requires mongodb >= 2.6.0
  • sort: 여러가지 document가 조회될 경우, sort 순서를 정합니다.
  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.
  • setDefaultsOnInsert: 이 옵션과 upsert옵션이 true일 경우는, 값이 없어서 새로운 Document생성시에, model의 schema에 지정된 디폴트값을 적용합니다.
  • rawResult: true 일경우, MongoDB driver의 raw result 값을 return 해 줍니다.  
  • strict: 이번 update를 위해서, strict mode옵션을 overwrite 해 줍니다.

 

await Movie.findOneAndUpdate({_id: ctx.params.id},{movieTitle: ctx.request.body.movieTitle})

 

 

B. findByIdAndUpdate

findByIdAndUpdate는 findOneAndUpdate({ _id: id }, ...)와 같습니다.

코드를 줄여주고, 더 명확해지는 효과 정도가 있겠지요.

아래와 같은 형식으로 쉽게 사용할 수 있습니다. 

위에서 사용한 option들을 모두 사용할 수 있습니다.

 

A.findByIdAndUpdate(id, update, options)  // returns Query

 

실제로는 아래와 같이 await를 이용해서 사용해 볼 수 있겠지요.

 

await Movie.findByIdAndUpdate(ctx.params.id, {movieTitle: ctx.request.body.movieTitle})

 

10. Upsert

10-1. upsert

위에서도 잠깐 보았지만, upsert 옵션을 true로 하면, findOne이나, findById를 통해서, 

기존에 문서가 있으면 Update를 하고, 없으면 Create를 할 수 있습니다.

 

const res = await Movie.updateOne(
  { movieTitle: 'starWars' },
  { movieTitle: 'starWars8' },
  { upsert: true }
);

res.nModified;
res.upserted; //array로 document에 대한 정보를 얻을 수 있다

 

 

upsert한 다음, 해당 문서를 얻고자 한다면,  아래와 같이 findOneAndUpdate()를 upsert 옵션과 함께 사용해 주면 됩니다.

 

const upsertedDoc = await Movie.updateOneAndUpdate(
  { movieTitle: 'starWars' },
  { movieTitle: 'starWars8' },
  { upsert: true, new: true }
);

console.log(`upsertedDoc: ${upsertedDoc.movieTitle}`)

 

11. Delete

11-1. deleteOne()

deleteOne은 Query와 Model에 모두 사용할 수 있는 API로 filter를 통해 찾아진 문서의 첫번째만 지우게 됩니다.

Query의 API를 사용할 경우, return타입은 Query입니다. Promise타입으로 반환하고 싶다면, execute()를 사용해 주어야 하겠지요.

 

await Movie.deleteOne({ name: 'movie6' });
Movie.deleteOne({ name: 'movie6' }, callback); //콜백을 사용하는 것도 가능

res.deletedCount;   // 지워진 문서의 수
res.n;   // 지워진 문서의 수, res.deletedCount와 동일
res.ok;  // error가 없으면 1

 

11-2. deleteMany()

위에서 본 deleteOne과 동일하지만, 여러개의 문서를 한번에 삭제할 수 있습니다.

아래와 같이 필터를 주지않는다면, 해당 콜랙션의 모든 문서가 삭제되겠지요.

 

Movie.deleteMany()

 

아래와 같이 정규식을 이용해서 문서를 찾고, 

특정 조건에 따라 삭제하는 것도 가능합니다.

아래는 reviewScore가 3이상인 문서를 지우게 하였네요.

 

await Movie.deleteMany({ name: /영화/, reviewScore: { $gte: 3 } });

res.deletedCount;   // 지워진 문서의 수
res.n;   // 지워진 문서의 수, res.deletedCount와 동일
res.ok;  // error가 없으면 1

 

마지막 인자에 콜백을 넣어서 실행시킬 수도 있습니다.

 

await Movie.deleteMany({ name: /영화/, reviewScore: { $gte: 3 }, <실행할 콜백 함수> });

 

11-3. Model.findOneAndDelete()

Model의 API인 findOneAndDelete()도 존재합니다.

filter를 주거나 주지 않을 수 있습니다.

 

A.findOneAndDelete(conditions, options, callback) // 콜백실행
A.findOneAndDelete(conditions, options)  // return Query타입
A.findOneAndDelete(conditions, callback) // 콜백 실행
A.findOneAndDelete(conditions) // return Query타입
A.findOneAndDelete()           // return Query타입

 

option값으로는 아래와 같은 값들을 설정할 수 있습니다.

  • sort: 만약 여러 document가 나올경우, sorting옵션을 설정
  • maxTimeMS: query하는데 걸리는 max시간을 설정 - requires mongodb >= 2.6.0
  • select: 원하는 필드만 설정, ex. { projection: { _id: 0 } }
  • projection: select와 동일
  • rawResult: true인경우, MongoDB driver의 raw값을 그대로 return
  • strict: schema의 strict mode option 을 이번 update만 overwrite 해 줍니다.

 

11. bulkWrite

이름에서 알 수 있듯이, bulk로 한번에 여러개의 Document를 create 또는 update해 줄 수 있습니다.

물론 create() API를 사용해도 여러개의 문서를 생성할 수 있는데요.

bulkWrite를 하면, 아래 명령어를 모두 한번에 처리해 줄 뿐만 아니라, upsert도 가능합니다.

  • insertOne, 
  • updateOne, updateMany, 
  • replaceOne, 
  • deleteOne, deleteMany

게다가 return 값이 return타입이 Promise여서, 사용하기도 편합니다.

아래와 같이 사용해 볼 수 있습니다.

Movie.bulkWrite([
  {
    insertOne: {
      document: {
        movieTitle: 'movie1'
      }
    }
  },
  {
    updateOne: {
      filter: { movieTitle: 'movie2' },
      update: { movieTitle: 'movie6' },
      upsert: true
    }
  },
  {
    deleteOne: {
      {
        filter: { name: 'movie9' }
      }
    }
  }
]).then(res => {
 console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
});

 

 

12. 팁 및 주의할 점

12-1. debug모드로 실행

debug 모드로 실행할 경우에, MongoDB로 mongoose가 보내는 operation이 print됩니다. 

설정하는 방법은 위와같이 아주 간단합니다.

 

mongoose.set('debug', true)

 

이상으로 Mongoose를 이용해서, CRUD를 해 보았습니다.

추가적으로 필요한 것이나 수정할 부분은 이 글에서 업데이트 하도록 하겠습니다.

 

728x90

댓글