본문 바로가기

Node.js

[코드잇] ORM으로 하는 데이터베이스 작업

1. 데이터베이스와 SQL

 

데이터베이스(Database)

 

- 일정한 체계 속에 저장된 데이터의 집합

- 하나의 row는 하나의 직원 정보를 의미 

- 하나의 column은 직원 정보의 세부 정보들 중 하나를 의미 

- 하나의 Database 안에는 여러 개의 Table이 들어갈 수 있음

 

데이터베이스의 데이터 처리 

 

- 테이블에 있는 row를 조회하는 작업

- 테이블에 새로운 row를 추가하는 작업

- 테이블의 기존 row를 수정하는 작업

- 테이블의 기존 row를 삭제하는 작업

 

- 데이터베이스를 사용하려면 DBMS라고 하는 프로그램이 필요 

 

DBMS(Database Managemnet System)

 - Database를 제어하기 위해 사용하는 프로그램

 - 사용자가 입력한 SQL을 해석해서 데이터베이스 작업을 수

 - 종류 : MySQL, PostgreSQL, SQLServer

 

 

2. ORM이란?

 

- 자바스크립트로 데이터베이스를 다루려면 클라이언트 객체라는 것이 필요 

- 클라이언트 객체는 SQL을 데이터베이스 서버에 전달해서 원하는 작업을 처리하고 그 결과를 받아오는 객체 

- 이때 데이터베이스의 종류에 맞는 클라이언트 객체를 사용해야 

- MySQL2라는 패키지에 있는 클라이언트 객체 사용

 

 1) 직접 작성한 SQL문을 클라이언트 객체를 통해서 DBMS로 전송하는 방법

  - 단점 : SQL를 모르면 사용 불가 

member 테이블에서 id column의 값이 1인 row 조회

 2) ORM이라고 하는 기술을 통해서 자바스크립트로 작성한 데이터베이스 관련 코드를 자동으로 SQL문으로 변환시켜서 클라이언트 객체를 통해  DBMS로 전송

  - ORM 패키지에서 ORM은 Object-Relational Mapping의 약자로 데이터베이스에 있는 데이터를 하나의 객체에 매핑시키는 기술

 

 

 

3. 데이터베이스 생성하기 

 

sequelize 패키지 사용

 

 - npm install myslq2 (클라이언트 객체 제공)

 - npm install sequelize

 - npm install sequelize-cil  

  (데이터베이스 관련 작업을 sequelize와 연동해서 터미널에서 직접 명령어를 수행할 수 있게 해줌)

 

npx sequelize init

 - 각각의 디렉토리 안에 기본파일들이 새롭게 생성

 - config / migrations / models/ seeders 디렉토리 생

 - config란 데이터베이스 접속에 관한 각종 설정들이 들어있는 디렉토리 

 

npx sequelize db:create --env development

 

 

4. npx의 의미 

 

- npx는 패키지에 내장된 명령을 실행하기 위해 사용하는 키워드 

- 우리가 npx sequelize를 실행하면 node_modules 디렉토리 안에 있는 .bin 디렉토리 안의 sequelize라는 파일을 실행하게 되는데

- 이때 이 sequelize 파일은 바로가기 파일이라서 그 원본 파일인 node_modules 디렉토리 안에 있는 sequelize-cli 패키지(디렉토리)안의 lib 디렉토리 안의 sequelize 파일을 nodef로 실행 

 

 

5. 모델과 테이블 생성하기 

 

- 직원 정보를 저장할 Table

- sequelize에서는 데이터베이스에 존재하는 하나의 Table이 자바스크립트 코드에서 하나의 class에 대응

- class의 객체는 하나의 row에 해당

 

-- model을 생성할거야, model의 이름은 Member
npx sequelize modle:generate -- name Member --attributes
name : string, team: string, position:string, emailAddress:string,
phoneNumber:string,addmissionDate:data,birthday:date,profilelmage:string

- migration이란 데이터베이스 내부에서 일어나는 모든 변경 사항을 의미 

  - up 메소드는 이 migration을 적용할 때 실행, Members라고 하는 테이블을 생성

  - 해당 row의 생성 시간과 갱신 시간을 기록하는 column들을 추가 

'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Members', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      team: {
        type: Sequelize.STRING
      },
      position: {
        type: Sequelize.STRING
      },
      emailAddress: {
        type: Sequelize.STRING
      },
      phoneNumber: {
        type: Sequelize.STRING
      },
      admissionDate: {
        type: Sequelize.DATE
      },
      birthday: {
        type: Sequelize.DATE
      },
      profileImage: {
        type: Sequelize.STRING
      },
     createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now'),
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now'),
      }
    });
  },
    });

  - down 메소드는 migration을 적용 해제할 때 실행 

  - Members 테이블 삭제 

 async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Members');
  }

- Migration 파일을 적용해서 Members 테이블 생성

  -  npx sequelize db:migrate

 

 

6. 테이블 지우기 

 

npx sequelize db:migrate:undo

- 가장 최근에 적용된 Migration 하나를 해제, 테이블 초기화 

 

 

7. 데이터 타입과 기본값

 

1) 데이터 타입

 

- 각 프로퍼티에 설정했던 데이터 타입들은 결국 데이터베이스의 테이블의 각 컬럼의 데이터 타입으로 적용

- Sequelize.STRING 은 문자열 타입으로, 데이터베이스에서 VARCHAR(255)라고 하는 타입으로 변환

- Sequelize.INTEGER 은 정수형 타입으로, 데이터베이스에서 INTEGER라고 하는 타입으로 변환

- Sequelize.FLOAT은 실수형 타입으로 데이터베이스에서 FLOAT이라고 하는 타입으로 변환

- Sequelize.DATE은 날짜형 타입으로 데이터베이스에서 DATETIME이라고 하는 타입으로 변환

 

2) 기본값

 

// ...      
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now')
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn('now')
      }
// ...

- defaultValue 속성을 주게 되면, 테이블의 createdAt, updateAt 컬럼에도 이 속성이 적용됨

 

 

8. Primary Key란?

 

Primary key

 - 데이터베이스의 테이블에서 특정 row를 고유하게 식별 할 수 있게 해주는 column

 - 반드시 allowNull 속성의 값이 false여야 함  (NULL을 허용하지 않음, 이 컬럼에는 항상 값이 있어야함)

 - autoIncrement는 Members 테이블에 새로운 직원 정보를 삽입할 때 마다 id column에 이전 값보다 1이 더 큰값을 데이터베이스가 자동으로 넣어 주는 속성 

 id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },

 

 

9. seed 데이터 넣기 

 

Seed Data

 

- 테이블에 가장 처음에 놓는 데이터

- sequelize-cli 명령어로 실행 

- npx sequelize seed:generate --name initialMembers 

  -> seed 데이터를 추가하는 내용을 담은 파일을 생성하고, 그 파일의 이름을 initialMembers로 하라는 뜻 

  -> seeders 디렉토리에 js 파일 생성

 

- npx sequelize db:seed:all

 -> 실제로 seed 데이터 삽입

 -> seeders 디렉토리에 있는 모든 seeder 파일이 적용

 

 

 

10. 모델과 테이블 연동하기 

 

- COWOK 데이터베이스에 있는 Members 테이블을 Member라는 모델이 인식할 수 있도록 연

 

member.js

'use strict';
const {Model} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Member extends Model {
    
    static associate(models) {
    
    }
  }
  Member.init({

    id: {
      allowNull: false,
      autoIncrement: true,
      primaryKey: true,
      type: DataTypes.INTEGER,
    },
    name: DataTypes.STRING,
    team: DataTypes.STRING,
    position: DataTypes.STRING,
    emailAddress: DataTypes.STRING,
    phoneNumber: DataTypes.STRING,
    admissionDate: DataTypes.DATE,
    birthday: DataTypes.DATE,
    profileImage: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'Member',
  });
  return Member;
};

 

index.js

const Sequelize = require('sequelize');
const config = require('../config/config.json');

const {
  username, password, database, host, dialect,
} = config.development;
const sequelize = new Sequelize(database, username, password, {
  host,
  dialect,
});

const Member = require('./member')(sequelize, Sequelize.DataTypes);

const db = {};
db.Member = Member;

module.exporst = db;

 

app.js

 

const express = require('express');

const app = express();

const db= require('./models');

const { Member } = db;

app.use(express.json());

app.get('/api/members', (req,res)=> {
    const { team } = req.query;
    if(team) {
        const teamMembers = members.filiter((m)=> m.team === team);
        res.send(teamMembers);
    }else {
        res.send(members);
    }
});

app.post('/api/members', (req,res) => {
    const newMember = req.body;
    members.push(newMember);
    res.send(newMember);
});

//members/뒤에 오는 값을 id변수에 대입
//라우트 파라미터(Route Parameter)
app.get('/api/members/:id', (req,res)=> {
    const { id } = req.params;
    const member = members.find((m)=>m.id === Number(id));
    if(member) {
        res.send(member);
    }else {
        //404 : 요청한 정보가 없다는 걸 나타냄
        res.status(404).send({message:"There is no such member"});
    }
});

app.put('/api/members:id', (req,res) => {
    const { id } = req.params;
    const newInfo = req.body;
    const member = members.find((m)=> m.id === Number(id));
    if(member) {
        Object.keys(newInfo).forEach((prop) => {
            member[prop] = newInfo[prop];
        });
        res.send(member);
    }else {
        res.status(404).send({message: 'There is no member with the id:'});
    }
});

app.delete('/api/members/:id',(req,res) => {
    const { id } = req.params;
    const memberCount = members.length;
    members = members.filter((member) => member.id != Number(id));
    if(members.length < membersCount) {
        res.send({message: 'Deleted'});
    } else {
        res.status(404).send({messge: 'There is no member with the id!'});
    }

});

 

 

11. 직원 정보 조회하기 - findAll

 

- 전체 직원 조회 

 -> findAll()은 Members 테이블에 있는 모든 row를 조회해서 가져오는 기능

 -> sequelize에 의해서 SQL문으로 변환되어서 DBMS에 전송

app.get('/api/members', async(req,res)=> {
    const { team } = req.query;
    if(team) {
        const teamMembers =  members.filiter((m)=> m.team === team);
        res.send(teamMembers);
    }else {
        const members = awaitMember.findAll();
        res.send(members);
    }
});

- 모델이 갖고 있는 대부분의 메소드들은 프로미스 객체를 리턴하는 비동기 실행 함수 

 

- 특정 팀에 속한 직원 조회 

 const teamMembers = await Member.findAll({where:{team:team}});

 

 

12. 특정 직원 정보 조회하기 -findOne

 

- 테이블에서 특정 row 하나만을 조회 

 

app.js

app.get('/api/members/:id', async (req,res)=> {
    const { id } = req.params;
    const member = await Member.findOne({where: {id}});
    if(member) {
        res.send(member);
    }else {
        //404 : 요청한 정보가 없다는 걸 나타냄
        res.status(404).send({message:"There is no such member"});
    }
});

 

 

13. 새 직원 정보 추가하기 

 

- 새로운 직원 정보를 Members 테이블에 추가

 

- 리퀘스트의 바디에 있는 새로운 직원 정보를 받아서 Member 모델의 build 메소드 안에 넣고 호출

- build는 하나의 Member 모델 객체를 생성하고 리턴  

  -> 이 모델 객체는 테이블에서 하나의 row가 됨

 

- Member 모델 객체의 save 메소드를 호출하면 실제로 테이블에 이 Member 모델 객체의 내용대로 

새로운 row가 추가 

- save 역시 프로미스 객체를 리턴하는 비동기 함수이기 때문에 await와 async 추가 

 

app.post('/api/members', async (req,res) => {
    const newMebmer = req.body;
    const member = Member.build(newMember);
    await member.save();
    res.send(member);
});

- 직원 정보의 프로퍼티들 중에서 id  부분은 굳이 추가해 주지 않아도 됨 

 -> id column 자동 증가 

 

 

14. build와 save 대신 create

 

- Member 모델의 build 메소드로 저장할 Member 모델 객체를 생성했고, 해당 객체의 save 메소드를 호출해서 실제로 Members 테이블의 새 row로 추가 

app.post('/api/members', async (req, res) => {
  const newMember = req.body;
  const member = Member.build(newMember);
  await member.save();
  res.send(member);
});

 

- 메소드 두개를 쓰지 않고, 단 하나의 메소드로도 똑같은 작업을 수행할 수 있음 

- create() 메소드 사용 

- build + save 조합을 create 메소드 하나로 대체 가능 

app.post('/api/members', async (req, res) => {
  const newMember = req.body;
  const member = await Member.create(newMember);
  res.send(member);
});

 

15. 기존 직원 정보 수정하기 

 

- Member 모델의 update 메소드 사용

- update의 첫번째 인수는 새로운 직원의 정보 , 두번째 인수는  수정할 row를 특정하기 위한 조건 객체 

- update 메소드도 프로미스 객체를 리턴하는 비동기 실행 함수 

- update 메소드가 리턴하는 프로미스 객체에는 그 작업 성공 결과로 배열 하나가 들어 있음

 -> 해당 배열의 첫 번째 요소에는 수정된 row가 리턴됨

 -> 배열의 첫번째 요소가 0인 요소에는 그런 직원이 없다는 메시지를 그렇지 않은 경우에는 해당 개수의 row들이 영향을 받았다는 메시지를 보내줌 

 

app.put('/api/members:id', async (req,res) => {
    const { id } = req.params;
    const newInfo = req.body;
    const request = await Member.update(newInfo, {where: {id}});
    if(result[0]) {
        res.send({message: `${result[0]} row(s) affected}`});
    } else {
        res.status(404).send({message: `There is no member with the id!`});
    }
});

- Member 모델의 update 메소드는 딱 지정해준 프로퍼티에 해당하는 column만 수행해주므로 수정할 column의 새 값만 적어줘도 됨

 

 

16. 직원 정보를 수정하는 또 다른 방법

 

 

- findOne 메소드는 해당 id값을 가지는 row를 가져오고 있음

- member 모델 객체는 해당 row와 연동되어 있음

- Member 객체의 프로퍼티 값을 리퀘스트의 바디에 있던 새 객체의 프로퍼티의 값들로 수정

- 모델 객체의 save 메소드를 호출하면 Member 모델 객체와 연동되어 있던 테이블의 실제 row가 수정

 

app.put('./api/members:id', async (req,res) => {
    const { id } =  req.params;
    const newInfo = req.body;
    const member = await Member.findOne({where: { id }});
    if(member) {
        Object.keys(newInfo).forEach((prop)=> {
            member[prop] = newInfo[prop];
        });
        await member.save();
        res.send(member);
    }else {
        res.status(404).send({message: `There is no member with the id!`});
    }
    
});

 

 

17.  기존 직원 정보 삭제하기 

 

-  Member 모델의 destroy 메소드를 사용하면 원하는 row를 삭제할 수 있음

- destroy 메소드도 프로미스 객체를 리턴하는데 그 작업 성공 결과로는 실제로 삭제된 row의 개수가 들어 있음

- 삭제가 row가 없다면 그런 id를 가진 직원이 없다는 내용의 리스폰스를 돌려주고 1개 이상 삭제가 되었다면 해당 개수의 row들이 삭제되었다는 메시지를 줌 

 

 

18. 모델의 주요 메소드 정리 

 

- 모델 = 클래스, 모델 객체 = 해당 클래스로 만든 객체 

 

1) findAll 메소드 

 

- 테이블에 있는 모든 row들을 조회 

const members = await Member.findAll();

- 특정 조건을 만족하는 row들만 조회 

  - where 프로퍼티를 가진 조건 객체를 넣어줌

const teamMembers = await Member.findAll({ where: { ~ } });

 

2) findOne 메소드 

 

- 테이블에 있는 특정 row 하나를 조회할 때 사용

const member = await Member.findOne({ where: { ~ } });

 

3) build, save 메소드 

 

- 테이블에 새로운 row를 추가할 때 사용 

- build 메소드로 하나의 row에 대응하는 모델 객체를 생성하고, 해당 모델 객체의 save 메소드를 호출 

const member = Member.build(newMember);
await member.save();

- create 메소드 사용

const member = Member.build(newMember);
await member.save();

 

4) update 메소드 

 

- 원하는 특정 row들을 수정하는 메소드 

- update 메소드가 리턴하는 Promise 객체는 그 작업 성공 결과로 배열 하나를 가지는데 해당 배열의 첫 번째 요소에는 갱신된 row 수가 담김

const arr = await Member.update(newInfo, { where: { ~ } });

 

5) destroy 메소드 

 

- 원하는 row를 삭제하는 메소드 

- destroy 메소드가 리턴하는 Promise 객체는 그 작업 성공 결과로 삭제된 row 수를 가짐 

const deletedCount = await Member.destroy({ where: { ~ } });