관계 정의하기
사용자 한 명은 댓글을 여러 개 작성할 수 있습니다. 하지만 댓글 하나에 사용자(작성자)가 여러 명일 수는 없습니다. 이러한 관계를 일대다(一對多)(이하 1:N) 관계라고 합니다. 1:N 관계에서는 사용자가 1이고, 댓글이 N입니다.
다른 관계로 일대일, 다대다 관계가 있습니다. 일대일 관계로는 사용자와 사용자에 대한 정보 테이블을 예로 들 수 있습니다. 사용자 한 명은 자신의 정보를 담고 있는 테이블과만 관계가 있습니다. 정보 테이블도 한 사람만을 가리킵니다. 이러한 관계를 일대일(이후 1:1) 관계라고 합니다.
다대다 관계로는 게시글 테이블과 해시태그(#) 테이블 관계를 예로 들 수 있습니다. 한 게시글에는 해시태그가 여러 개 달릴 수 있고, 한 해시태그도 여러 게시글에 달릴 수 있습니다. 이러한 관계를 다대다(이후 N:M) 관계라고 합니다.
MySQL에서는 JOIN이라는 기능으로 여러 테이블 간의 관계를 파악해 결과를 도출합니다. 시퀄라이즈는 JOIN 기능도 알아서 구현합니다. 대신 테이블 간에 어떠한 관계가 있는지 시퀄라이즈에 알려야 합니다.
1:N
시퀄라이즈에서는 1:N 관계를 hasMany라는 메서드로 표현합니다. users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들도 같이 불러올 수 있습니다. 반대로 belongsTo 메서드도 있습니다. comments 테이블의 로우를 불러올 때 연결된 users 테이블의 로우를 가져옵니다.

모델 각각의 static associate 메서드에 넣습니다.
models/user.js
const Sequelize = require("sequelize");
module.exports = class User extends Sequelize.Model {
static init(sequelize) {
return super.init(
{
name: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
},
age: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
},
married: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
comment: {
type: Sequelize.TEXT,
allowNull: true,
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.NOW,
},
},
{
sequelize,
timestamps: false,
underscored: false,
modelName: "User",
tableName: "users",
paranoid: false,
charset: "utf8",
collate: "utf8_general_ci",
}
);
}
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: "commenter", sourceKey: "id" });
}
};
module.exports = User;
models/comment.js
const Sequlize = require("sequelize");
module.exports = class Comment extends Sequlize.Model {
static int(sequelize) {
return super.init(
{
comment: {
type: Sequlize.STRING(100),
allowNull: false,
},
created_at: {
type: Sequlize.DATE,
allowNull: true,
defaultValue: Sequlize.NOW,
},
},
{
sequelize,
timestamps: false,
modelName: "Comment",
tableName: "comments",
paranoid: false,
charset: "utf8mb4",
collate: "utf8mb4_general_ci",
}
);
}
static associate(db) {
db.Comment.belongsTo(db.User, { foriegnKey: "commenter", targetKey: "id" });
}
};
module.exports = Comment;
왜 db라는 매개변수를 사용하는지 궁금할 수 있습니다. ‘최상단에 const Comment = require('./comment') 식으로 불러오면 되지 않을까’라고 생각할 수 있지만, 이 경우 순환 참조 문제가 발생합니다. comment.js에서 user.js를 require하는데 user.js에서도 comment.js를 require하면 문제가 발생하기 때문입니다. 이와 같이 서로가 서로를 require하는 방식을 순환 참조라고 합니다. 이는 자바스크립트에서는 지양해야 하는 방식입니다. 따라서 index.js에서 각 모델을 불러와 db 매개변수로 넘기는 방식을 취한 것입니다.
어떤 모델에 hasMany를 쓰고, 어떤 모델에 belongsTo를 쓰는지 헷갈릴 것입니다. 다른 모델의 정보가 들어가는 테이블에는 belongsTo를 사용합니다. 예제에서는 commenter 컬럼이 추가되는 Comment 모델에 belongsTo를 사용하면 됩니다. 사용자는 한 명이고, 그에 속한 댓글은 여러 개이므로 댓글 로우에 사용자(commenter)가 누구인지 적어야 합니다.
시퀄라이즈는 위에서 정의한 대로 모델 간 관계를 파악해서 Comment 모델에 foreignKey(외래 키)인 commenter 컬럼을 추가합니다. Comment 모델의 외래 키 컬럼은 commenter이고, User 모델의 id 컬럼을 가리키고 있습니다.

hasMany 메서드에서는 sourceKey 속성에 id를 넣고, belongsTo 메서드에서는 targetKey 속성에 id를 넣습니다. sourceKey의 id와 targetKey의 id 모두 User 모델의 id입니다. hasMany에서는 sourceKey를 쓰고, belongsTo에서는 targetKey를 쓴다고 생각하면 됩니다.
foreignKey를 따로 지정하지 않는다면, 이름이 ‘모델명+기본 키’인 컬럼이 모델에 생성됩니다. 예를 들어 commenter를 foreignKey로 직접 넣어주지 않았다면 user(모델명)+기본 키(id)가 합쳐진 UserId가 foreignKey로 생성됩니다.
npm start 명령어로 서버를 시작하고 나서 콘솔을 보면 다음과 같은 메시지가 나옵니다. 시퀄라이즈가 스스로 실행하는 SQL문입니다.
$ npm start
Executing (default): CREATE TABLE IF NOT EXISTS `users` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL UNIQUE, `age` INTEGER UNSIGNED NOT NULL, `married` TINYINT(1) NOT NULL, `comment` TEXT, `created_at` DATETIME NOT NULL DEFAULT now(), PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `users` FROM `nodejs`
Executing (default): CREATE TABLE IF NOT EXISTS `comments` (`id` INTEGER NOT NULL auto_increment , `comment` VARCHAR(100) NOT NULL, `created_at` DATETIME DEFAULT now(), `commenter` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`commenter`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `comments` FROM `nodejs`
시퀄라이즈는 워크벤치가 테이블을 만들 때 실행했던 구문과 비슷한 SQL문을 만듭니다(7.4.2절 참조). CREATE TABLE 뒤에 IF NOT EXISTS라고 되어 있는데, 이 부분은 테이블이 존재하지 않을 경우에 실행된다는 뜻입니다. 이미 워크벤치 또는 콘솔로 테이블을 만들어뒀으므로 구문은 실행되지 않습니다. 대신 실수로 테이블을 삭제했을 때는 위의 구문으로 인해 다시 테이블이 생성됩니다.
1:1
1:1 관계에서는 hasMany 메서드 대신 hasOne 메서드를 사용합니다. 사용자가 정보를 담고 있는 가상의 Info 모델이 있다고 하면 다음과 같이 표현할 수 있습니다.
db.User.hasOne(db.Info, { foreignKey: 'UserId', sourceKey: 'id' });
db.Info.belongsTo(db.User, { foreignKey: 'UserId', targetKey: 'id' });
1:1관계라고 해도 belongsTo와 hasOne이 반대면 안 됩니다. belongsTo를 사용하는 Info 모델에 UserId 컬럼이 추가되기 떄문입니다.

N:M

시퀄라이즈에는 N:M 관계를 표현하기 위한 belongsToMany 메서드가 있습니다. 게시글 정보를 담고 있는 가상의 Post 모델과 해시태그 정보를 담고 있는 가상의 Hashtag 모델이 있다고 하면 다음과 같이 표현할 수 있습니다.
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
양쪽 모델에 모두 belongsToMany 메서드를 사용하며, N:M 관계 특성상 새로운 모델이 생성됩니다(그림 7-59). through 속성에 그 이름을 적으면 됩니다. 새로 생성된 PostHashtag 모델에는 게시글과 해시태그의 아이디가 저장됩니다. 9장의 예제에서 N:M 관계를 사용하는 것을 볼 수 있습니다.

자동으로 만들어진 모델들도 다음과 같이 접근할 수 있습니다.
db.sequelize.models.PostHashtag
'프로그래밍 언어 > NODE JS' 카테고리의 다른 글
| 쿼리 수행하기 (0) | 2025.09.08 |
|---|---|
| 쿼리 알아보기 (0) | 2025.09.05 |
| 모델 정의하기 (1) | 2025.08.27 |
| MySQL 연결하기 (0) | 2025.08.24 |
| 시퀄라이즈 사용하기 (0) | 2025.08.21 |