본문 바로가기
Language/Node.js

[ORM] Sequelize

by 노믹 2022. 12. 24.

Sequelize란?

노드에서 MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리이다.

시퀄라이즈는 ORM (Object-relational Mapping)으로 분류되며, ORM은 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.

 

시퀄라이즈는 MySQL 외에도 MariaDB, PostgreSQL 등등 다른 데이터베이스에도 쓸 수 있다. 문법이 어느 정도 호환되므로 프로젝트를 다른 SQL 데이터베이스로 전활할 때도 편리하다.

 

시퀄라이즈를 쓰는 이유는 자바스크립트 구문을 알아서 SQL로 바꿔주기 때문이다. 따라서 SQL 언어를 직접 사용하지 않더라도 자바스크립트만으로 MySQL을 조작할 수 있고, 따라서 SQL 언어를 몰라도 MySQL을 어느 정도 다룰 수 있다.

 

Sequelize 모델 정의하기

MySQL의 테이블은 시퀄라이즈의 모델과 대응된다.

시퀄라이즈는 모델과 MySql의 테이블을 연결해주는 역할을 한다.

 

User 와 Comment 모델을 만들어 users 테이블과 comments 테이블에 연결해보자

시퀄라이즈는 기본적으로 모델 이름은 단수형(User), 테이블 이름은 복수형(users) 으로 사용한다.

 

models/user.js

const Sequelize = require('sequelize');

class User extends Sequelize.Model {

   // 스태틱 메소드
   // 테이블에 대한 설정
   static init(sequelize) {

      return super.init(
         {  // 첫번째 객체 인수는 테이블 필드에 대한 설정
            name: {
               type: Sequelize.STRING(20),
               allowNull: false,
               unique: true,
            },
            age: {
               type: Sequelize.SMALLINT,
               allowNull: false,
            },
            married: {
               type: Sequelize.BOOLEAN,
               allowNull: false,
            },
            comment: {
               type: Sequelize.TEXT,
               allowNull: true,
            },
            created_at: {
               type: Sequelize.DATE,
               allowNull: false,
               defaultValue: Sequelize.NOW,
            },
         },
         {  // 두번째 객체 인수는 테이블 자체에 대한 설정
            sequelize, /* static init 메서드의 매개변수와 연결되는 옵션으로, db.sequelize 객체를 넣어야 한다. */
            timestamps: false, /* true : 각각 레코드가 생성, 수정될 때의 시간이 자동으로 입력된다. */
            underscored: false, /* 카멜 표기법을 스네이크 표기법으로 바꾸는 옵션 */
            modelName: 'User', /* 모델 이름을 설정. */
            tableName: 'users', /* 데이터베이스의 테이블 이름. */
            paranoid: false, /* true : deletedAt이라는 컬럼이 생기고 지운 시각이 기록된다. */
            charset: 'utf8', /* 인코딩 */
            collate: 'utf8_general_ci'
         }
      );
   }

   // 다른 모델과의 관계
   static associate(db) { // 인자로 index.js에서 만든 여러 테이블이 저장되어있는 db객체를 받을 것이다.
      
      db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', onDelete: 'cascade', onUpdate: 'cascade' });
      // db.User (hasMany) db.Comment = 1:N 관계 이다.  
      // db.User는 가지고있다. 많이. db.Comment를
   }
};

module.exports = User;

 

models/comment.js

const Sequelize = require('sequelize');

class Comment extends Sequelize.Model {
   static init(sequelize) {
      return super.init(
         {
            comment: {
               type: Sequelize.STRING(100),
               allowNull: false,
            },
            created_at: {
               type: Sequelize.DATE,
               allowNull: true,
               defaultValue: Sequelize.NOW,
            },
         },
         {
            sequelize,
            timestamps: false,
            modelName: 'Comment',
            tableName: 'comments',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci'
         });
   }

   static associate(db) {
      db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id', onDelete: 'cascade', onUpdate: 'cascade'});
      // db.Comment (belongTo) db.User = N:1 관계 이다.
      // db.Comment는 속해있다. db.User에게
   }
};

module.exports = Comment;

모델은 Sequelize.Model을 확장한 클래스로 선언한다.

 

모델은 static init 메서드와 static associate 메서드로 나뉘는데,

  init 메서드에서는 테이블에 대한 설정을 하고,

  associate 메서드에는 다른 모델과의 관계(1:1, 1:N)를 적는다.

 

init 메서드의 부모 콜백 super.init 메서드는

첫 번째 인수는 테이블 컬럼에 대한 설정이고,

두 번째 인수는테이블 자체에 대한 설정이다.

 

시퀄라이즈는 알아서 id를 기본 키로 연결하므로, id 컬럼은 따로 적어줄 필요는 없다.

 

시퀄라이즈의 자료형

시퀄라이즈의 자료형은 MySQL과 조금 다르다.

아래 비교를 통해 맞는 옵션을 입력하자.

MySQL Sequelize
VARCHAR(100) STRING(100)
INT INTEGER
TINYINT BOOLEAN
DATETIME DATE
INT UNSIGNED INTEGER.UNSIGNED
NOT NULL allowNull: false
UNIQUE unique: true
DEFAULT now() defaultValue: Sequelize.NOW
ZEROFILL INTEGER.ZEROFILL

 

시퀄라이즈 옵션

super.init의 두 번째 인수는 테이블 옵션이다.

 

sequelize

static init 메서드의 매개변수와 연결되는 옵션으로 db.sequelize 객체를 넣어야 한다.

나중에 model.index.js에서 연결한다.

 

timestamps

이 속성 값이 true면 시퀄라이즈는 createdAtupdatedAt 컬럼을 추가하며, 각각 로우가 생성될 때와 수정될 때의 시간이 자동으로 입력된다.

(그러나 예제에선 직접 created_at 컬럼을 만들었으므로 지금은 timestamps 속성이 필요없다. 나중엔 꼭 true로 하고 하자)

 

underscored

시퀄라이즈는 기본적으로 테이블명과 컬럼병을 카멜 표기법(Camel Case)으로 만든다.

이를 스테이크 표기법 (Snake Case)으로 바꾸는 옵션이다. (예를 들어 updatedAt을 updated_at으로)

 

modelName

모델 이름을 설정할 수 있다. 노드 프로젝트에서 사용한다.

 

tableName

실제 데이터베이스의 테이블 이름

기본적으로 모델 이름의 소문자 및 복수형으로 만든다.

예를 들어 모델 이름이 User 라면 테이블 이름은 users 이다.

 

paranoid

true로 설정하면 deletedAt이라는 컬럼이 생긴다.

로우를 삭제할 때 완전히 지우지 않고, deletedAt에 지운 시각이 기록된다.

로우를 조회하는 명령을 내렸을 경우 deletedAt의 값이 null인 로우를 조회한다.

이렇게 하는 이유는 후에 로우를 복원하기 위해서다. 로우를 복원해야 할 상황이 생길 것 같다면 미리 true로 설정해두자.

 

charset / collate

각각 utf8 과 utf8_general_ci 로 설정해야 한글이 입력된다.

이모티콘까지 입력할 수 있게 하고 싶다면 utf8mb4 와 utf8mb4_general_ci를 입력한다.

 

 

이제 models/index.js에 두 테이블 파일을 연결시킨다.

const Sequelize = require('sequelize');

// 클래스를 불러온다.
const User = require('./user')
const Comment = require('./comment')

const env = process.env.NODE_ENV || 'development';

// config/config.json 파일에 있는 설정값들을 불러온다.
// config객체의 env변수(development)키 의 객체값들을 불러온다.
// 즉, 데이터베이스 설정을 불러온다고 말할 수 있다.
const config = require("../config/config.json")[env]

const db = {};

// new Sequelize를 통해 MySQL 연결 객체를 생성한다.
const sequelize = new Sequelize(config.database, config.username, config.password, config)

// 연결객체를 나중에 재사용하기 위해 db.sequelize에 넣어둔다.
db.sequelize = sequelize; 

// 모델 클래스를 넣음.
db.User = User;
db.Comment = Comment;

// 모델과 테이블 종합적인 연결이 설정된다.
User.init(sequelize); 
Comment.init(sequelize);

// db객체 안에 있는 모델들 간의 관계가 설정된다.
User.associate(db);
Comment.associate(db);

// 모듈로 꺼낸다.
module.exports = db;

db라는 객체에 User과 Comment 모델을 담았다.

앞으로 db 객체를 require하여 User과 Comment 모델에 접근할 수 있을 것이다.

또한 User.init과 Comment.init은 각각의 모델의 static.init 메서드를 호출하며, init이 실행되어야 테이블이 모델로 연결된다.

다른 테이블과의 관계를 연결하는 associate 메서드 역시 실행해둔다.

 

Sequelize 관계 정의하기

이번엔 users 테이블과 comments 테이블 간의 관계를 정의해보자.

사용자 한 명은 댓글을 여러 개 작성할 수 있지만, 댓글 하나에 작성자가 여러 명일 수는 없다.

이러한 관계를 일대다 (1:N) 관계라고 한다. 위 관계에서는 사용자가 1이고 댓글이 N이다.

 

다른 관계로는 일대일 (1:1), 다대다 (N:M) 관계가 있다.

일대일 관계로는 사용자와 사용자의 대한 정보 테이블을 예로 들 수 있으며, 다대다 관계로는 게시글 테이블과 해시태그 (#) 테이블 관계를 예로 들 수 있겠다.

한 게시글에 여러 해시태그가 달릴 수 있으며, 한 해시태그 또한 여러 게시글에 달릴 수 있다.

 

1:N관계 (hasMany, belongsTo)

시퀄라이즈에서는 1:N 관계를 hasMany 메서드로 표현한다.

users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들도 같이 불러올 수 있다.

 

반대로 belongsTo 메서드도 있다.

이는 comments 테이블의 로우를 불러올 때 연결된 users 테이블의 로우를 가져온다.

보통은 외래키가 붙은 테이블이 belongsTo 가 된다.

 

이제 모델 각각의 static associate 메서드에 정의를 해줘야 한다.

/* user.js */

    static associate(db) {
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', onDelete: 'cascade', onUpdate: 'cascade' });
        // db.User (hasMany) db.Comment = 1:N 관계 이다.
        // 남(db.Comment)의 컬럼 commenter가 내(db.User) 컬럼 id를 참조 하고 있다.  
    }
};
// comment.js

    static associate(db) {
        db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id', onDelete: 'cascade', onUpdate: 'cascade'});
        // db.Comment (belongTo) db.User = N:1 관계 이다.
        // 내(db.Comment)의 컬럼 commenter는 남(db.User) 컬럼 id에 속해 있다.  
    }
};

간단히 User가 많은 댓글을 가질 수 있으니 User.hasMany가 되는 것이고,

Comment는 한 User에 속할 수 있으니 Comment.belongsTo가 되는 것이다.

 

둘이 소통하는 키는 foreignKey인 commenter이며,

User의 sourceKey는 곧 Commenter의 targetKey가 된다 (hasMany에서는 sourceKey, belongsTo에서는 targetKey)

 

foreignKey를 따로 지정하지 않는다면 이름이 모델명+기본 키인 컬럼이 모델에 생성된다.

즉, 예를 들어 위 예제에서 commenter를 foreignKey로 설정해 주지 않았다면, 제약조건에 따라 모델명인 User과 기본 키인 id가 합쳐진 UserId가 foreignKey로 따로 생성된다.

 

1:1관계 (hasOne, belongsTo)

1:1 관계에서는 hasMany 대신 hasOne을 사용한다.

foreignKey, sourceKey, targetKey의 사용법은 1:N관계와 같다.

hasOne도 1:1, belongsTo도 1:1이면 누가 기준으로 될지 애매할 때가 있다.

이때 외래키가 붙은 모델은 belongsTo로 기준을 잡아주면 된다.

 

N:M 관계 (belongToMany)

시퀄라이즈에서는 N:M 관계를 belongsToMany 메서드로 표현한다.

이 경우엔 어느 한 테이블이 어느 다른 테이블에 종속되는 관계가 아니다.

많이 쓰지 않는다.

 

 

참고 블로그 - https://inpa.tistory.com/entry/ORM-%F0%9F%93%9A-%EC%8B%9C%ED%80%84%EB%9D%BC%EC%9D%B4%EC%A6%88-%EB%AA%A8%EB%8D%B8-%EC%A0%95%EC%9D%98%ED%95%98%EA%B8%B0

'Language > Node.js' 카테고리의 다른 글

객체 지향 (Object-Oriented)  (0) 2022.12.26
[OpenAPI] Swagger  (1) 2022.12.25
미들웨어  (0) 2022.12.19
Access Token, Refresh Token  (0) 2022.12.19
JWT란?  (0) 2022.12.19