ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MongoDB - 개념설명, 초기 설정, 데이터 저장·조회
    Data Base/MongoDB 2024. 4. 25. 15:08

    MongoDB 개념설정과 초기설정

    RDB와 NoSQL 개념의 차이 

    RDB의 경우 데이터베이스에 테이블이 여러개 있지만 NoSQL MongoDB의 경우 컬렉션이 여러개 있다.각 컬렉션에는 레코드(record)가 아닌 도큐먼트(document)형태로 기록 된다. 레코드와 도큐먼트는 그 명칭이 다를 뿐만 아니라 데이터베이스의 핵심 철학에 큰 차이점이 있다. 큰 차이점은 RDB와 다르게 MongoDB의 경우는 스키마리스로 컬렉션의 문서 즉 데이터나 항목이 RDB처럼 항상 같은 구조를 가질 필요가 없다. 

     

    RDB의 경우 User타입의 데이터는 id,이메일,이름,닉네임,비밀번호 등등 정해져있는 정보의 구조가 있었다면 NoSQL의 경우 User라는 한 컬렉션 안에 아무 유형의 데이터가 있어도 된다. 보통은 비슷한 구조를 사용하게 된다고 하더라고 꼭 똑같은 구조를 가질 필요는 없다. 이렇게 유연성이 있는 만큼 시간이 지남에 따랄 데이터베이스에 표현하기 어렵지 않으면서 애플리케이션의 데이터 요구사항을 변경할 수 있는 것이다. 

     

    JSON(BSON) Data Format

    {
        "name" : "Dona",
        "age"  : 34,
        "address"  : 
        {
        	"city" : "Hawai"
        },
        "hobbies": [
        	{"name" : "Swimming"},
            {"name": "Surfing"}
        ]
    }

     

    MongoDB의 문서는 JavaScript의 객체 표기법과 아주 흡사하다. 정확히 말하면 MongoDB는 JSON을 통해 컬렉션에 데이터를 저장한다. 즉 저장하는 도큐먼트가모두 위 예시와 같은 방식으로 저장되며 JavaScript의 객체 표기법을 따른다. 엄밀히 얘기하면 MongoDB는 Binary JSON인 BSON을 이용하는데 파일에 저장하기 전 데이터를 변형한다는 의미이다.

     

    위 예시에서 adress 속성 처럼 중첩된 객체 요소는 MongoDB에서 내장문서(embedded document)로 불린다. 또 hobbied 속성처럼 배열을 가질 수 있는데 배열역시 다른 문서 객체나 문자열, 숫자 등을 가질 수 있을 정도로 데이터에 유연성이 크다. 또한 내장문서가 다는 의미는 MongoDB와 같은 NoSQL에서는 Relation이 조금 다르게 관리된다는 의미이기도 하다.


    MongoDB에서의 Relation

    데이터 저장시 데이터 형식에서 뿐만 아니라 데이테간의 관계에서도 높은 유연성을 제공한다.

    NoSQL에서는 위와 같은 구조가 일반적으로 사용된다. 위 그림에는 세 개의 컬렉션이 있고 그 중 중복되는 데이터가 있다. User 컬렉션에 사용자에 관한 세부 사항이 존재한다. 즉 Orders 컬렉션의 데이터의 일부는 다른 컬렉션 문서에 중첩 혹은 내장되어 있을 수 있다.

     

    MongoDB의 경우 다른 문서의 데이터를 내장함으로써 관계를 표현할 수도 있다. 다른 문서를 가리키는 ID를 내장해서 두 문서를직접 병합하는 것이다.하지만 중점이 되는 정보만 가지고 다른 문서에 넣을 수도 있다. 예를 들어 특정 사용자 데이터를 Orders에 넣으면 Orders를 검색할 때마다 해당 데이터도 함께 나온다. 즉 RDB 처럼 주문을 검색한 후 해당 주문을 한 사용자를 따로 찾아서 가져올 필요가 없다. 바로 이 측면에서 NoSQL 특히 MongoDB가 빠르고 효율적이다.

     

    필요한 형식으로 데이터를 저장해서 추후 병합하는 과정을 많이 거치지 않고도 즉 서버의 백그라운드에서 여러 컬렉션을 합치지 않고도 필요한 형식의 데이터를 가져올 수 있게 만든다.

     

    Relations - Options

    관계를 표현하기 위해 사용할 수 있는 중첩/내당 문서 외에도 참조가 존재한다.

    내장 문서의 예시로 address가 Customers 문서의 일부이다. 내장 문서의 예시로 address가 Customers 도큐먼트의 일부이다. 이 경우 Custoemers와 Addresses 컬렉션이 따로있지 않아서 추후에 ID로 매칭해서 Customer 객체안에 adress 객체를 넣지 않아도 된다. 이 경우 중복되는 데이터가 아주 많다. 또 해당 데이터를 자주 변경해야 하는 경우에는 중복되는 데이터를 모두 업데이트해야 한다. 이때는 내장 문서를 사용하는 것이 이상적인 방법이 아니다.

     

    즉 고객마다  좋아하는 책 몇 가지를 배열로 기록하는 속성이 있다고 할 때 이때 해리포터 시리즈가 유행하고 있다고 하면  해리포터 시리즈 중 많은 것들이 각각 Cumstomer의 도큐먼트에 중복되어 기록될 것이다. 그리고 좋아하는 책에 대한 기록은 자주 변경될 수도 있다.

     

    반면 Reference 시나리오에서는 Customers문서에 책에 대한 참조만 저장한 다른 컬렉션으로 관리하는 Book로 직접 병합하는것이 좋다.

     

    MongoDB 특징

    정해진 스키마가 없기 때문에 특정 구조가 필요하지 않으면서 유연성이 향상되었다. 또 내장을 통해 관계를 지을 수 있어 데이터 관계는 적이진다. option에 따라 참조를 통해 관계를 직접 구축할수도 있지만 상황에 따라 무엇이 이상적인 방식인지 판단해서 option을 선택해서 활용할 수 있다.


    MongoDB 활용하기

    DB Connection

    util/database.js

    const mongodb = require('mongodb');
    const MongoClinet = mongodb.MongoClient;
    const dotenv = require('dotenv');
    dotenv.config({path:'./.env.local'});
    
    let _db;
    
    /**
     * 데이터베이스를 연결
     * @param {*} callback 
     */
    const mongoConnect = (callback => {
    
    MongoClinet.connect(`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASSWORD}@cluster0~`)
    .then(client => {
      console.log('Connected!');
      _db = client.db();
      callback();
    })
    .catch(err => {
      console.log(err);
      throw err;
    
    });
    
    });
    
    /**
     * 데이터베이스 연결을 저장
     */
    const getDb = () =>{
      if(_db){
        return _db;
      }
      throw 'No database found!';
    }
    
    exports.mongoConnect = mongoConnect;
    exports.getDb = getDb;

     

    _db처럼 변수에 "_" 언더스코어가 사용되는건 해당 변수가 해당 파일내에서만 사용된다는 의미이다.  또 _db변수에 현재 db 연결정보를 넣을수있다. mongoDB URL에 "mongodb.net/" 뒤에 path 키워드를 넣는지에 따라 해당 이름의 데이터베이스와 연결되고, 기존의 존재하기 않는 데이터베이스 이름이라고 해도 자동으로 해당 이름의 데이터베이스가 생성 된다. 이것이 기존 RDB와는 차별되는 mongoDB의 유연한 특징이다.  또 위코드에서 client.db()의 메서드의 인자로 어떤 데이터베이스에 연결할지 지정하는 것도 가능하다.

     

    • mongoConnect  : MongoDB데이터베이스에 연결을 시도하는 함수이다. MongoClient.connect()에소드를 사용하고 해당 메서드에 MongoDB URL을 인자로 넣어 DB에 연결한다. 연결 성공실 데이터 베이스 객체를 _db변수에 저장한다.
    • callback : 연결이 공적으로 완료된 후 실행될 콜백 함수이다. 이를 통해 데이터베이스 연결 후 필요한 다른 작업을 수행할 수 있다. 
    • getDb : 앞서 저장된 데이터베이스 객체 _db를 반환한느 함수이다. 만약_db가 초기화되지 않았다면 예외를 발생시켜 오류를 알린다.

     

    MongoDB 데이터 생성하기

    model/product.js

    const getDB = require('../util/database').getDb;
    
    class Product{
      constructor(title, price, description, imageUrl){
        this.title = title;
        this.price = price;
        this.description = description;
        this.imageUrl = imageUrl;
      }
    
      save(){
        //데이터베이스 connection 호출 
        //connection된 데이터베이스 인스턴스를 반환 
        const db = getDB();
       return db.collection('products')
          .insertOne(this)
          .then(result =>{
            console.log(result);
          })
          .catch(err => {
            console.log(err);
          });
      }
    }
    
    module.exports = Product;

    db.collection() 메서드에 문자열로 인자를 전달하여 원하는 컬렉션 객체 인스턴스를 호출할 수 있다. 또 해당 인스턴스로 DB 데이터에 입력 수정등의 작업을 수행할 수 있다.

     

    이때 데이터베이스에 존재하지 않는 'products' 컬렉션을 전달하면 mogodb는 이를 새로운 컬렉션을 생성한다.

     

    https://www.mongodb.com/ko-kr/docs/manual/tutorial/insert-documents/

     

    문서 삽입 - MongoDB 매뉴얼 v7.0

    문서 홈 → 애플리케이션 개발 → MongoDB 매뉴얼 오른쪽 상단의 언어 선택 드롭다운 메뉴를 사용하여 다음 예제의 언어를 설정하거나 MongoDB Compass를 선택합니다.이 페이지에서는 MongoDB 삽입 작업

    www.mongodb.com

     

    Model 객체 실행 

    controllers/admin.js

    exports.postAddProduct = (req, res, next) => {
      const title = req.body.title;
      const imageUrl = req.body.imageUrl;
      const price = req.body.price;
      const description = req.body.description;
      const product = new Product(title, price, description, imageUrl);
      product.save()
      .then(result =>{
        console.log(result);
        res.redirect('/admin/products');
      }).catch(err =>{
        console.log(err);
      });

     

    해당코드를 실행하는 request를 날리면 코드가 실행된다.

     

    아직 MVC 패턴에서 View 페이지에 대한수정이 이루어 지지 않아 브라우저 상에서는 확인할 수 없지만 DB상에서는 위와 같이 데이터가 정상적으로 생성된것을 확인 할 수 있다.

     

     

    MongoDB가 제공하는 Cursor객체와  Product 검색 메서드 작성

    model/product.js

    ...
    static async fetchAll(){
        const db = getDB();
        return await db.collection('products').find().toArray(); 
      }
    ...

     

    위 코드에서는 find()메서드가 Promise를 즉시 반환하지 않고 커서(cursor)라는 것을 반환한다.커서는 MongoDB가 제공하는 객체로 단계별로 요소와 문서를 탐색한다. 왜냐하면 이론적으로 MongoDB의 컬렉션에서 find는 수백만 개의문서를 반환할 수도 있지만 그만한 분량을 데이터를 한번에 조회할 일을 드물것이다. 대신 find는 MongoDB에게 정돈된 상태의 문서를 조회할수 있는 일종의 편집기능을 제공하는 것이다. 

     

    위 코드와 같이 find()메서드에 바로 뒤에 toArray()메서드를 붙이면 'products'안에 컬렉션 안에 있는 모든 도큐먼트를 JavaScript의 배열 형태로 바꾸어 반환한다. 하지만 반환될 것이 수십개에서 수백개 정도일 때만 사용하는 것이 권장 된다.그렇지 않다면 pagenation 설정을 추가로 붙여야 한다. 

     

    controller/product.js

    exports.getProducts = async (req, res, next) => {
     const products = await Product.fetchAll();
        res.render('admin/products', {
          prods: products,
          pageTitle: 'Admin Products',
          path: '/admin/products'
        });
    };

     

     

    [출처 - NodJS 완벽가이드 , Maxi ]

    https://www.udemy.com/course/nodejs-mvc-rest-apis-graphql-deno/?couponCode=ST6MT42324

     

     

    댓글

Designed by Tistory.