본문 바로가기
학원에서 배운 것/DBMS MongoDB

KDT 5th 웹개발자 입문 수업 32일차

by 쿠리의일상 2023. 3. 21.

몽고DB - 문서 데이터 저장소

스키마리스(schema-less)인 몽고DB는 컬렉션과 문서가 비구조적이다.

몽고DB 인스턴스(컬렉션 및 문서) 구조는 사전에 정의되지 않고 저장되는 데이터에 적합하도록 유연하게 변형된다.

 

document는 키-값 집합으로 동작 방식은 자바스크립트와 같은 코드의 객체와 매우 유사하며, 안에 들어가는 데이터에 따라 구조가 변경된다. 그러므로 몽고DB는 관계형 DB에 비해 쉽고 민첩하다.


MongoDB의 ID 필드

RDBMS 에는 기본키가 있는데, 몽고DB에도 비슷한 목적으로 모든 문서에 id필드가 존재한다.

몽고DB 엔진이 UUID로 자동 생성하는데 id 필드는 기본키와 마찬가지로 자동으로 인덱싱 되며, 고유해야 한다.

 

몽고DB의 인덱싱

관계형 데이터베이스의 인덱싱과 비슷

문서 필드에 대한 부가적인 데이터를 생성하여 이 필드에 의존하는 조회 속도를 높인다.

몽고DB는 B-트리 인덱스를 사용한다.

 

몽고DB의 중첩 문서

문서 지향 구조를 가지고 있는 몽고DB는 문서를 중첩할 수 있다는 것이다.

 

몽고DB의 역정규화

몽고DB와 같은 문서 저장소는 조인을 제한적으로 지원하며 외래키의 개념이 없다. -> 데이터 구조의 동적인 특성 때문

그래서 몽고DB의 데이터 모델링은 역정규화로 흐르는 경향이 있으며 엄격하게 데이터를 유지하는 대신 문서에 데이터 사본을 만든다.

조회 속도는 빨라지지만 대신 데이터 일관성을 위한 유지보수 작업이 늘어나게 된다.

 

 

 

MongoDB 쿼리문

기본적으로 매개변수 첫번째에는 처리해줄 도큐먼트를,

두번째 매개변수에는 error와 result를 받아주는 콜백함수를 받아준다.

(err, result) => { }

 

 

1. 삽입

삽입 콜백함수의 결과를 출력하면,

쿼리문을 실행 결과를 담은 boolean값인 acknowledged와 오브젝트의 고유 ID인 insertedId가 출력된다.

insertMany() 의 경우 배열형식으로 insertedId와, 삽입된 도큐먼트의 개수인 insertedCount가 출력된다.

insertOne({객체}, 콜백함수)

하나의 도큐먼트Document를 삽입

 

insertMany([{객체}, {객체},...], 콜백함수)

여러 도큐먼트를 삽입

삽입해줄 도큐먼트는 배열에 담긴 객체 형태로 전달되어야 한다.

 

 

2. 삭제

삭제 콜백함수의 결과값을 출력하면,

쿼리문을 실행 결과를 담은 boolean값인 acknowledged와 삭제된 개수를 나타내는 deletedCount가 출력된다.

deleteOne({조건}, 콜백함수)

조건을 만족하는 가장 처음 도큐먼트 1개를 삭제

조건은 객체 형태로 기재

 

deleteMany({조건}, 콜백함수)

조건을 만족하는 모든 도큐먼트를 삭제

 const collection = client.db('kdt5').collection('test');
  collection.deleteMany({}, (deleteErr, deleteResult) => {
    if (deleteErr) throw deleteErr;
    console.log(deleteResult);

    collection.insertMany(
      [
        {
          name: 'pororo',
          age: 5,
        },
        {
          name: 'loopy',
          age: 6,
        },
        {
          name: 'poby',
          age: 7,
        },
      ],
      (insertErr, insertResult) => {
        if (insertErr) throw insertErr;
        console.log(insertResult);
        collection.deleteMany(
          { age: { $gte: 6 } },
          (deleteManyErr, deleteManyResult) => {
            if (deleteManyErr) throw deleteManyErr;
            console.log(deleteManyResult);

            client.close();
          }
        );
      }
    );
  });
});

 

조건을 객체로 받을 때, 키값에도 추가로 객체값을 줘서 $을 사용한 명령어를 수행시킬 수 있다.

$gte 는 greater than equal 이라는 의미로, 숫자에서 크거나 같은 조건을 의미한다. 아래에 명령어 정리해둠

 

 

 

3. 수정

조건을 만족하는 도큐먼트를 수정

변경해줄 내용을 담아준 객체는 $set을 사용하여 변경해준다.

{
	$set : { 변경해줄 키와 값 }
}

 

updateOne({조건}, { $set : {변경해줄내용} }, 콜백함수)

조건을 만족하는 가장 처음 도큐먼트를 하나 수정

client.connect((err) => {
  const collection = client.db('kdt5').collection('test');
  collection.deleteMany({}, (deleteErr, deleteResult) => {
    if (deleteErr) throw deleteErr;
    console.log(deleteResult);

    collection.insertMany(
      [
        {
          name: 'pororo',
          age: 5,
        },
        {
          name: 'loopy',
          age: 6,
        },
        {
          name: 'poby',
          age: 7,
        },
      ],
      (insertErr, insertResult) => {
        if (insertErr) throw insertErr;
        console.log(insertResult);
        collection.updateOne(
          { name: 'loopy' },
          {
            $set: { name: '루피' },
          },
          (updateOneErr, updateOneResult) => {
            if (updateOneErr) throw updateOneErr;
            console.log(updateOneResult);
          }
        );
      }
    );
  });
});

modifiedCount 와 matchedCount 의 개수가 변경된 개수임

 

updateMany({조건}, { $set : {바꿔줄객체} }, 콜백함수)

client.connect((err) => {
  const collection = client.db('kdt5').collection('test');
  collection.deleteMany({}, (deleteErr, deleteResult) => {
    if (deleteErr) throw deleteErr;
    console.log(deleteResult);

    collection.insertMany(
      [
        {
          name: 'pororo',
          age: 5,
        },
        {
          name: 'loopy',
          age: 6,
        },
        {
          name: 'poby',
          age: 7,
        },
      ],
      (insertErr, insertResult) => {
        if (insertErr) throw insertErr;
        console.log(insertResult);
        collection.updateMany(
          { age: { $gte: 6 } },
          {
            $set: { name: '6살 이상인 친구들' },
          },
          (updateOneErr, updateOneResult) => {
            if (updateOneErr) throw updateOneErr;
            console.log(updateOneResult);
          }
        );
      }
    );
  });
});

 

4. 검색(SELECT와 유사)

지금까지 삽입, 삭제, 수정에선 콜백함수의 두번째 매개변수가 쿼리문 실행 결과를 담았다면,

검색의 경우 콜백함수의 두번째 매개변수에 조건으로 찾은 도큐먼트를 리턴해준다.

 

findOne({조건}, 콜백함수)

검색 조건을 만족하는 처음 도큐먼트를 하나 찾아줌

 

find({조건}, 콜백함수)

지금까지 배운 쿼리문과 다른 구조를 가지고 있다.

 

조건에 맞는 도큐먼트를 모두 찾은 값이 리턴되지만, MongoDB의 정보를 가진 Cursor 객체로 저장되며 시간이 필요한 작업이 아니므로 콜백 함수를 사용하지 않고, 리턴 받은 Cursor로 찾은 DB의 정보를 데이터화 할 때, 시간이 필요하다.

// find 쿼리는 시간이 걸리지 않으므로 콜백을 사용하지 않는다.
const findCursor = collection.find({ name: 'loopy' });

console.log(findCursor);

// 대신 원하는 데이터를 찾아주는 것이 아닌, 데이터가 있는 위치정보인 Cursor 객체를 리턴하므로
// Cursor에서 데이터를 뽑을 때, 배열을 사용해서(toArray)는 시간이 필요하므로 콜백을 사용해준다.
findCursor.toArray((findErr, findData) => {
  console.log(findData);
});

findCursor 객체 정보

 

MongoDB 명령어, 쿼리연산자

$set : { 키 : 값, ... }

몽고db의 도큐먼트를 수정할 때 사용

 

 

비교식

$eq ( == )

 

$gt 클 때

$gte 크거나 같을 때 

 

$lt 작을 때

$lte 작거나 같을 때

 

$ne ( != )

 

$in 배열에 지정된 값 중 하나와 일치한 값 찾기

{ 필드: { $in: [ 값1, 값2, ... ] }} 에서 값1, 값2, ... 에 해당하면 리턴

<-> $nin 배열에 지정된 값과 일치하지 않는 값 찾기

 

논리식

여러 조건을 걸 때에는 조건들을 배열에 담긴 객체 형태로 전달

데이터베이스명.컬렉션명.find({ ($or / $and / $not / $nor): [ {조건1}, {조건2}, ... ] });

$or : 조건들 중 하나라도 true

$and : 조건들이 모두 true

$not : 조건이 false 일 때

$nor : 조건이 모두 false 일 때

const cursor = users.find({
	$and: [{age : { $gte: 5 } }, { name : 'loopy'}],
});
cursor.toArray((err, data) => {
	console.log(data);
});

 

 

 

콜백함수를 async await 로 바꿔주기

const { MongoClient, ServerApiVersion } = require('mongodb');
const uri =
  'mongodb+srv://아이디:비번@cluster0.y4isdtc.mongodb.net/?retryWrites=true&w=majority';
const client = new MongoClient(uri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  serverApi: ServerApiVersion.v1,
});

client.connect((err, db) => {
	// db 쿼리 코드
    client.close();
});
const { MongoClient, ServerApiVersion } = require('mongodb');
const uri =
  'mongodb+srv://아이디:비번@cluster0.y4isdtc.mongodb.net/?retryWrites=true&w=majority';
const client = new MongoClient(uri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  serverApi: ServerApiVersion.v1,
});

async function main() {
	const db = await client.connect();
    // db 쿼리 실행
    client.close();
}

main();

 

  • 시간이 걸리는 쿼리는 콜백함수 대신 await를 붙이고 그 결과값을 리턴 받는 변수를 지정해준다.
    • 해당 변수는 성공했을 때 리턴 받는 값이므로 acknowledged 정보를 가지고 있다.
    • acknowledged 가 true면 쿼리 성공, fasle면 쿼리 실패로 에러 처리를 해준다.
  • await는 promise를 리턴하는 행동을 기다려준다음(pending) promise를 받으면 다음 코드를 실행해준다.
async function main() {
  await client.connect();
  const test = client.db('kdt5').collection('test');

  const deleteResult = await test.deleteMany({});
  if (!deleteResult.acknowledged) return '삭제 에러 발생';

  const insertResult = await test.insertOne({
    name: 'pororo',
    age: 5,
  });
  if (!insertResult.acknowledged) return '삽입 에러 발생';

  client.close();
}
main();

acknowledged 값에 따라 성공 여부를 체크해준다.

 

 

콜백함수로 작성했을 때와달리, async/await 의 에러 핸들링은 try-catch 문을 사용해준다.

try 안에서 발생하는 에러는 자동적으로 throw 되어 catch 문에 잡힌다.

async function main() {
  try {
    await client.connect();
    const test = client.db('kdt5').collection('test');

    await test.deleteMany({});
    await test.insertOne({
      name: 'pororo',
      age: 5,
    });
  } catch (err) {
    console.error(err);
  }
  client.close();
}
main();

 

MySQL 로 구현했던 기능을 MongoDB 로 변경하기

mongoDB 접속 클라이언트를 모듈화하기

const { MongoClient, ServerApiVersion } = require('mongodb');
const uri =
  'mongodb+srv://아이디:비번@cluster0.y4isdtc.mongodb.net/?retryWrites=true&w=majority';
const client = new MongoClient(uri, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  serverApi: ServerApiVersion.v1,
});

module.exports = client;

 

몽고DB 접속 모듈을 사용한 회원 가입 기능과 중복 회원 체크 기능을 몽고DB로 수정

const mongoClient = require('./mongoConnect');

const userDB = {
  // 중복 회원 찾기
  userCheck: async (userId) => {
    try {
      const client = await mongoClient.connect();
      const user = client.db('kdt5').collection('user');

      const findUser = await user.findOne({ id: userId });
      return findUser;
    } catch (err) {
      console.error(err);
    }
  },
  // 회원가입
  userRegister: async (userInfo) => {
    try {
      const client = await mongoClient.connect();
      const user = client.db('kdt5').collection('user');

      await user.insertOne(userInfo);
      return true;
    } catch (err) {
      console.error(err);
    }
  },
};

module.exports = userDB;

 

const express = require('express');
const userController = require('../controllers/userController');

const router = express.Router();

router.get('/', (req, res) => {
  res.render('register');
});

router.post('/', async (req, res) => {
  // 중복 회원 찾기
  const duplicatedUser = await userController.userCheck(req.body.id);
  if (!duplicatedUser) {
    const registerResult = await userController.userRegister(req.body);
    if (registerResult) {
      res
        .status(200)
        .send(
          '회원 가입 성공! <br/><br/> <a href="/login">로그인으로 이동</a>'
        );
    } else {
      res
        .status(500)
        .send(
          '회원가입 실패, 알 수 없는 문제가 발생하였습니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>'
        );
    }
  } else {
    res
      .status(400)
      .send(
        '회원가입 실패, 동일한 아이디가 존재합니다. <br/><br/> <a href="/register">회원 가입으로 이동</a>'
      );
  }
});

module.exports = router;

 

몽고DB와 연동하여 MySQL 코드를 변경하여 회원가입을 구현했다.