AI / DL/엘리스 SW 코딩 훈련 트랙

[SW 코딩 훈련] Express.js와 MongoDB 03. MongoDB와 Mongoose

bri9htstar 2022. 11. 29. 16:50

MongoDB

대표적인 NoSQL, Document DB

Mongo는 Humongous에서 따온 말로, 엄청나게 큰 DB 라는 의미다.

대용량 데이터를 처리하기 좋게 만들어짐

 

RDB

Relational Database

 

관계형 데이터베이스로 자료들의 관계를 주요하게 다룬다. 이 관계에서 SQL 질의어를 사용하기 위해 데이터를 구조화, 정형화해야 한다. RDB의 일반적인 형태는 우리가 사용하는 엑셀과 같은 표(table) 형태로 많이 이루어져 있다.

 

NoSQL

Non SQL 또는 Not Only SQL 

 

SQL 관계지향적 특징에서 벗어나 다양한 기능을 제공한다. 구조화된 질의어를 사용하지 않는 데이터베이스로, 자료 간의 관계에만 초점을 두진 않는다. 각각의 자료, 객체의 고유한 의미에도 초점을 둔다. 데이터를 구조화하지 않고, 유연하게 저장한다.

 

NoSQL을 사용하는 이유

SQL을 사용하기 위해서는 데이터를 구조화하는 것이 필수다.(DDL) 스키마(구조)에 정의된 데이터가 아니면 저장할 수 없는 제약이 따른다. NoSQL을 사용하면 사전작업 없이 데이터베이스를 사용할 수 있다. 데이터베이스에 바로 정보를 주고 받을 수 있다. 데이터베이스 작업에 크게 관여하지 않고 프로젝트를 빠르게 진행할 수 있다.

 

MySQL(RDB) vs MongoDB(NoSQL)

MySQL에서 중간 CREATE TABLE이 핵심이다. posts 라는 구조를 만들고 안에 각각의 열, 특징을 나열해서 구조에 맞게 INSERT 구문을 통해 데이터 삽입하는 형식이다.

MongoDB는 posts 명시하고 insert라고 하면 데이터를 구조에 짜맞추지 않고 그대로 단순하게 넣어준다. 왼쪽과 같은 케이스에서는 데이터를 어떻게 정의할지를 사전에 고민 해야하지만 오른쪽은 우선 데이터베이스를 사용할 수 있다는 큰 장점이 있어서 NoSQL이 널리 쓰인다.

 

NoSQL과 Document DB

NoSQL은 다양한 종류가 있지만, 대표적으로 자료를 Document(문서)로 저장하는 Document DB가 일반적이다. 이 외에, Key-value, Graph, large collection 등의 NoSQL DB가 존재한다. 유명한 Document DB로는 MongoDB가 있다.

 

MongoDB 기본 개념

MongoDB는 기본적으로 Database는 여러 개의 Collection으로 이루어져 있고, 여러 개의 Collection은 여러 개의 Document로 이루어져 있다고 본다. Document의 모음이 Collection이 되고, Collection 모음이 Database가 된다.

 

  • Database

하나 이상의 Collection을 가질 수 있는 저장소

SQL에서의 database와 유사

 

  • Collection

하나 이상의 Document가 저장되는 공간

SQL 에서의 table과 유사하지만, collection이 document의 구조를 정의하지 않는다.

 

  • Document

MongoDB에 저장되는 자료,

SQL에서 row와 유사하지만 구조제약 없이 유연하게 저장 가능

JSON과 유사한, BSON을 사용하여 다양한 자료형을 지원

 

  • Document - ObjectID

각 document의 유일한 키 값, SQL의 primary key와 유사

하나씩 증가하는 값이 아닌 document를 생성할 때 자동으로 생성되는 값

 

※ timestamp(현재 시간) + random value(임의의 값) + auto increament(자동증가값) = 겹칠 수가 없음.

 

MongoDB 사용 방법

MongoDB를 직접 설치하거나 Cloud 서비스를 사용할 수 있다.

직접 설치하면 귀찮고 어렵지만 원하는 만큼 얼마든지 데이터를 사용할 수 있다.

Cloud를 사용하면 쉽고 빠르게 시작 가능하지만, 사용량에 따라 요금이 부과된다.

 

직접 MongoDB 설치하기

직접 모든 데이터베이스 관련 설정을 해야한다.

Sharding이나 Replication 등의 작업이 필요할 때 운영지식과 노하우가 요구된다. 즉 어떤 여러 document의 집합이 있을 때 어떻게 효율적으로 다룰지에 대한 지식이 필요하다. 이를 위해 무료나 유료 등의 데이터베이스를 설치해서 사용한다. 무료로 사용할 수 있는 Community Version을 제공한다.

 

MongoDB Cloud 이용하기

모든 데이터베이스 관련 기능을 웹에서 관리 가능하다. 특별한 노하우 없이 데이터베이스 운용 가능하단 장점이 있다. 사용량에 따라 비용이 발생하지만, 512MB까지는 평생 무료로 사용 가능하다.

 

MongoDB Compass

MongoDB에 접속하여 Database, Collection, Document 등을 시각화하여 관리할 수 있게 도와주는 도구다.

원래라면 터미널, cmd 같은 툴을 사용하지만 실제 데이터를 JSON처럼 시각화해서 보여줌으로써 편리하게 다룬다. MySQL을 사용할 때 MySQL Workbench와 유사하다.

 

Mongoose ODM

Object Data Modeling

 

MongoDB의 Collection에 집중하여 관리하도록 도와주는 패키지다.

Collection을 모델화하여, 관련 기능들을 쉽게 사용할 수 있도록 도와준다.

 

Mongoose ODM을 사용하는 이유

  • 연결관리

Node.js에서는 기본적인 MongoDB 드라이버를 제공해준다. 드라이버라 함은 Node.js에서 MongoDB를 활용할 수 있게 해주는 툴이다. 그러나 이 MongoDB의 기본 Node.js 드라이버는 연결 상태를 관리하기 어려웠다. DB <-> Node.js 사이에 먼저 Node.js에서 DB를 불러서 DB에서 정보를 가져오는 이 과정 사이에서 연결이 끊어지거나 DB가 여러 개가 연결되어 있을 때 연결상태를 관리하기 어려웠다. 이것을 보완하는 Mongoose를 사용하면 간단하게 데이터베이스와의 연결상태를 관리해준다. 더욱 편리하게 데이터를 주고 받을 수 있게 됐다.

 

  • 스키마 관리 (스키마 : 어떤 데이터가 가져야 하는 성질, 객체로 따지면 프로퍼티)

스키마를 정의하지 않고 데이터를 사용할 수 있는 것은 NoSQL의 장점이지만, 데이터 형식을 미리 정의 해야 코드 작성과 프로젝트 관리에 유용하다. NoSQL의 특징을 죽이는 것과도 같은 것이다. 하지만 궁극적으로 구조적인 관리를 해야 이후에 오류를 찾거나 새로운 데이터 넣을 때 편하다. Mongoose는 Code-Level에서 스키마를 정의하고 관리할 수 있게 해준다. 코드를 JavaScript에서 편집만 해도 DB에서 갱신되고 추가할 수 있다는 뜻.

 

  • Populate

MongoDB는 기본적으로 Join을 제공하지 않는다. Join이라 함은 RDB(관계지향 DB)에서 어떤 특정 행 두 개 간에 특정 요소가 같다, 혹은 포함되어 있다 등을 바탕으로 데이터를 융합, 복합적으로 뽑아내는 과정을 말하는데, MongoDB는 지원하지 않는다. 그래서 Join과 유사한 기능을 사용하기 위해선 aggregate 라는 복잡한 쿼리를 해야 하지만, Mongoose는 populate를 사용하여 간단하게 구현할 수 있다.

 

Mongoose ODM 사용 방법

Mongoose ODM 사용 순서

1. 스키마 정의

2. 모델 만들기

3. 데이터베이스 연결

4. 모델 사용

 

스키마 정의하기

new Schema 안에 title, content가 포함돼있다. 두 개의 데이터를 가지고 있어야 함을 명시.

Collection에서 저장될 Document의 스키마를 Code-Level에서 관리할 수 있도록 Schema를 작성할 수 있다. 다양한 형식을 미리 지정하여, 생성, 수정 작업 시 데이터 형식을 체크해주는 기능을 제공한다. timestamps 옵션을 사용하면 생성, 수정 시간을 자동으로 기록해준다.

 

모델 만들기

세 번째 줄에서 알 수 있듯이 모델 이름은 'Post'

작성된 스키마를 mongoose에서 사용할 수 있는 모델로 만들어야 한다. 모델의 이름을 지정하여 Populate 등에서 해당 이름으로 모델을 호출할 수 있다.

 

데이터베이스 연결하기

connect 함수를 이용하여 간단하게 데이터베이스에 연결할 수 있다. index.js 에서 Mongoose DB를 불러오고 이전에 만들었던 models 로부터 Post 모델을 만들어냈다. mongoonse.connect를 통해서 Mongoose DB와 연결한다.

mongoose는 자동으로 연결을 관리해 주어 직접 연결 상태를 체크하지 않아도 모델 사용시 연결 상태를 확인하여 사용이 가능할 때 작업을 실행한다.

 

모델 사용하기

작성된 모델을 이용하여  CRUD을 수행할 수 있다. 데이터를 다룸에 있어서 필수적인 과정이다.

CRUD 함수형
CREATE create
READ find, findById, findOne
UPDATE updateOne, updateMany,
findByIdAndUpdate, findOneAndUpdate
DELETE deleteOne, deleteMany,
findByIdAndDelete, findOneAndDelete

 

CREATE

모델명.create를 호출해서 안에 있는 객체를 불러옴. 복수 Document는 대괄호를 통해 배열로 받아줬다.

create 함수를 사용하여 Document 생성한다. create 함수에는 Document Object나(단일 Document 생성) Document Object의 Array 전달 가능하다(복수 Document 생성). create는 생성된 Document를 반환해준다. 단일 Document 생성 반환된 데이터를 바탕으로 추가 연산 진행 가능하다.

 

FIND (READ)

find 관련 함수를 사용하여 Document를 검색한다. find/findONe은 query를 사용하여 특정 조건에 맞는 데이터를 검색한다. findById를 사용하면 ObejctID로 Document를 검색할 수 있다. find 관련 함수를 통해 탐색하고 결과 받아오기가 가능하다.

 

Query

MongoDB에도 SQL의 where의 유사한 조건절 사용 가능하다.

MongoDB의 query는 BSON 형식으로, 기본 문법 그대로 mongoose에서도 사용 가능하다.

{key : value} 로 exact match해서 정확히 해당 데이터를 찾늗다.

$lt, $lte, $gt, $gte를 사용하여 수치형 데이터에 range query 작성 가능하다.

 

$lt lower than
$lte lower than equal
$gt greater than
$gte greater than equal

 

$in 을 사용하여 다중 값으로 검색하고, $or 를 사용하여 다중 조건 검색한다.

($in을 사용하면 배열에 주어진 값 중 최소 한 개의 일치하는 값을 가진 Document를 검색한다.)

MongoDB의 쿼리는 기본적으로 \$and 조건으로 연결된다. 예시를 보면 name과 age가 쉼표(\$and)로 연결된 것을 볼 수 있다. 그런데 \$or의 경우에는 해당하는 조건 중에 하나만 만족해도 결과를 가져온다.

참고 - Mongoose ODM - $in

\$in의 경우 특수한 형태가 있는데, Mongoose는 퀴리 값으로 배열이 주어지면 자동으로 \$in 쿼리를 생성해준다.
참고 - MongoDB Query Operators
MongoDB 홈페이지에서 다양한 Query Operator를 확인 가능하다.
https://docs.mongodb.com/manual/reference/operator/query/ 

 

UPDATE

updateOne, updateMany로 수정(수정할 Query를 명시하고, 뒤에 나오는 BSON을 통해서 해당하는 데이터에 바뀌는 요소들을 작성)

update 관련 함수를 사용하여 Document를 수정한다. find~ 함수들은 검색된 Document업데이트를 반영하여 완료된 결과를 반환해 준다. mongoose의 update는 기본적으로 $set operator 를 사용하여, Document를 통째로 변경하지 않는다. {a : __, b : __, c : __} 의 요소를 단순히 c만 담게 바꾸는 게 아니라 단지 C 요소만 바꿔주는 것을 의미한다. find 시리즈는 Document 그 자체를 반환하는데, update는 몇 개에 대해서 업데이트가 완료됐는지, 숫자를 반환하게 된다.

 

DELETE

delete 관련 함수를 사용하여 Document 삭제한다. 하나를 삭제할려면 deleteone, 여러 개는 deletemany를 사용한다. find~ 함수들은 검색된 Document를 반환해준다. 즉, 삭제된 Document가 반환이 된다는 것이다. delete의 경우에는 몇 개의 정보가 삭제되었는지를 반환한다.

 

populate

Document 안에 Document를 담지 않고, ObjectID를 가지고 reference하여 사용할 수 있는 방법을 제공한다. 예시를 보면 post라는 schema 안에 user, comments가 있다. 알고보니 user는 name, age이 정보를 담는 하나의 스키마로 구성된 document 이고 comments 또한 title, content 등이 담긴 요소이다. 이런 요소 개별을 다 명시한 게 아니라, user와 comments는 단순히 ObjectID를 담는다고 명시한다. reference는 User, Comment를 각각 명시했는데 그것이 각 스키마의 이름인 것이다. 이런 user의 ObjectID를 담게 되면서 마치 user 스키마의 정보를 담는 것과 동일한 효과를 주게 된다. User는 중괄호 하나인 단일 요소, comments는 배열 요소임을 알 수 있다. 이러한 reference에 배열이 있음을 알 수 있다. 즉, Document에는 reference되는 ObjectID를 담고, 사용할 때 populate 하여 하위 Document처럼 사용할 수 있게 해준다.


Mongoose Populate

Post 모델에 User를 추가하고 이를 Populate하여 사용하는 방법을 실습합니다.

지시사항에 따라 코드를 완성합니다.

 

지시사항

Populate를 사용하기 위해, 스키마를 선언하고 모델을 생성합니다.

이후, populate() 함수를 이용하여 reference 된 Document를 주입합니다.


  1. UserSchema를 완성합니다.
    • User는 name, level 속성을 갖습니다.
    • name의 타입은 String이고 필수값입니다.
    • level의 타입은 Number이고 필수값입니다.
    • level의 값이 없을 경우 기본값은 0입니다.
    • Schema 작성 시 기본값을 설정하는 예제는 다음과 같습니다.
{
    attr: {
        type: String,
        default: "Default Value",
    },
}

 

2. User 모델을 생성합니다.

  • ./models/index.js에 User 모델을 추가합니다.
  • 모듈의 export되는 이름을 User로 설정합니다.
  • 모델의 이름은 'User'로 선언합니다.

3. PostSchema에 User를 reference 하도록 추가합니다.

  • Post에 author 속성을 추가합니다.
  • author는 필수값으로 추가합니다.
  • populate()가 동작하도록 author의 타입과 ref를 정확하게 선언합니다.

4. ./index.js에 모델을 사용하여 populate를 수행하는 main() 함수의 코드를 완성합니다.

  • Post.find()에 populate()를 추가하여 author가 주입되도록 코드를 완성합니다.

users.js

const { Schema } = require('mongoose');

const UserSchema = new Schema({
  // name, level 선언
  name : {
      type : String,
      required : true,
  },
  level : {
      type : Number,
      required : true,
      default : 0,
  }
}, {
  timestamps: true,
})

module.exports = UserSchema;

index.js .../models

const mongoose = require('mongoose');
const PostSchema = require('./schemas/post');
const UserSchema = require('./schemas/user');

exports.Post = mongoose.model('Post', PostSchema);
// User 모델 생성
exports.User = mongoose.model('User', UserSchema);

post.js

const { Schema } = require('mongoose');

const PostSchema = new Schema({
  title: {
    type: String,
    required: true,
  },
  content: {
    type: String,
    required: true,
  },
  author: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
  }
}, {
  timestamps: true,
})

module.exports = PostSchema;

index.js

const mongoose = require('mongoose');
const { Post } = require('./models');

mongoose.connect("mongodb://localhost:27017/exam_6");

async function main() {
  const posts = await Post.find({}).populate("author");
  // populate 사용하기
  return posts;
}

module.exports = main;