Sequelize 시리즈


2019. 07. 20 수정

 

이전 글에서 sequelize를 효율적으로 다루기 위한 sequelize-cli 모듈을 살펴보았습니다.

이 글에서는 sequelize-cli가 생성해준 파일을 이용하여, 모델을 정의하는 방법에 대해서 알아보도록 하겠습니다.

 

모델을 정의할 때는 직접 파일을 만들어서 직접 작성하는 방법이 있고, 커맨더 창을 이용해서 손쉽게 작성하는 방법이 있습니다.

우선 모델을 직접 작성하여 모델을 어떻게 정의 하는지 살펴본 후에, 커맨더 창을 이용해서 모델을 빠르게 작성하는 방법을 알아보도록 하겠습니다.

  • 개발환경
    • express-generator 4.16.1
    • MySQL 8.0.16
    • sequelize 5.10.1

 

 

 

 

1. 모델 정의하기 (1) - 직접 작성

sequelize init 명령어로 sequelize-cli를 실행하면, models 폴더와 그 아래에 index.js 파일이 생성된다는 것을 이전 글에서 확인했었습니다.

이제 index.js 모듈에서 모델을 취합하기위한 모델들을 정의할 것입니다.

우선 /models/users.js 파일을 생성하여 아래의 코드를 작성합니다.

 

users.js

module.exports = function(sequelize, DataTypes){
    let user = sequelize.define("User", {
        userID: {
            filed: "user_id",
            type: DataTypes.STRING(50),
            unique: true,
            allowNull: false
        },
        password: {
            field: "password",
            type: DataTypes.STRING(30),
            allowNull: false
        }
    }, {
        underscored: true,
        freezeTableName: true,
        tableName: "user"
    });
    return user;
}

이렇게 테이블을 정의하면, /models/index.js에서 반복문을 돌면서 models 폴더 내에 있는 파일들을 읽어들여, 모델들을 취합한다고 했었습니다.

따라서 이렇게 모델을 작성하면, index.js에서 users.js 파일을 읽어들여서 모델로 정의가 될 것입니다.

 

모델을 정의하는 메서드는 define() 메서드이며, 파라미터는 다음과 같습니다.

  • sequelize.define( "객체이름", 스키마 정의, 테이블 설정 )
  • 세 번째 인자인 테이블 설정 부분에서 tableName: "user"으로 작성했기 때문에, 테이블 이름은 user가 됩니다.
    • 즉, DB에 user라는 테이블을 정의하고, 이는 User라는 객체로 매핑됩니다.

모델을 정의하는 모든 방법을 다룰 수는 없으므로 더 자세한 내용은 공식 문서를 참고해주세요.

 

 

 

이제 테스트를 위해 app.js에 sync() 메서드가 작성되어 있는지 확인하고 서버를 실행한 후에,

커맨더 창에서 mysql에 user 테이블이 생성되었는지 확인하도록 하겠습니다.

sync()를 잘 모르시겠다면 여기를 참고해주세요. )

 

user 테이블이 잘 생성이 되었네요.

이런 식으로 직접 models 폴더 안에 js 파일을 생성하여, 테이블을 정의 및 반영할 수 있습니다.

 

그런데 userID, password 와 같은 필드에는 어떤 옵션들을 줄 수 있는지,

그리고 테이블 설정 부분에 있는 underscored, freezeTableName 같은 것들은 무엇인지 궁금할 수 있는데, 이 부분에 대해서는 마지막에 정리해보도록 하겠습니다.

 

 

 

 

2. 모델 정의하기 (2) - CLI로 정의

이번에는 직접 user.js 파일을 만들어서 모델을 정의하지 않고, 커맨더 창에서 모델을 손쉽게 만드는 방법에 대해 알아보겠습니다.

 

cli로 모델을 만들 때 기본 문법은 다음과 같습니다.

# sequelize model:create --name TABLE_NAME --attributes "COLUMN1:type, COLUMN2:type, COLUMN3:type"

 

예를 들어, 아래의 명령어를 커맨더 창에서 실행하면, 그 결과로 새로운 modelmigration이 생성됩니다.

# sequelize model:create  --name user2 --attributes "user_id:string, password:string"

 

  • migrations 폴더
    • 현재 시간을 이름으로 갖는 migration 파일이 생성
  • models 폴더
    • user2.js 파일이 생성

 

user2.js 파일을 열어보면 다음과 같습니다.

user2.js

'use strict';
module.exports = (sequelize, DataTypes) => {
  const user2 = sequelize.define('user2', {
    user_id: DataTypes.STRING,
    password: DataTypes.STRING
  }, {});
  user2.associate = function(models) {
    // associations can be defined here
  };
  return user2;
};

지금은 모델을 정의만 한 것이므로, 이제 서버를 실행하여 user2 테이블을 생성해보도록 하겠습니다.

# npm start

 

 

그런데 user2 테이블은 없고, user2s 테이블이 존재하고 있는 상황입니다.

그 이유는 cli로 테이블을 생성하면, 자동으로 테이블 명을 복수형으로 만들어주기 때문입니다.

  • ex)
    • user  =>  users
    • reply  =>  replies

 

테이블 명이 복수형으로 바뀌는 것을 확인 했으니, 스키마는 제대로 정의되어 있는지 확인해봅니다.

 

그런데 스키마를 보니까, user2.js 파일에서 작성하지 않은 id, createdAt, updatedAt 컬럼이 자동으로 생성된 것을 확인할 수 있습니다.

 

이처럼 cli를 통해 모델을 생성하면, 3개의 필드를 자동으로 생성해줍니다.

이와 같은 정의는 migration 파일에서 확인해 볼 수 있습니다. ( 참고로 이 파일에서 테이블 이름도 확인이 가능합니다. )

 

 

이렇게 cli를 통해 모델을 생성하면, 다음과 같은 특징이 있습니다.

  • 테이블 이름이 복수형으로 변경
  • 자동으로 id, createAt, updateAt 3개의 필드를 생성
  • migration 파일을 생성

 

 

 

 

3. 모델 정의하기 (2) - 옵션 부여

이번에는 cli로 모델을 정의할 때, 칼럼에 unique, Not NULL과 같은 옵션을 부여하는 방법을 알아보도록 하겠습니다.

cli로 모델을 생성하면, 컬럼의 옵션은 모두 기본 값으로 정의됩니다.

예를 들어, 문자열이면 "VARCHAR(255), NULL 허용, DEFAULT 값 없음"처럼 말이죠.

 

이번에는 cli로 모델을 정의한 후에, 제약조건을 부여하는 방법에 대해 알아보겠습니다.

/models/user2.js 파일을 아래와 같이 수정해서, 옵션을 부여하겠습니다.

'use strict';
module.exports = (sequelize, DataTypes) => {
  var user2 = sequelize.define('user2', {
    user_id: {
      type: DataTypes.STRING,
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
      }
    }
  });
  return user2;
};
  • user_id 필드
    • unique 옵션
  • password 필드
    • allowNull 옵션
  • 그리고 두 필드에 공통적으로 type 프로퍼티를 정의했습니다.
  • 필드의 여러 옵션들은 글 마지막 부분에 정리해놓았습니다.

 

이렇게 정의를 한 상태에서 테스트를 위해 기존의 user2s 테이블을 삭제한 후, 서버를 실행하고 다시 확인해보겠습니다.

$ drop table user2s;
$ desc user2s;

 

user_id의 Key 옵션과 password의 Null 옵션이 바뀐 것을 확인할 수 있습니다.

 

이와 같이 cli를 통해서 빠르게 파일을 만들어주고, 필요한 옵션들을 작성해주면 옵션이 정의된 테이블을 만들 수 있습니다.

( 서버를 실행했는데 테이블 생성이 안된다면 아래의 migration을 더 진행해보고 다시 시도해보세요. )

 

 

 

 

4. 모델 정의하기 (3) - migration

모델을 정의한 뒤에는 migration을 해줘야 하는데, migration이 무엇인지에 대한 설명을 해준 글이 있습니다. - 링크

 

 

"테이블에 column을 생성하기 위해서는 migration 안에 존재해야 한다."라고 하네요.

 

cli로 모델을 정의할 때 생성된 migration 파일과 /models/user2.js 파일은 같은 내용입니다.

그런데 위에서 옵션을 추가했기 때문에, /models/user2.js 파일은 수정되었기 때문에, 따라서 /models/user2.js 내용과 migration 파일의 테이블 정의를 맞춰줘야 합니다.

 

/migrations/20190720024218-create-user-2.js

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('user2s', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      user_id: {
        type: Sequelize.STRING,
        unique: true
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('user2s');
  }
};

/models/user2.js의 user_id, passwd 프로퍼티 부분만 복붙하면 되는데, 이 과정에서 DataTypes를 Sequelize로 바꿔줘야 합니다.

 

migration 파일을 위와 같이 수정했으면, 커맨더 창에서 아래의 명령어를 실행합니다.

# sequelize db:migrate

그러면 테이블 정의된 부분이 migrate 된 것이고, /models/user2s.js 파일과 migration 파일이 일치하게 됩니다.

 

 

 

migration

조금 더 migration 파일의 구성을 살펴보도록 하겠습니다.

 

 

migration이란 "up"기능과 "down"기능을 가진 일련의 데이터베이스 작업입니다.

  • "up"은 데이터베이스를 변경
    • sequelize db:migrate 명령어 실행 시, up에 정의된 코드 실행
  • "down"은 "up"이 실행되기 전의 상태로 데이터베이스를 복원
    • sequelize db:migrate:undo 명령어 실행 시, down에 정의된 코드 실행

더 자세한 정보는 공식 문서를 참고해주세요.

 

 

 

 

5. migration한 상태에서 수정하기 - undo

migration을 하지 않았을 때는 옵션을 부여했을 때와 같이 테이블의 필드를 마음대로 수정할 수 있었습니다.

그렇다면 migration까지 한 상태에서 테이블에 필드를 하나 더 추가하거나, 기존 필드에 옵션을 바꾸고 싶을 경우에는 어떻게 해야 할까요?

 

/models/user2.js 파일에서 아래와 같이 수정해보도록 하겠습니다.

'use strict';
module.exports = (sequelize, DataTypes) => {
  var user2 = sequelize.define('user2', {
    user_id: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
      }
    }
  });
  return user2;
};

user_id에 allowNull 옵션과 테이블에 email 컬럼을 추가했습니다.

 

그리고 나서 커맨더 창에서 user2s 테이블을 삭제하고 서버를 실행한 후, user2s 테이블을 다시 확인해보도록 하겠습니다.

 

보시는 바와 같이 스키마는 잘 수정되었습니다.

그런데 이게 끝이 아닙니다.

 

migration 파일을 보시면, 방금 /models/user2.js 파일에서 수정한 것이 반영되어 있지 않습니다.

그래서 models/user2.js과 테이블 정의를 맞추기 위해 내용을 수정하겠습니다.

'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('user2s', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      user_id: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
      },
      password: {
        type: Sequelize.STRING,
        allowNull: false
      },
      email: {
        type: Sequelize.STRING,
        allowNull: false
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('user2s');
  }
};
# sequelize db:migrate

 

/models/user2.js 파일과 migration 파일 내용이 일치하도록 수정했으니, 위의 명령어를 실행하겠습니다.

 

 

"변화된 것이 없다 ( No migrations were executed, database schema was already up to date. )"는 메시지를 출력합니다.

migration 파일을 삭제하고 위의 과정을 반복해도 소용없습니다. ( 파일만 삭제될 뿐입니다. )

 

 

즉, migration의 내용을 수정하기 위해서는 sequelize db:migrate:undo 명령어를 실행해서 "되돌리기"를 해야합니다.

이 명령어는 migration 파일의 down 기능을 수행합니다.

 

따라서 정리하면, migration한 상태에서 내용을 수정하기 위한 과정은 다음과 같습니다.

  1. sequelize db:migrate:undo 명령어 실행
    • sequelize db:migrate:undo:all 을 명령하면 모든 migration파일에 대해서 각 파일의 down에 정의된 코드가 실행됩니다.
  2. migration 파일과 /models/user2.js의 내용을 맞춘다.
  3. sequelize db:migrate를 명령어를 실행하면, migration이 create 되면서 내용이 맞춰짐.

 

 

*** error ***

혹시 migration 파일을 삭제하셨으면, "ERROR: Unable to find migration: 20171108014301-create-user-2.js"과 같은 메시지가 출력 될 것입니다.

그러면 위의 파일명을 가진 skeleton을 생성하면 해결 됩니다.

즉, 아래의 명령어를 실행하고, 생성된 파일의 이름을 20171108014301-create-user-2.js로 수정하면 됩니다.

# sequelize migration:create 20171108014301-create-user-2

 

 

 

 

6. 모델 정의 옵션

지금까지 모델을 정의하고, 모델 변경 및 옵션을 추가하는 방법에 대해 알아보았습니다.

마지막으로 모델을 정의할 때 사용할 수 있는 여러 옵션들에 대해 알아보도록 하겠습니다.

 

1) 대표적인 필드 옵션

  • type
    • Data type을 의미
  • primaryKey
    • 기본키 인지 아닌지 설정 ( default: false )
  • autoIncrement
    • SERIAL( auto increment )인지 아닌지 ( default: false )
  • allowNull
    • NOT NULL 조건인지 아닌지 ( default: true )
  • unique
    • Unique 조건인지 아닌지에 대한 옵션
  • comment
    • column에 대한 comment
  • validate
    • 각 column에 대한 validation check 옵션을 설정
    • 이메일 형식만 저장할 수 있도록 하거나, 숫자만 저장하는데 문자를 저장할 경우를 막을 수 있도록 하는 것이 그 예입니다. (참고)
    • validate 사용 예시 - 링크 

 

 

2) 데이터 타입

datatype에 대한 자세한 정보는 여기를 참고해주세요.

  • STRING
    • 변하는 길이의 문자열
  • CHAR
    • 고정된 길이의 문자열
  • TEXT
    • 제한되지 않은 길이의 텍스트
    • tiny, medium, long 으로 길이 설정가능
  • INTEGER
    • 32 bit Integer
  • BIGINT,  FLOAT,  DOUBLE,  DECIMAL
  • BOOLEAN
  • DATE
  • JSON
    • PostgreSQL 지원
  • ARRAY
    • PostgreSQL 지원
  • GEOMETRY
  • 등....

 

 

 

3) config

  • timestamps
    • Database에 해당 테이블이 언제 생성되었고 가장 최근에 수정된 시간이 언제인지 추적할 수 있도록 해줍니다.
    • Sequelize는 테이블을 생성한 후 자동적으로 createdAt, updatedAt column을 생성합니다.
    • 기능을 끄려면 false로 설정하면 됩니다.
  • paranoid 
    • paranoid가 true로 설정하면, deletedAt column이 table에 추가됩니다.
    • 해당 row를 삭제시 실제로 데이터가 삭제되지 않고 deletedAt에 삭제된 날짜가 추가되며, deletedAt에 날짜가 표기된 row는 find 작업시 제외됩니다.
    • 즉, 데이터는 삭제되지 않지만 삭제된 효과를 줍니다.
    • 이 옵션은 timestamps 옵션이 true여야만 사용할 수 있습니다.
  • underscored
    • 이 옵션이 true이면 column이름을 camalCase가 아닌 underscore방식으로 사용합니다.
  •  freezeTableName
    • Sequelize는 define method의 첫 번째 파라미터 값으로 tablename을 자동 변환하는데, 이 옵션의 값이 true이면 변환 작업을 하지 않도록 합니다.
  • tableName
    • 실제 Table 이름 정의
  • comment
    • table 에 대한 주석

 

 

 

 

이상으로 sequelize로 모델을 정의하는 방법에 대해 알아보았습니다.

글이 굉장히 길어졌네요... 그래도 테이블에 대한 정의를 코드로 관리할 수 있다는 점은 큰 장점인 것 같습니다.

 

[ 참고 ]

https://www.duringthedrive.com/2017/05/06/models-migrations-sequelize-node/

http://sequelize.readthedocs.io/en/latest/docs/migrations/