ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MongoDB
    DB 2020. 1. 9. 00:21

    본 글은 "Node.js 교과서: 기본기에 충실한 노드제이에스 10 입문서"를 토대로 작성되었습니다.

    mongoDB

    자바스크립트 문법을 사용한다.

    • 속성 안에 객체를 넣을 수 있다.

    • 노드도 자바스크립트를 사용하므로 데이터베이스마저 몽고디비를 사용한다면 자바스크립트만 사용해서 웹 애플리케이션을 만들 수 있는 것이다. → 하나의 언어만 사용하므로 생산성이 매우 높다.

    대표적인 NoSQL(Not only SQL)

     

    NoSQL vs SQL

    SQL(MySQL) NoSQL(몽고디비)
    규칙에 맞는 데이터 입력 자유로운 데이터 입력
    테이블 간 JOIN 지원 컬렉션 간 JOIN 미지원 →
    하나의 쿼리로 여러 테이블을 합치는 작업이
    항상 가능하지는 않다.
    트랜잭션 지원 트랜잭션 미지원(몽고디비 4 버전부터는 지원)
    안정성, 일관성 확장성, 가용성
    용어(테이블, 로우, 컬럼) 용어(컬렉션, 다큐먼트, 필드)

    MySQL은 users테이블을 만들 때 name, age, married 등의 컬럼과 자료형, 옵션 등을 정의하지만, 몽고디비는 users 컬렉션(테이블에 상응)을 만들고 끝이다.

    NoSQL에는 고정된 테이블이 없으며, 컬럼을 따로 정의하지 않는다.

    Screen Shot 2020-01-08 at 9 59 20 PM

    컬렉션에는 어떠한 데이터라도 다 들어갈 수 있다.

    어떤 다큐먼트(로우에 상응)에는 name, age, married 데이터가, 다른 다큐먼트에는 name, comment, createdAt, updatedAt 등의 데이터가 들어갈 수 있다.

     

    트랜잭션

    여러 쿼리가 모두 정상적으로 수행되거나, 아예 하나도 수행되지 않음을 보장하는 기능.

    ATM 기기에서 다른 사람에게 돈을 송금한다고 하면 계좌 테이블에서 내 계좌 로우를 찾아 출금하고, 다른 사람 계좌 로우에 입금해야한다. 입금, 출금 두 쿼리를 수행하는 중 에러가 생겨 하나의 쿼리만 수행된다면 돈이 도중에 사라지거나 없던 돈이 생기는 등 문제가 발생한다. 트랜잭션은 이러한 일이 발생하지 않도록 보장한다.

    트랜잭션 기능이 없으면 데이터 일관성에 문제가 생길 수 있다.

     

    몽고디비를 사용하는 이유?

    트랜잭션도 안 되고, JOIN도 안 되지만 몽고디비를 사용하는 이유는 확장성가용성 때문이다.

    데이터를 빠르게 넣을 수 있고, 쉽게 여러 서버에 데이터를 분산할 수 있다.

    인터넷에서 서핑하는 사람들의 데이터를 수집하다보면 어떤 사람은 fashion 관련 페이지 세 번, computer 관련 페이지를 5번 들어갔고, 다음 사람은 fashion 페이지 1번, computer는 안 들어갔고 news를 10번 들어갔을 수 있다.

    이 경우, 서로 다른 컬럼이 생길 수 있어 컬럼을 미리 정의할 수 없다.

    데이터 수집의 경우, 컬럼이 없는 mongoDB 같은 NoSQL을 많이 사용한다.

     

    애플리케이션을 만들 때 SQL과 NoSQL을 동시에 사용해서 만들 수 있다.

    항공사 예약 시스템의 경우 비행기 표에 관한 정보가 모든 항공사에 일관성 있게 전달되어야 하므로 예약 처리 부분의 데이터베이스는 MySQL을 사용한다. 대신 핵심 기능 외의 빅데이터, 메시징, 세션 관리 등에는 확장성과 가용성을 위해 몽고디비를 사용할 수 있다.

     

    자료형

    Date나 정규표현식 같은 자바스크립트 객체를 자료형으로 사용할 수 있다.

    Binary Data, ObjectId, Int, Long, Decimal, Timestamp, Javascript 등의 추가적인 자료형이 있다.

    • ObjectId: 고유한 랜덤 문자열 생성. MySQL에서 기본키로 쓰이는 값과 비슷한 역할을 한다. 고유한 값을 가지므로 다큐먼트를 조회할 때 사용할 수 있다.

     

    몽구스(Mongoose)

    ODM(Object Document Mapping): 다큐먼트를 자바스크립트 객체로 매핑한다.

    • 몽고디비 자체가 이미 자바스크립트인데 굳이 자바스크립트 객체와 매핑하는 이유 → 몽고디비에 없어서 불편한 기능들을 몽구스가 보완해준다.

    몽고디비는 테이블이 없어서 자유롭게 데이터를 넣을 수 있지만, 실수로 잘못된 자료형의 데이터를 넣을 수도 있고, 다른 다큐먼트에는 없는 필드의 데이터를 넣을 수 있다.

    몽구스는 몽고디비에 데이터를 넣기 전 노드 서버 단에서 데이터를 한 번 필터링하는 역할을 해준다.

    몽고디비에 제약을 두지만 편의성과 안정성을 추가한다.

    • JOIN 기능을 populate메서드로 어느 정도 보완해줘서, 관계가 있는 데이터를 쉽게 가져올 수 있다.

    • 프로미스 문법과 강력하고 가독성이 높은 쿼리 빌더를 지원한다.

     

    몽고디비 연결하기

    /* schemas/index.js */
    const mongoose = require('mongoose');
    
    module.exports = () => {
      const connect = () => {
        if(process.env.NODE_ENV !== 'production'){ // 배포 상황이 아닐 때
            mongoose.set('debug', true); // 몽구스를 디버깅 모드(에러 표시 모드)로 바꿔준다.
        }
        mongoose.connect('mongodb://이름:비밀번호@localhost:27017/admin',{
          dbName: 'nodejs',
        }, (error) => {
         if(error) => {
            console.log('몽고디비 연결 에러', error);
         }else{
          console.log('몽고디비 연결 성공');
         }
        });
      };
         connect();
    };

    노드와 몽고디비는 주소를 사용해 연결한다. 주소 형식 → mongodb://username:password@host:port/database

    접속을 시도하는 주소의 데이터베이스는 admin이지만, 실제로 사용할 데이터베이스는 nodejs이므로 두 번째 인자로 dbName 옵션을 주어 nodejs 데이터베이스를 사용하게 한다.

     

    schemas/index.js를 app.js와 연결하기

    // app.js
    ...
    var indexRouter = require('./routes');
    var usersRouter = require('./routes/users');
    var connect = require('./schemas');
    
    var app = express();
    connect();
    ...

    스키마 정의하기

    몽구스 스키마(schema) 만들기

    // schemas/user.js
    const mongoose = require('mongoose');
    
    const { Schema } = mongoose; // 몽구스 모듈에서 Schema 생성자를 가져온다.
    // Schema 생성자를 사용해 스키마를 만든다. 시퀄라이즈에서 모델을 정의하는 것과 비슷하다.
    const userSchema = new Schema({ 
      name: {
        type: String,
        required: true,
        unique: true,
      },
      age: {
        type: Number,
        required: true,
      },
      married: {
        type: Boolean,
        required: true,
      },
      comment: String,
      createdAt: {
        type: Date,
        default: Date.now,
      },
    });
    
    module.exports = mongoose.model('User', userSchema);
    // schemas/comment.js
    const mongoose = require('mongoose');
    
    const { Schema } = mongoose;
    const { Types: { ObjectId } } = Schema;
    const commentSchema = new Schema({
      commenter: {
        type: ObjectId, // commenter 필드에 User 스키마의 사용자 ObjectId가 들어간다는 뜻.
        required: true,
        ref: 'User',
      },
      comment: {},
      createdAt: {},
    });
    
    module.exports = mongoose.model('Comment', commentSchema); // 스키마와 몽고디비 컬렉션 연결.

    몽구스 스키마는 String, Number, Date, Buffer, Boolean, Mixed, ObjectId, Array를 값으로 가질 수 있다.

    commenter 필드는 나중에 몽구스가 JOIN과 비슷한 기능을 할 때 사용된다.

    쿼리 수행하기

    // routes/index.js
    
    var express = require('express');
    var router = express.Router();
    // const { User } = require('../models') 로 index.js에서 간접적으로 User 모델을 가져오는 시퀄라이즈와 달리, schemas에서 User 스키마를 직접 가져온다.
    const User = require('../schemas/user'); 
    
    router.get('/', (req, res, next) => {
      User.find({})
          .then((users) => {
            res.render('mongoose', { users });
          })
          .catch((err) => {
            console.error(err);
            next(err);
          });
    });
    
    module.exports = router;

    find: 모두 찾기

    findOne: 하나만 찾기

    new 스키마(data).save: 생성

    update: 수정하기

    remove: 제거하기

     

    routes/users.js

    <Mongoose>

    // routes/users.js
    var express = require('express');
    var router = express.Router();
    const User = require('../schemas/user');
    
    /* GET users listing. */
    router.get('/', function(req, res, next) {
        User.find()
            .then((users) => {
              res.json(users);
            })
            .catch((error) => {
              console.error(error);
              next(error);
            });
    });
    
    router.post('/', (req, res, next) => {
      const user = new User({ // new 스키마(데이터)로 user 객체 만든 후 다큐먼트에 들어갈 내용들 넣어주기.
          name: req.body.name,
          age: req.body.age,
          married: req.body.married,
      });
      user.save() // save메서드로 user 객체 저장
        .then((result) => {
            res.status(201).json(result);
        })
        .catch((error) => {
            console.error(error);
            next(error);
        });
    });
    
    module.exports = router;

    <Sequelize>

    Screen Shot 2020-01-09 at 10 43 04 AM

     

    routes/comments.js

    <populate 메서드 적용 전>

    Screen Shot 2020-01-09 at 2 30 13 PMScreen Shot 2020-01-09 at 2 28 59 PM

    <populate 메서드 적용 후>

    const express = require('express');
    const router = express.Router();
    const Comment = require('../schemas/comment');
    
    router.get('/:id', (req, res, next) => {
        Comment.find({ commenter: req.params.id }).populate('commenter')
            .then((comments) => {
                res.json(comments);
            })
            .catch((error) => {
                console.error(error);
                next(error);
            });
    });
    router.patch('/:id', (req, res, next) => {
        Comment.update({ _id: req.params.id }, { comment: req.body.comment }) //시퀄라이즈와 달리 조건이 첫번째로 온다.
            .then((result) => {
                res.json(result);
            })
            .catch((error) => {
                console.error(error);
                next(error);
            });
    });
    router.delete('/:id', (req, res, next) => {
        Comment.remove({ _id: req.params.id })
            .then((result) => {
                res.json(result);
            })
            .catch((error) => {
                console.error(error);
                next(error);
            });
    });
    router.post('/', (req, res, next) => {
        const post = new Comment({
            commenter: req.body.id,
            comment: req.body.comment,
        });
        post.save()
        .then((result) => {
            res.status(201).json(result);
        })
        .catch((error) => {
            console.error(error);
            next(error);
        });
    });
    
    module.exports = router;

    populate(필드)는 시퀄라이즈 include와 비슷하다.

    시퀄라이즈 include = MySQL JOIN

    Screen Shot 2020-01-09 at 2 56 11 PMScreen Shot 2020-01-09 at 2 56 51 PM

    Comment스키마 commenter 필드의 ref가 User로 되어 있으므로 알아서 users컬렉션에서 사용자 다큐먼트를 찾아 합친다.

    Screen Shot 2020-01-09 at 3 57 05 PM

    commenter 필드가 사용자 다큐먼트로 치환되어, 이제 commenter필드는 ObjectId가 아니라 그 ObjectId를 가진 사용자 다큐먼트가 된다.

    'DB' 카테고리의 다른 글

    MongoDB) Expected "payload" to be a plain object 에러 해결법.  (0) 2020.05.06
Designed by Tistory.