201027_TIL

oh_ji_0·2020년 10월 27일
2

TIL

목록 보기
51/61

TODAY I LEARNED

-express
-sequelize ORM

@@ 오늘은 sequelize에 대한 스프린트를 진행했다. mySQL 배울 때보다 오히려 어렵게 느껴졌다. 마이그레이션과 모델을 관리하는 것이 복잡하게 느껴지고(분명 왜 이렇게 하는지에 대해선 필요성을 알지만) 개념 이해가 잘 되지 않았다.

모델-마이그레이션의 차이에 대해서는 이해했지만, association에서 다대다 관계에서 조인테이블 생성하는 법 설정하는 법은 아직 잘 모르겠고, association 및 FK키를 설정하는 법 등은 아직도 헷갈리는 게 많다. 공식문서가 잘 정리된 편인데도 불구하고 파악하기가 쉽지 않고 개념에 대한 이해와 문법에 대한 이해가 뒤섞여서 정리가 좀처럼 되지 않는 기분이다.

레퍼런스 코드를 보면서 내일 다시금 고민을 좀 더 해봐야겠다.

express

  • res.json()
  • res.status().end()
  • res.redirect(statusCode, 'url');

Sequelize

model

orm, model 부분은 생성된 것에서 많이 고칠 필요가 없다.

그러나 defaultValue 등 마이그레이션으로 생성할 때 잡아 줄 수 없는 부분에 대해서

잡아줘야 한다. 마이그레이션은 테이블 스키마에 대한 버전관리 부분이고,

모델은 앞으로 DB에 넣어줄 레코드 부분에 관여한다.

그렇기 때문에 defaultValue값은 마이그레이션과 모델에 둘 다 잡아줘야한다.

Sequelize-cli 설치

npm install --save-dev sequelize-cli

부트스트랩핑 (초기 파일 설치, 마이그레이션 폴더등 )

npx sequelize-cli init

Configuration 설정 config내의 config.json설정 수정

Creating the first Model

npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string

초기 스키마를 잡아준다. 해당 값을 입력하면 migrations 폴더 내에 마이그레이션 파일이 생성.

위의 대문자로 해야 해당 모델 클래스가 User 대문자로 시작이 되게 된다.

모델이 생성될 때 해당 네임에 대해서 복수화가 진행돼서 테이블 이름이설정된다.

sequelize는 id, createAt, updateAt의 컬럼을 자동으로 추가한다.

id는 auto_increment 속성으로 고유한 pirmary 값으로 설정되며, 오름차순으로 번호를 할당한다.

모델에 고유한 값이 들어와야 할 경우 unique 키를 다음과 같이 설정해준다

//...생략
id: {
    allowNull: false,
    primaryKey: true,
    type: DataTypes.UUID,
    defaultValue: DataTypes.UUIDV4
},
email: {
    type: DataTypes.STRING,
    unique: true
},
createdAt: {
    allowNull: false,
    type: Sequelize.DATE
},

name: {
  type: DataTypes.STRING,
  allowNull: false,
  validate: {
    len: {
      args: [3, 50],
      msg: "Your to-do item name must be between 3 and 50 characters.  Please try again."
    }
  }
},
//...생략

Data type엔 uuid를 사용할 수 있다.

모델은 기준을 충족했는지 확인하고 레코드로 값을 저장한다. 해당 기준을 충족하지 않으면 레코드에 추가되지 않는다. 모델에 전달된 대부분의 데이터를 저장할 수 있도록 프론트엔드 유효성 검사 및 백엔드 삭제를 사용해서, 저장 실패를 방지해야한다.

위처럼 validate 를 설정하여 옵션을 설정하고 해당 유효성 검사를 통과하지 못할 경우 내보낼 msg를 설정할 수도 있다.

테이블 구조의 모델에 대한 변경 사항을 반영하려면 사용자 마이그레이션을 편집하여 해당 내용을 반영해줘야 한다.

migrations

DB 스키마 설정에 대한 파일, 마이그레이션을 진행하면 해당 파일을 참고하고 디비로 해당 스키마대로 이주(마이그레이션)

runnning migrations

npx sequelize-cli db:migrate

마이그레이션 파일대로 마이그레이션 실행.

이미 파일대로 실행된 적이 있으면 마이그레이션이 진행되지 않는다.undo를 해줘야 한다.

undoing migrations

npx sequelize-cli db:migrate:undo

//특정파일 undo 하고싶을땐
npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js

seed 는 더미 데이터 만들때, 샘플 데이터 테스트 데이터로 채우는 데 사용할 수 있는 데이터 변경이다.

Migration Skeleton

마이그레이션 스켈레톤 생성

npx sequelize-cli migration:generate --name migration-skeleton

스켈레톤 파일

인자 쿼리 인터페이스는 데이터베이스를 수정하는데 사용할 수 있고, shependize객체는 STRING, NUTER 과 같은 사용 가능한 데이터 유형을 저장한다.

장애가 발생할 경우, 모든 명령이 성공적으로 실행되지 않으면 롤백이 진행되는데, 이것을 스켈레톤 파일이 자동으로 관리해준다.

up은 새로운 상태로의 변화 , down은 변경 사항을 되돌리는 로직이다. 두 함수는 promise를 반환해야 한다.

컬럼 추가 변경의 스켈레톤 예시 (async/await)

module.exports = {
  async up(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();
    try {
      await queryInterface.addColumn(
        'Person',
        'petName',
        {
          type: Sequelize.DataTypes.STRING,
        },
        { transaction }
      );
      await queryInterface.addIndex(
        'Person',
        'petName',
        {
          fields: 'petName',
          unique: true,
          transaction,
        }
      );
      await transaction.commit();
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
  },
  async down(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();
    try {
      await queryInterface.removeColumn('Person', 'petName', { transaction });
      await transaction.commit();
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
  }
};

해당 스켈레톤 작성 후,

마이그레이션 실행한다. 잘못 수정해야할 땐 undo하여 수정 후 다시 실행한다.

해당 파일은 마이그레이션 파일의 down 메소드와 관련이 있다.

down메소드가 제대로 설정되지 않은채로 undo를 실행해버리면, 수정이 힘들어질 수 있다.


모델은 해당 레코드 값이 어떻게 들어갈 건지를 정해준다.

마이그레이션은 해당 테이블 스키마에 대한 이야기다.

그렇기 때문에 어떤 값을 수정해줄 때 둘 다 수정을 해줘야 한다.

foreign key 설정 (마이그레이션 스켈레톤)

다른테이블 컬럼을 참조 하는 키.

참조 컬럼을 orm의 마이그레이션에 정의해줄 수 있다.

foreign키를 지정해주는 방법은 아래 참고.

마이그레이션에 스켈레톤에 정의하거나,

Foreign Key

queryInterface.addConstraint('Posts', {
  fields: ['username'],
  type: 'foreign key',
  name: 'custom_fkey_constraint_name',
  references: { //Required field
    table: 'target_table_name',
    field: 'target_column_name'
  },
  onDelete: 'cascade',
  onUpdate: 'cascade'
});

UNIQUE

queryInterface.addConstraint('Users', {
  fields: ['email'],
  type: 'unique',
  name: 'custom_unique_constraint_name'
});

CHECK

queryInterface.addConstraint('Users', {
  fields: ['roles'],
  type: 'check',
  where: {
     roles: ['user', 'admin', 'moderator', 'guest']
  }
});

Default - MSSQL only

queryInterface.addConstraint('Users', {
   fields: ['roles'],
   type: 'default',
   defaultValue: 'guest'
});

Primary Key

queryInterface.addConstraint('Users', {
   fields: ['username'],
   type: 'primary key',
   name: 'custom_primary_constraint_name'
});

삭제는 신중하게 하는 것이 좋다. 정말 명확하게 아무도 쓰지 않는 게 드러나거나, 확신이 있을 때 실행해야 한다.


모델 Association ( hasMany, hasOne, belongsTo )

https://sequelize.org/master/class/lib/associations/base.js~Association.html

https://sequelize.org/master/manual/assocs.html

model/user.js 예시

user : context 의 관계는 1:N

user.hasMany(models.Context)

일대다 모델에선 다에 해당하는 테이블에 FK 를 넣는 것이 Best Practice 이다.

user.id = context.userId

FK키, context에 PK는 User모델에.

PK를 가진 User모델과 FK를 가진 Context모델

'use strict';
module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define('User', {
    ...
  }, {
    classMethods: {
      associate: function(models) {
        User.hasMany(models.Context, {
          foreignKey: 'UserId',
		      onDelete: 'CASCADE'
        });
      }
    }
  });
  return User;
};

sequelize는 대상 및 원본 모델 모두에 정의돼야 한다.

PK를 포함하는 모델 및 FK를 포함하는 모델이 있을 때

PK를 포함하는 모델은

(hasMany (1:many), hasOne(1:1)을 사용

FK를 포함하는 모델은 belongTo(1:1, 1:many)를 사용한다.

FK 키를 포함하는 모델의 열 목록에 FK 키 열을 넣으면

equelize는 열을 두번 만들려고 하기 때문에 열이 이미 존재한다는 오류가 발생하게 된다.

association에서만 foreign key를 설정해라.

PK모델과 FK모델을 연결짓기 위해 FK모델의 외래키 이름을 PK모델에 전달한다.(옵션으로)

또한 onDelete도 설정해줄 수 있는데, CASCADE 의 의미는

우리가 만약 사용자를 삭제하면 User의 컨텍스트를 삭제해야한다고 명시하는 것.

또 다른 옵션 SET NULL

'use strict';
module.exports = function(sequelize, DataTypes) {
  var Context = sequelize.define('Context', {
    ...
  }, {
    classMethods: {
      associate: function(models) {
        Context.hasMany(models.Task, {
          foreignKey: 'ContextId',
	      onDelete: 'CASCADE'
        });
        //Context.belongsTo(models.User, {
        //  foreignKey: 'UserId',
         // onDelete: 'CASCADE'
        //});
        // associations can be defined here
      }
    }
  });
  return Context;
};

models/task.js

'use strict';
module.exports = function(sequelize, DataTypes) {
  var Task = sequelize.define('Task', {
    ...
  }, {
    classMethods: {
      associate: function(models) {
        Task.belongsTo(models.Context, {
          foreignKey: 'ContextId',
          onDelete: 'CASCADE'
        });
      }
    }
  });
  return Task;
};

다대다 관계

N:N 의 경우에 모델 association 설정

models/TableA.js

...
}, {
	classMethods: {
		associate: function(models) {
			TableA.belongsToMany(models.TableB, {
				 through: models.JoinTable,
				 foreignKey: 'TableAId',
				 onDelete: 'CASCADE'
		});
	}
} ...

model/TableB.js


...
}, {
	classMethods: {
		associate: function(models) {
			TableB.belongsToMany(models.TableA, {
				 through: models.JoinTable,
				 foreignKey: 'TableBId',
				 onDelete: 'CASCADE'
		});
	}
} ...

migrations / JoinTable.js

...

//primary key definition

	TableAId: {
       type: Sequelize.UUID,
       onDelete: 'CASCADE',
       references: {
            model: 'TableA',
            key: 'id'
       }
	},

	TableBId: {
       type: Sequelize.UUID,
       onDelete: 'CASCADE',
       references: {
            model: 'TableB',
            key: 'id'
       }
	},

...

마이그레이션에서 외래키 만들기

FK 가 참조하는 테이블에 대한 정보와 함께 Fk를 포함하는 마이그레이션에

FK 을 명시적으로 만들어야한다.

migrations/context.js


'use strict';

module.exports = {
  //function to run when (trying) to create the table (or make needed changes to it)
  up: function (queryInterface, Sequelize) {
    //define all columns in Contexts, including id, createdAt, and updatedAt as well as foreign keys (see UserId)
    return queryInterface.createTable('Contexts', {
      id: {
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4
      },
      name: {
        type: Sequelize.STRING
      },
      UserId: {
        type: Sequelize.UUID,
        onDelete: 'CASCADE',
        references: {
          model: 'Users',
          key: 'id'
        }
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  //function to run when reverting the changes to the table
  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('Contexts');
  }
};

migrations/task.js

'use strict';

module.exports = {
  //function to run when (trying) to create the table (or make needed changes to it)
  up: function (queryInterface, Sequelize) {
    //define all columns in Tasks, including id, createdAt, and updatedAt as well as foreign keys (see ContextId)
    return queryInterface.createTable('Tasks', {
      id: {
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUIDV4
      },
      name: {
        type: Sequelize.STRING,
        allowNull: false
      },
      description: {
        type: Sequelize.TEXT
      },
      done: {
        type: Sequelize.BOOLEAN
      },
      ContextId: {
        type: Sequelize.UUID,
        onDelete: 'CASCADE',
        references: {
          model: 'Contexts',
          key: 'id'
        }
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  //function to run when reverting the changes to the table
  down: function (queryInterface, Sequelize) {
    return queryInterface.dropTable('Tasks');
  }
};

해당 내용에 대해서 잘 정리된 블로그 글을 발견했다.

다만 2017에 작성된 글이므로, 코드 변경사항이 있는지는 확인을 해야한다. 그래도 개념파악을 하기 좋다.

참고

profile
기본에 충실하고 싶습니다. #Front-end-developer

0개의 댓글