Server/Node.js (Express)

[Node.js] Sequelize : ORM(Object-relational Mapping) 사용해보기

ooeunz 2019. 12. 16. 13:14
반응형

Sequelize란?

이번 포스팅에서는 Node.js의 ORM 기술인 sequelize를 사용해보도록 하겠다. 시퀄라이즈는 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다. 시퀄라이즈는 자바스크립트 구문을 알아서 SQL로 변환해주어서 좀 더 의존성이 낮은 프로그래밍을 할 수 있도록 도와준다. 또한 어느 정도 문법이 호환되므로 MySQL 뿐만 아니라, MariaDB, PostgreSQL, SQLite 등 다른 데이터베이스로 전환이 비교적 자유롭다.

 

※ 좀 더 자세한 내용은 아래 URI에서 확인하도록 하자.

 

[Database] ORM(Object-relational Mapping)이란?

ORM이란? ORM(Object-relational Mapping)이란 OOP 간의 호환되지 않는 데이터를 변환하는 프로그래밍 기법으로 쉽게 말해 객체로 관계형 데이터베이스를 관리하는 기술이다. 대부분의 개발 언어 platform마다 제..

ooeunz.tistory.com

 


sequelize install

그럼 간단하게 시퀄라이즈를 실습해보도록 하겠다. 먼저 express-generator를 이용해 시퀄라이즈를 실습할 프로젝트를 생성하고 mysql, sequelize, sequelize-cli 패키지들을 설치해주자.

 

※ sequelize-cli는 sequelize CLI(command-line-interface)를 사용하기 위한 패키지 이므로 전역으로 설치한다. 

express sequelize-project	// express 프로젝트 생성

npm install sequelize mysql2

npm install -g sequelize-cli	// 전역으로 설치

sequelize-cli를 이용하면 초기 설정을 손쉽게 할 수 있다. 아래의 명령어를 터미널에 입력하자.

sequelize init

 

 

그럼 아래와 같은 명령어들이 뜨면서 config, models, migration, seedeers 디렉터리들이 생성된다.

 

간단하게 새로 생긴 디렉터리들을 살펴보자면 models/index.js는 config/sequelize.json의 설정 값을 읽어서 시퀄라이즈를 생산한 후 models 디렉터리의 model들을 db객체에 정의한다.

 

 


config

config > config.json으로 이동해서 아래와 같이 데이터베이스를 연결해주도록 한다.

{
  "development": {
    "username": "root",
    "password": "database 비밀번호",
    "database": "database 이름",
    "host": "host 주소",
    "dialect": "mysql",
    "operatorsAliases": false
  },
  
  ...
}

 


models

models은 실제로 MySQL과 매핑될 객체들이 정의된다. 객체들을 정의하기 전에 먼저 서버에 모델을 연결 켜주도록 한다.

 

app.js

 

models > model에 가서 MySQL에 정의한 테이블을 시퀄라이즈에 정의하도록 하자. MySQL의 테이블과 컬럼 내용은 시퀄라이즈에 정의하는 내용과 일치해야 정확하게 대응되므로 MySQL의 내용과 동일하게 작성하도록 한다. 다만 id만은 시퀄라이즈가 알아서 연결하므로 id 컬럼은 따로 작성해주지 않아도 된다.

 

※ MySQL테이블 작성은 model에 정의된 내용과 똑같이 생성하면 이 포스팅에서는 다루지 않겠다. 혹여 아직 MySQL을 다룰 줄 모른다면 앞의 포스팅들을 참고하자.

 

user 테이블

module.exports = (sequelize, Datatypes) => {
  const user = sequelize.define('user', {
    name: {
      type: Datatypes.STRING(20),
      allowNull: false,
      unique: true
    },
    age: {
      type: Datatypes.INTEGER.UNSIGNED,
      allowNull: false,
    },
    comment: {
      type: Datatypes.TEXT,
      allowNull: true,
    },
  }, {
    timestamps: true,
  });
};

 

comment 테이블

module.exports = (sequelize, DataTypes) => {
  return sequelize.define('comment', {
    comment: {
      type: DataTypes.STRING(100),
      allowNull: false,
    }
  }, {
    timestamps: true
  });
};

 

코드를 읽다보면 대부분 직관적이기 때문에 이해할 수 있을 거라 생각한다. 다만 timestamps 속성은 조금 생소할 수 있는데, 만약 timestamps 속성이 true면 createdAt(생성 시간)과 updatedAt(수정 시간)을 자동으로 입력해준다.

 

 

※ 시퀄라이즈 자료형은 MySQL과 조금 다르므로 아래 표를 참고하도록 하자.

Sequelize MySQL
STRING VARCHAR
INTEGER INT
BOOLEAN TINYINT
DATE DATETIME

 

 

이제 model을 생성했으므로 model들을 시퀄라이즈와 연결해주고, 테이블 간의 관계를 정의해주어야 한다. 관계에는 1 : 1 관계, 1 : N 관계, N : M관계가 있고 각각의 관계들을 시퀄라이즈는 메서드로 표현한다.

 

※ 만약 위의 관계들에 대해 모른다면 아래 URI를 참고하자.

 

[Database] RDB(relational database)에서 테이블간의 관계 (1:1, 1:N, N:M)

관계형 데이터베이스는 두 entity끼리 관계를 맺을 수 있기 때문에 이러한 이름을 가지게 되었다. 따라서 rdb를 사용하다 보면 foreign key를 이용하여 테이블 간의 관계를 정의해야 하게 되는데, 이러한 관계에..

ooeunz.tistory.com

 

시퀄라이즈에서는 관계를 아래와 같은 메서드들로 정의한다.

 

1 : N

  • 1 -> N : hasMany
  • N -> 1 : belongsTo

 

1 : 1

  • 1 -> 1 : hasOne

 

N : M

  • N -> M : belongsToMany

이 중, 우리가 예시로 작성하고 있는 user와 comment관계는 1 : N 관계 이므로 models > index.js에 아래와 같이 작성해줄 수 있다.

// model을 sequelize에 연결해주는 코드
db.User = require('./user')(sequelize, Sequelize);
db.Comment = require('./comment')(sequelize, Sequelize);


// model들 간의 관계를 정의해주는 코드
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id'});
db.Comment.belongsTo(db.User, { foreignKey: 'commenter', sourceKey: 'id'});

 

이제 Node 서버를 실행시켜보도록 하겠다. 서버를 실행시키면 시퀄라이즈가 스스로 SQL문을 실행시켜주는데, 자세히 읽어보면 IF NOT EXISTS라고 되어있는데, 만약 위에서 정의한 테이블들이 존재하지 않을 시에는 자동적으로 위의 테이블들을 생성해주게 된다.

 

 


sequelize query

앞서 이야기 했듯이 시퀄라이즈는 객체와 데이터베이스 사이에서 서로를 매핑해주는 역할을 한다. SQL문을 자바스크립트로 생성하는 형태이기 때문에 시퀄라이즈만의 방법을 따라야 한다. (JPA와 같은 다른 ORM 역시 각자만의 방법이 있지만 큰 틀은 크게 다르지 않다.) 시퀄라이즈 쿼리는 프로미스를 반환하기 때문에 then을 붙여 결괏괎을 받을 수 있다. 프로미스 형태이기 때문에 async / await 문법 역시 함께 사용할 수 있다.

 

create, select

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


// create
User.create({
  name: 'ooeunz',
  age: '25',
  comment: '안녕하세요'
});


// select
User.findOne({});

User.findAll({});


// select column
User.findAll({
  attributes: ['name', 'comment']
});

 

처음 시퀄라이즈 문법을 접하게 되면 조금 당황할 수 있다. 아마 "이것만으로 쿼리를 수행할 수 있어?!"라는 생각이 들 수 있다. 앞서 이야기했듯 시퀄라이즈는 객체와 데이터베이스를 사이에서 매핑해준다. 때문에 해당 코드를 작성하면 시퀄라이즈가 알아서 데이터베이스에 매핑해준다.

 

위의 코드를 살펴보면 require('../models); 라는 코드를 유의할 필요가 있다. 정확히 말하면 models의 index.js를 require 하는 코드이지만 해당 디렉터리를 require 하면 index.js로 자동으로 경로가 설정되기 때문에 index.js는 생략할 수 있다. 즉 models/index.js에 정의했던 User를 불러오는 코드이다. 이후의 코드 중에 살펴볼만한 것은 findAll에 attributes이다. attributes는 원하는 전체를 불러오는 코드가 아닌 특정 컬럼만 불러오고 싶을 때 이용한다. 위의 코드를 쿼리로 살펴본다면,

 

SELECT name, comment FROM database.table;

 

과 같은 형태가 될 것이다. 그럼 계속해서 다른 쿼리들도 살펴보도록 하겠다.

 

 

쿼리 수행에 조건

const { User, Sequelize: { Op } } = require('../models');

// 초과
User.findAll({
  attributes: ['name', 'comment'],
  where: {
    age: { [Op.gt]: 30 }
  }
})

// or
User.findAll({
  attributes: ['name', 'comment'],
  where: {
    [Op.or]: [{ age: {[Op.gt]: 25} }, { name: 홍길동 }]
  }
})

// order & limit
User.findAll({
  attributes: ['name', 'comment'],
  order: [['age', 'DESC']],
  limit: 1
})

이번에는 models에서 Sequelize: { Op } 객체를 함께 받아왔다. Op객체에는 여러가지 활용 가능한 연산자들이 포함되어있다.

  • Op.gt(초과)
  • Op.gte(이상)
  • Op.lt(미만)
  • Op.lte(이하)
  • Op.ne(같지 않음)
  • Op.or(또는)
  • Op.in(배열 요소 중 하나)
  • Op.notIn(배열 요소와 모두 다름)

추가적으로 order와 limit과 같은 쿼리역시 수행할 수 있다.

 

 

수정, 삭제

// update
User.update({
  comment: '수정 내용'
}, {
  where: { id: 1}
});


// delete
User.destory({
  where: { id: 1}
});

수정과 삭제는 따로 설명하지 않아도 이해할 수 있는 코드라고 생각한다.

 


여기까지 시퀄라이즈의 개념과 대략적인 사용방법에 대해 알아봤다. 대략적인 사용방법을 보면서 느꼈을 수 있지만 어느 정도 기본적인 기능을 사용하는 것에 있어서는 데이터베이스나 쿼리에 대해 깊은 이해가 있지 않더라도 ORM으로 쉽게 데이터베이스를 조작할 수 있다. 하지만 다시 한번 강조하지만 ORM은 데이터베이스와 객체 위에 있는 개념이므로 데이터베이스에 관한 깊은 이해가 동반되어야 좀 더 큰 프로젝트에서 자유롭게 ORM을 사용할 수 있다. 그러니 편리함에 익숙해지기보다는 먼저 데이터베이스를 이해하는 것에 초점을 맞추는 것을 권고한다. :)

반응형