ORM(Object Relation Mapping)은 객체와 관계형 데이터베이스의 관계를 매핑해주는 도구이며, 수동으로 SQL 구문을 적을 필요성을 배제하기 위해 사용되고 있다. 또한 객체을 통해서 훨씬 빠르고 보다 읽기 쉬운 쿼리조작을 하는 것도 가능하다.
Sequelize는 Node.js용의 Promise 기반의 ORM이며, Synchronization、Association과 같은 기능을 가지고 있다. 현재 Sequelize은 PostgreSQL, MySQL, MariaDB, SQLite, MSSQL 등을 지원하고 있다.
Sequelize의 Association은 관계형 데이터베이스의 JOIN과 같이 관계성을 갖는 데이터 사이의 처리를 위해 사용된다 그 중 가장 비번하게 사용되는 1:n의 관계인 hasMany를 설명하고자 한다.
유저(user)가 복수의 댓글(comment)을 갖는 Association을 설정하고 사용해 보겠다. (* 기본적으로 HasOne의 경우도 hasMany의 부분을 hasOne으로 바꿔적는 것만으로 user가 하나의 comment을 갖는 Association으로 동작한다.
// ./models/users.js
module.exports = (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
username: {
type: DataTypes.STRING,
allowNull: false,
},
});
Users.associate = function (models) {
models.Users.hasMany(models.Comments, {
foreignKey: 'userId',
onDelete: 'cascade',
});
};
return Users;
};
// ./models/comments.js
module.exports = (sequelize, DataTypes) => {
const Comments = sequelize.define('Comments', {
comment: {
type: DataTypes.STRING,
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
allowNull: true,
},
});
Comments.associate = function (models) {
Comments.belongsTo(models.Users, {
onDelete: 'cascade',
foreignKey: {
allowNull: true,
},
});
};
return Comments;
};
// ./migrate/users.js
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
email: {
allowNull: false,
type: Sequelize.STRING,
},
password: {
allowNull: false,
type: Sequelize.STRING,
},
username: {
allowNull: false,
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
}),
down: (queryInterface) => queryInterface.dropTable('Users'),
};
// ./migrate/comments.js
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Comments', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
userId: {
allowNull: true,
type: Sequelize.INTEGER,
onDelete: 'cascade',
references: {
model: 'Users',
key: 'id',
},
},
comment: {
allowNull: false,
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
}),
down: (queryInterface) => queryInterface.dropTable('Comments'),
};
Sequelize cli를 이용하여 데이터베이스를 구축하는 과정에 크게 다음과 같은 두 가지의 문제가 발생하였다.
첫 번째, 마이그레이팅의 문제는 테이블 간의 관계설정에 따라 foreign key를 이용함에 따라 발생한 문제였다. 예를 들어 comment 테이블에는 userId라는 user테이블로 부터의 foreign key가 존재하는데 user 테이블이 마이그레이션 되기 전에 comment 테이블을 마이그레이션 했기 때문이다.
두 번째, 여러 모델에서 쿼리를 하여 얻은 데이터를 다른 테이블에 삽입할 때 null이 되는 문제는 model과 migrate에서 설정한 'AllowNull: false' 라는 옵션이 문제였다.
첫 번째 문제는 Sequelize-cli의 경우 migrate 폴더 내의 이름순으로 마이그레이팅이 되는 때문에 이름변경을 통해 순서를 조정하여 해결하였다.
두 번째 문제는 migrate를 담당하는 부분과 model을 정의하는 부분에서 AllowNull 옵션을 바꿔서 해결하였다.
userId: {
type: DataTypes.INTEGER,
allowNull: true, // false => true
}
AllowNull 옵션에 대한 의문
AllowNull : false일 때 왜 null이 되는 것인지?
여러 model의 메서드를 여러 번 실행하고 데이터베이스를 닫아주지 않았기 때문인지( *db.sequelize.close() ) 문제인건지?(추측)
안녕하세요.
좋은 글 남겨주셔서 감사합니다.
현재 expressjs 라이브러리를 활용해서 토이 프로젝트를 개발중인데 개발하는데 많은 도움이 될거 같습니다^^
혹시 질문 하나만 드려도 될까요?
개발하신 Stroll 서비스의 백엔드 소스 코드를 읽어보았는데, migrations 폴더에 있는 파일들은 무슨 용도 인가요?
그리고 migrations 폴더에 있는 파일들을 호출하는 곳을 못 찾겠는데 어디서 호출되어 실행되는 건가요?