쾌락코딩

sequelize belongsTo vs hasOne

|

Associations One-To-One

sequelize로 데이터베이스를 설계하다가, belongsTo와 hasOne의 관계가 헷갈렸다. Book과 Category 테이블이 있는데, Book은 무조건 Category를 하나 가져야만 했다. 따라서 Category는 Book에 속해있는 것이다. 그러나 따지고 보면 Book도 카테고리에 속해있다는 생각이 들었다. 그럼 결국 Book belongsTo Category인가? Book hasOne Category인가? 동시에 Category belongsTo Book을 해줘야 하나???

공식 문서 파헤치기

한번 확실히 하고 가는게 좋을 것 같아서 공식 문서를 정독했다. 따라서 이 글은 거의 공식 문서를 번역한 내용이 될 것 같다.

source vs target

공식 문서에 보면 source Model, target Model 이란 말이 나온다. User.hasOne(Project)라는 코드가 있을 때, User는 source Model이고, Project는 target Model 이다.

One-To-One 관계

1 대 1 관계는 서로 다른 두 개의 모델이 오직 하나의 foreign key로 연결 되어 있는 관계이다.

BelongsTo

belongsTo는 1 대 1 관계에서 필요한 foreign key가 source Model에 추가되는 관계(?함수?)이다. 간단한 예시로, 팀(Team)에 속해있는 선수(Player) 관계를 보자.

const Player = this.sequelize.define('player', {/* attributes */});
const Team  = this.sequelize.define('team', {/* attributes */});

Player.belongsTo(Team); // 이 경우 Player 모델에 Team모델을 참조하기 위한 TeamId라는 속성이 생긴다.

여기서 몇 가지 옵션을 줄 수 있다.

const User = this.sequelize.define('user', {/* attributes */})
const UserRole  = this.sequelize.define('userRole', {/* attributes */});

User.belongsTo(UserRole, {as: 'role'}); // userRoleId가 아닌 roleId 로 생성된다.

또한 foreignKey 옵션은 항상 기본 적으로 생성되는 foreign key 이름은 덮어 쓸 수 있다.

const User = this.sequelize.define('user', {/* attributes */})
const Company  = this.sequelize.define('company', {/* attributes */});

User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User

Target keys 옵션도 줄수가 있는데, 여기서 Target keys란 source 모델에서 foreign key가 가르킬 target model의 칼럼이다. 즉, target keys를 설정하면, 무조건 target Model의 primary key를 참조하는게 아니라 원하는 값을 참조 할 수 있다는 것.

const User = this.sequelize.define('user', {/* attributes */})
const Company  = this.sequelize.define('company', {/* attributes */});

User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User

추가적으로 중요한 점은, User는 create 함수와 getter, setter를 얻는 다는 것이다. 공식문서에는 이렇게 나와있다

In the API reference below, add the name of the association to the method, e.g. for User.belongsTo(Project) the getter will be user.getProject().

아래 hasOne에도 나오겠지만, 둘다 getter와 setter를 가진다.

HasOne

hasOne은, foreign key가 target Model에 생긴다. 공식 문서처럼 User와 Project로 설명하자면,

// One-way associations
Project.hasOne(User)

/*
  In this example hasOne will add an attribute projectId to the User model!
  Furthermore, Project.prototype will gain the methods getUser and setUser according
  to the first parameter passed to define. If you have underscore style
  enabled, the added attribute will be project_id instead of projectId.

  The foreign key will be placed on the users table.

  You can also define the foreign key, e.g. if you already have an existing
  database and want to work on it:
*/

이렇게 설정할 경우, Project.getUser()과 Project.setUser() 사용이 가능하다. 또 한 몇가지 옵션으로,

// Or let's define some self references
const Person = sequelize.define('person', { /* ... */})

Person.hasOne(Person, {as: 'Father'})
// 이 것은 Person 모델에 FatherId 속성을 추가한다.

// also possible:
Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'})
// this will add the attribute DadId to Person

// In both cases you will be able to do:
Person.setFather
Person.getFather

foreign key는 DadId로 생기지만, orm으로 사용할 때는 as : ‘Father’를 사용했으므로, Person.getFather으로 호출할 수 있다.

(추가) 생성된 foreign Key에 데이터 넣기

예를 들어, Book과 Category 테이블이 존재하고, Book 모델에 Category를 참조할 foreign key를 가지고 싶다고 하자. 그렇다면, 위에서 보았듯이 Book.belongsTo(Category)를 하자. Book 모델에 외래키로써 CategoryId가 생긴다. 따라서 Book모델은 Book.setCategory(카테고리 인스턴스)로 외부 모듈을 참조 할 수가 있게 된다. 굳이 Book 모델을 정의할 때 CategoryId를 선언할 필요도 없고, Book.create()에서 인수로 Category인스턴스의 id를 구해서 넘겨줄 필요가 없다는 것이다.

공식문서 링크

Comments