시퀄라이즈 모델 생성

Ywoosang·2021년 1월 29일
3

Sequelize

목록 보기
1/2
post-thumbnail

시퀄라이즈는 promise 기반 Node.js ORM 이다. Postgres,MySQL,MariaDB,SQLite 등 다양한 SQL 서버와 호환된다. 데이터 베이스 트랜잭션(Transaction), 관계(relation), 지연 로딩(lazy loading), 레플리케이션(Replication) 등을 지원한다.

시퀄라이즈는 Semantic Versioning 을 따르며 v10 버전 이상의 노드에서 호환된다.

Quick example

공식문서에서 명시한 기본 사용법은 아래와 같다.

const { Sequelize, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

class User extends Model {}
User.init({
  username: DataTypes.STRING,
  birthday: DataTypes.DATE
}, { sequelize, modelName: 'user' });

(async () => {
  await sequelize.sync();
  const jane = await User.create({
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
  });
  console.log(jane.toJSON());
})();

트랜잭션이란?
트랜잭션(Transaction 이하 트랜잭션)이란, 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 뜻한다. 간단하게 말하면 SQL을 이용해 데이터베이스를 접근 하는 것을 의미한다.

SELECT
INSERT
DELETE
UPDATE

추가로 공식문서의 API Reference 를 참고해 보면 좋을 것 같다.

Model 이란?

Models 는 시퀄라이즈에서 필수적이다. 모델은 데이터베이스의 테이블에 해당하는 추상적인 개념이다. 시퀄라이즈에서 모델은 Modelextends 하는 클래스(class)다.

모델은 시퀄라이즈가 데이터베이스의 테이블이름,테이블에서 어떤 컬럼이 있는지, 컬럼의 데이터 타입은 무엇인지와 같은 엔티티(entity) 를 인식하도록 해준다.

시퀄라이즈에서 모델은 이름을 가진다. 이 이름은 데이터베이스의 테이블 이름과 일치하지 않아도 된다. 대체로 시퀄라이즈의 모델은 User 와 같은 단수 이름을 가지고, 테이블은 Users 와 같은 복수 이름을 가진다.

Model 정의

참고
시퀄라이즈는 id 를 자동으로 넣어주기 때문에 아래를 생략 가능하다.

...
id : {
    type : DataTypes.INTEGER,
    primaryKey : true,
    autoIncrement : true
   }
...

시퀄라이즈에서 모델은 아래 두 가지 방법으로 정의될 수 있다.

Calling sequelize.define(modelName, attributes, options)
Extending Model and calling init(attributes, options) 

모델이 정의된 후, sequelize.models 에서 모델 이름을 통해 접근 가능하다.

에제를 통해 배우기 위해, user 를 나타내는 firstNamelastName 을 가진 모델을 생성해보자. 모델 이름은 User 이고 데이터베이스 테이블에서 나타내는 이름은 Users 다.

아래에서 두 가지 방법으로 모델을 정의했다. 모델이 정의된 후 sequelize.models.User 로 모델에 접근 가능하다.

Using sequelize.define

const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

const User = sequelize.define('User', {
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
});

// `sequelize.define` also returns the model
console.log(User === sequelize.models.User); // true

Extending Model

const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory');

class User extends Model {}

User.init({
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
  sequelize, // We need to pass the connection instance
  modelName: 'User' // We need to choose the model name
});

// the defined model is the class itself
console.log(User === sequelize.models.User); // true

내부적으로 sequelize.defineModel.init 으로 불린다. 따라서 두 방법은 근본적으로 동일하다.

Table name inference

위 두 방법을 보면, 데이터베이스 테이블 이름인 Users 는 정의되지 않은 반면, 시퀄라이즈 모델 이름인 User 는 주어졌다.

기본적으로 테이블 이름이 주어지지 않으면, 시퀄라이즈는 자동적으로 모델이름의 복수형을 테이블 이름으로 생성한다.

Enforcing the table name to be equal to the model name

freezeTableName: true 옵션을 줌으로써 위와 같이 테이블 이름이 자동으로 생성되는것을 멈출 수 있다. 이 옵션을 주면 복수형을 붙이지 않고 테이블 이름을 모델 이름과 같게 설정한다.

sequelize.define('User', {
  // ... (attributes)
}, {
  freezeTableName: true
});

위 예제는 테이블 이름과 모델 이름 모두 User 로 생성한다.

이 과정을 시퀄라이즈 인스턴스에 대해 전역적으로 설정하려면 아래와 같이한다.

...
const sequelize = new Sequelize('sqlite::memory:', {
  define: {
    freezeTableName: true
  }
});
...

이렇게 설정하면 모든 테이블이름이 모델 이름과 같게 설정된다.

Providing the table name directly

아래와 같은 방법으로 테이블 이름을 직접적으로 설정할 수 있다.

sequelize.define('User', {
  // ... (attributes)
}, {
  tableName: 'Employees'
});

Model synchronization

모델을 정의하는 것은 데이터베이스의 테이블에 대한 정보를 시퀄라이즈에게 전송하는 것이다.

하지만 만약 데이터베이스가 실제로 존재하지 않거나, 존재하지만 모델과 다른 컬럼을 가지는 등 차이가 있다면 어떨까?

이것이 바로 모델 동기화가 필요한 이유다. 모델은 model.sync(options) 라고 불리는 Promise 를 반환하는 비동기 함수에 의해 데이터베이스와 동기화될 수 있다. 이 함수가 실행되면 시퀄라이즈는 자동적으로 데이터베이스에 SQL 쿼리를 실행한다.

자바스크립트로 작성된 모델이 아닌 데이터베이스 테이블에서 일어난다는 것을 주목하자.

User.sync() : 테이블이 존재하지 않는다면 생성한다. 만약 테이블이 존재한다면 실행되지 않는다.
User.sync({ force: true }) : 테이블을 생성한다. 만약 테이블이 이미 존재한다면 기존에 있던 테이블을 삭제하고 새로 생성한다.

User.sync({ alter: true }) : 테이블에 어떤 컬럼이 있는지, 데이터 타입은 무엇인지 데이터베이스에 있는 테이블의 현재 상태를 체크한다. 그리고 시퀄라이즈 모델과 동기화 하기 위한 필수적인 변환 작업을 실행한다.

예를들어 아래와 같이 사용할 수 있다.

await User.sync({ force: true });
console.log("The table for the User model was just (re)created!");

Synchronizing all models at once

sequelize.sync() 를 이용해 자동적으로 모든 모델을 동기화할 수 있다.

await sequelize.sync({ force: true });
console.log("All models were synchronized successfully.");

Dropping tables

특정 모델과 관계된 테이블을 삭제한다.

await User.drop();
console.log("User table dropped!");

모든 테이블을 삭제한다.

await sequelize.drop();
console.log("All tables dropped!");

Database safety check

syncdrop 명령은 잘못 실행했을 시 리스크가 크기 때문에. 시퀄라이즈는 정규표현식을 이용한 match 옵션을 사용해 추가적인 안전 체크를 할 수 있도록 제공한다.

// This will run .sync() only if database name ends with '_test'
sequelize.sync({ force: true, match: /_test$/ });

Synchronization in production

위 설명에 나와있듯이 sync({ force: true })sync({ alter: true }) 옵션은 파괴적인 명령이다. 따라서 배포시에는 사용하지 않는것을 권장한다.

위 두 명령어 대신, 동기화(synchronization)는 시퀄라이즈 CLI 의 도움을 받아 향상된 마이그레이션(Migration) 개념으로 실행되어야 한다.

Timestamps

기본적으로 시퀄라이즈는 DataTypes.DATE 타입을 사용하는 createdAtupdatedAt 필드를 모든 모델에 추가한다. 시퀄라이즈를 이용해 생성 또는 업데이트하면 이 필드들이 자동적으로 설정된다. createdAt 필드는 생성된 시점에 대한 정보를 나타내는 타임스탬프를 포함하고 있고 updatedAt 필드는 마지막 업데이트에 대한 타임스탬프를 포함한다.

참고
이 작업은 Secretalize 에서 실행된다.(즉, SQL 트리거로는 수행되지 않음). 즉, 직접 SQL 쿼리(예: 다른 방법을 통해 Secretize 없이 수행된 쿼리)가 이러한 필드를 자동으로 업데이트하지 않는다.

timestamps: false 옵션을 통해 자동적으로 타임스탬프 필드가 설정되지 않도록 할 수 있다.

sequelize.define('User', {
  // ... (attributes)
}, {
  timestamps: false
});

또한 createdAt/updatedAt 중 하나의 옵션만 설정하는 것이 가능하고 생성되는 컬럼에 이름을 변경할 수도 있다.

class Foo extends Model {}
Foo.init({ /* attributes */ }, {
  sequelize,

  // don't forget to enable timestamps!
  timestamps: true,

  // I don't want createdAt
  createdAt: false,

  // I want updatedAt to actually be called updateTimestamp
  updatedAt: 'updateTimestamp'
});

Column declaration shorthand syntax

만약 컬럼을 특정짓는게 데이터타입밖에 없다면 아래와 같이 축약형으로 사용가능하다.

// This:
sequelize.define('User', {
  name: {
    type: DataTypes.STRING
  }
});

// Can be simplified to:
sequelize.define('User', { name: DataTypes.STRING });

Default Values

기본적으로 시퀄라이즈는 컬럼의 기본값을 Null 로 추정한다. 이러한 설정은 컬럼 정의에 defaultValue 인자를 추가해 변경할 수 있다.

sequelize.define('User', {
  name: {
    type: DataTypes.STRING,
    defaultValue: "John Doe"
  }
});

Sequelize.NOW 같은 몇몇 특정한 값들도 기본값으로 설정 가능하다.

sequelize.define('Foo', {
  bar: {
    type: DataTypes.DATETIME,
    defaultValue: Sequelize.NOW
    // This way, the current date/time will be used to populate this column (at the moment of insertion)
  }
});

Data Types

정의한 모든 컬럼은 반드시 데이터타입을 가져야 한다. 시퀄라이즈는 많은 빌트인 데이터타입들을 제공한다. 아래와같이 import 해 사용한다.

const { DataTypes } = require("sequelize"); // Import the built-in data types

Strings

DataTypes.STRING             // VARCHAR(255)
DataTypes.STRING(1234)       // VARCHAR(1234)
DataTypes.STRING.BINARY      // VARCHAR BINARY
DataTypes.TEXT               // TEXT
DataTypes.TEXT('tiny')       // TINYTEXT
DataTypes.CITEXT             // CITEXT          PostgreSQL and SQLite only.

Boolean

DataTypes.BOOLEAN            // TINYINT(1)

Numbers

DataTypes.INTEGER            // INTEGER
DataTypes.BIGINT             // BIGINT
DataTypes.BIGINT(11)         // BIGINT(11)

DataTypes.FLOAT              // FLOAT
DataTypes.FLOAT(11)          // FLOAT(11)
DataTypes.FLOAT(11, 10)      // FLOAT(11,10)

DataTypes.REAL               // REAL            PostgreSQL only.
DataTypes.REAL(11)           // REAL(11)        PostgreSQL only.
DataTypes.REAL(11, 12)       // REAL(11,12)     PostgreSQL only.

DataTypes.DOUBLE             // DOUBLE
DataTypes.DOUBLE(11)         // DOUBLE(11)
DataTypes.DOUBLE(11, 10)     // DOUBLE(11,10)

DataTypes.DECIMAL            // DECIMAL
DataTypes.DECIMAL(10, 2)     // DECIMAL(10,2)

Unsigned & Zerofill integers - MySQL/MariaDB only

MySQL 과 MariaDB 에서, INTEGER, BIGINT, FLOAT, DOUBLE 데이터타입은 아래와같이 UNSIGNED 혹은 ZEROFILL 로 할당될 수 있다.

DataTypes.INTEGER.UNSIGNED
DataTypes.INTEGER.ZEROFILL
DataTypes.INTEGER.UNSIGNED.ZEROFILL
// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER
// Same for BIGINT, FLOAT and DOUBLE

Dates

DataTypes.DATE       // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
DataTypes.DATE(6)    // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
DataTypes.DATEONLY   // DATE without time

UUID

DataTypes.UUID 은 PostgreSQL 과 SQLite 의 UUID , MySQL 의 CHAR(36) 데이터타입이 됩니다. 시퀄라이즈는 Sequelize.UUIDV1 또는 Sequelize.UUIDV4 를 사용해 필드들의 UUID 들을 자동적으로 생성할 수 있다.

{
 type: DataTypes.UUID,
 defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1
}

Column Options

컬럼을 정의할 때, 컬럼의 타입을 특정하는것과 allowNull,defaultValue 를 제외하고도
사용할 수 있는 많은 옵션들이 있다.

const { Model, DataTypes, Deferrable } = require("sequelize");

class Foo extends Model {}
Foo.init({
  // instantiating will automatically set the flag to true if not set
  flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },

  // default values for dates => current time
  myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },

  // setting allowNull to false will add NOT NULL to the column, which means an error will be
  // thrown from the DB when the query is executed if the column is null. If you want to check that a value
  // is not null before querying the DB, look at the validations section below.
  title: { type: DataTypes.STRING, allowNull: false },

  // Creating two objects with the same value will throw an error. The unique property can be either a
  // boolean, or a string. If you provide the same string for multiple columns, they will form a
  // composite unique key.
  uniqueOne: { type: DataTypes.STRING,  unique: 'compositeIndex' },
  uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },

  // The unique property is simply a shorthand to create a unique constraint.
  someUnique: { type: DataTypes.STRING, unique: true },

  // Go on reading for further information about primary keys
  identifier: { type: DataTypes.STRING, primaryKey: true },

  // autoIncrement can be used to create auto_incrementing integer columns
  incrementMe: { type: DataTypes.INTEGER, autoIncrement: true },

  // You can specify a custom column name via the 'field' attribute:
  fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' },

  // It is possible to create foreign keys:
  bar_id: {
    type: DataTypes.INTEGER,

    references: {
      // This is a reference to another model
      model: Bar,

      // This is the column name of the referenced model
      key: 'id',

      // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type.
      deferrable: Deferrable.INITIALLY_IMMEDIATE
      // Options:
      // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints
      // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction
      // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction
    }
  },

  // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL
  commentMe: {
    type: DataTypes.INTEGER,
    comment: 'This is a column name that has a comment'
  }
}, {
  sequelize,
  modelName: 'foo',

  // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options:
  indexes: [{ unique: true, fields: ['someUnique'] }]
});

Taking advantage of Models being classes

시퀄라이즈의 모델은 ES6 클래스이므로, 별도로 인스턴스를 추가하거나 클래스메소드를 추가할 수 있다.

class User extends Model {
  static classLevelMethod() {
    return 'foo';
  }
  instanceLevelMethod() {
    return 'bar';
  }
  getFullname() {
    return [this.firstname, this.lastname].join(' ');
  }
}
User.init({
  firstname: Sequelize.TEXT,
  lastname: Sequelize.TEXT
}, { sequelize });

console.log(User.classLevelMethod()); // 'foo'
const user = User.build({ firstname: 'Jane', lastname: 'Doe' });
console.log(user.instanceLevelMethod()); // 'bar'
console.log(user.getFullname()); // 'Jane Doe'

컬럼 관련 찾아볼만한 링크
https://jeonghwan-kim.github.io/node/2017/02/06/utf8mb4-in-sequelize.html

profile
백엔드와 인프라에 관심이 많은 개발자 입니다

0개의 댓글