Sequelize(8)_코드로 외래키 설정

차분한열정·2021년 4월 7일
0

Sequelize 튜토리얼

목록 보기
8/14

자, 이제 드디어 테이블 간의 관계를 설정할 것이다. 지금 존재하는 테이블들을 간단히 살펴보자.

  • Students 테이블
  • ScholarshipAccounts 테이블
  • Courses 테이블
  • Professors 테이블

테이블 간의 관계는 크게 1:1, 1:M(1대다), M:N(다대다) 이렇게 3가지로 나누어볼 수 있다. 지금 이 테이블들 사이에는 이 관계들이 모두 존재한다.

(1) 일단 학생 한 명 당 하나의 장학금 통장만을 가질 것이므로 Students - ScholarshipAccounts 간의 관계는 1:1
(2) 수업 하나는 교수님 한 명이 담당해서 가르치지만, 교수님 한 명은 여러 개의 수업을 가르칠 수 있기 때문에 Professors - Courses 간의 관계는 1:M
(3) 수업 하나는 여러 명의 학생들이 듣고 한 명의 학생은 여러 수업을 들을 수 있기 때문에 Courses - Students 간의 관계는 M:N

이라고 할 수 있다. 이 각각의 관계를 코드로 어떻게 설정할 수 있는지, 코드로 설정하면 실제 데이터베이스에 무엇이 반영되는지 살펴보겠다.

일단 app.js 파일을 이렇게 수정하자.

// app.js
const db = require('./models/index.js');

const sequelize = db.sequelize;

(async () => {
  await sequelize.sync({ force: true });
})();

(1) 1:1 관계 설정

그리고 Student.js 파일에서 associate 함수 안을 이렇게 채우자.

// Student.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Student extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      Student.hasOne(models.ScholarshipAccount); // 
    }
  }
  Student.init(
    {
      registrationNum: DataTypes.STRING,
      name: DataTypes.STRING,
      age: DataTypes.INTEGER,
    },
    {
      sequelize,
      modelName: 'Student',
    }
  );
  return Student;
};

이 부분이 아주 중요하다. 그리고 ScholarshipAccount.js 파일은 이렇게 수정해주어야 한다.

// ScholarshipAccount.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class ScholarshipAccount extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      ScholarshipAccount.belongsTo(models.Student); //
    }
  }
  ScholarshipAccount.init(
    {
      accountNum: DataTypes.STRING,
      validDate: DataTypes.DATE,
      balance: DataTypes.INTEGER,
    },
    {
      sequelize,
      modelName: 'ScholarshipAccount',
    }
  );
  return ScholarshipAccount;
};

지금 두 파일에서 바뀐 부분은

// Student.js
~~
Student.hasOne(models.ScholarshipAccount);
~~
// Scholarship.js
~~
ScholarshipAccount.belongsTo(models.Student);
~~

이 부분이다. hasOne 메소드와 belongsTo 메소드를 볼 수 있는데 1:1 관계는 바로 이런 식으로 설정한다. 이때 foreign key를 갖고 있는 테이블에서 belongsTo를 호출해줘야 한다. 이 associate 메소드 내부의 코드들은 models 디렉토리의 index.js 파일에 있는 코드 중

~~
Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});
~~

이 부분이 실행될 때 실행된다. 자, 전체 코드를 실행해보자.

node app.js 

를 실행하면

ScholarshipAccounts 테이블에 자동으로 StudentId가 생긴 것을 확인할 수 있다. 정말로 foreign key로 설정된 것이 맞는지 확인해보면

이렇게 foreign key로 잘 설정되어 있는 것을 알 수 있다.

(2) 1:M 관계 설정

이번엔 Professors 테이블과 Courses 테이블에 1:M 관계를 설정해보자. 방금 전과 똑같이 각 파일의 associate 함수 부분을 바꾸면 된다. 이렇게 코드를 바꾸자.

// professor.js
~~
Professor.hasMany(models.Course);
~~
// course.js
~~
Course.belongsTo(models.Professor);
~~

그리고 코드를 실행하면

node app.js

이렇게 ProfessorId라고 하는 foreign key가 자동으로 생성된 것을 알 수 있다.

(3) M:N 관계 설정

마지막으로 Students 테이블과 Courses 테이블에 M:N 관계를 설정해보겠다. 주의할 점은 앞서 살펴본 1:1, 1:M 관계와 달리 M:N 관계는 단지 두 테이블 중 한 쪽 테이블에 foreign key를 두는 방법으로 관계를 설정할 수 없다. 대신 둘의 관계를 담고 있는 새로운 별도 테이블(보통 junction table이라고 한다)을 만들고 junction table의 두 컬럼이 각각 Students 테이블을 참조하는 foreign key, Courses 테이블을 참조하는 foreign key를 갖는다. 한번 해보겠다.

// Student.js
Student.hasOne(models.ScholarshipAccount);
Student.belongsToMany(models.Course, { through: 'Grade' });
// Course.js
Course.belongsTo(models.Professor);
Course.belongsToMany(models.Student, { through: 'Grade' });

코드를 실행하면

이렇게 Grade라는 테이블이 자동으로 생기고, CourseId, StudentId라고 하는 foreign key가 자동으로 생성되어 있는 것을 알 수 있다. (createdAt, updatedAt 컬럼은 이전에 배운 것처럼 테이블이 생성될 때 timestamps 옵션의 기본값이 true이기 때문에 생긴 것 뿐이다)

그런데 M:N 관계를 설정하는 것은 어떤 의미있는 정보를 추가로 함께 저장하고 싶기 때문이다. 우리는 각 학생이 각 수업에 대해 맞은 점수를 저장해보자. 이렇게 자동으로 생기게 하지 말고, 이미 존재하는 테이블을 junction table로 활용하는 것도 가능하다. 일단 Grade라는 모델을 직접 만들어보자. 일단 다음 명령을 실행하면

npx sequelize model:generate --name Grade --attributes score:integer
// Grade.js
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Grade extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  Grade.init({
    score: DataTypes.INTEGER
  }, {
    sequelize,
    modelName: 'Grade',
  });
  return Grade;
};

Grade 모델이 잘 생성됩니다. 그리고 이제 각 코드에서 문자열 'Grade' 대신 실제로 이미 존재하는 모델을 나타내기 위해 model.Grade를 넣어주겠습니다.

// Student.js
Student.hasOne(models.ScholarshipAccount);
Student.belongsToMany(models.Course, { through: models.Grade });
// Course.js
Course.belongsTo(models.Professor);
Course.belongsToMany(models.Student, { through: models.Grade });

실행하면(혹시 문제가 생기면 Workbench에서 직접 테이블들을 모두 삭제하고 실행하세요)

score 컬럼이 추가된 Grades 테이블을 볼 수 있습니다.

자, 이때까지 1:1, 1:M, M:N 관계에 대해서 배워보았는데요. 각 경우에 어떤 메소드 쌍(pair)을 사용해야 하는지 잘 이해해둡시다.

참고로 관계를 설정할 때 ON DELETE, ON UPDATE 옵션을 설정하는 것도 가능한데요. 예를 들어,

Student.hasOne(models.ScholarshipAccount, {
  onDelete: 'RESTRICT',
  onUpdate: 'RESTRICT'
});
ScholarshipAccount.belongsTo(Student);

이런 식으로 설정할 수 있습니다. 줄 수 있는 옵션에는 RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL이 있으니 각자가 원하는 것을 선택하면 됩니다. 주지 않으면 각 메소드의 기본 옵션들이 적용됩니다.

profile
성장의 기쁨

0개의 댓글