-
Notifications
You must be signed in to change notification settings - Fork 4
Sequelize ORM by 윤현우
작성자 : J131 윤현우
최종수정일 : 2020.10.28(수)
첨언 : 블로그에 쓰려고 정리하다가 시간이 부족해서 아직 완성하지 못한 글입니다. 조원분들께 1이라도 도움이 될까 하여 업로드합니다.
- mySQL 기반의 ORM
- 비동기식 API이므로
then
,catch
,finally
,async
,await
모두 이용 가능합니다.
- 처음부터 완성된 소스코드를 기재한 후 설명하기보다는 윤곽부터 잡아가며 흐름을 설명하는 글입니다. 시간이 부족하신 분들은 가장 하단에 최종 소스코드를 참고해주시면 될 것 같습니다.
- 하지만 그 최종 소스코드 또한 제 소스코드이지, 보시는 분들이 필요한 내용이 없을 수도 있습니다. 되도록 어떤 코드가 왜 들어가게 되었는지 흐름을 파악한 후, 자기 것으로 만드시길 권장드립니다.
-
server
- dev 환경 : Ubuntu 20.04 (WSL2)
- production 환경 : Ubuntu 16.04
-
DB
- dev 환경 : MySQL 8.0.21
- production 환경 : MySQL
-
Node
- dev 환경, production 환경 : v12.18.4
-
Package
- express : v4.17.1
- sequelize : v6.3.5
- mysql2 : v2.2.5
-
node express 기반의 웹 서비스를 만든다고 가정하고, 최상단 디렉토리에는 app.js가 위치합니다.
-
같은 디렉토리 레벨에는 models라는 디렉토리가 존재하며, 해당 디렉토리 내부에서 model과 관련된 전체적인 작업이 이루어집니다.
-
가계부를 만든다고 생각하고 테이블을 '사용자(user)', '결제수단(payment)', '내역(history)', '내역 카카테고리(category)'로 두었습니다. N:M 관계로 인해 파생되는 테이블은 제외했습니다. 이 내용을 인지하시고 설명을 보면 더욱 이해가 잘 되실 것 같습니다.
npm install sequelize
npm install mysql2
models라는 디렉토리 내부에 index.js 파일을 생성한 후 아래와 같이 기재해줍니다.
디렉토리명이나 파일명은 당연히 달라도 되지만, 컨벤션에 따르는 것을 권장합니다.
models
└── index.js
// index.js
const Sequelize = require('sequelize');
const sequelize = new Sequelize(
'database', // 데이터베이스 이름
'username', // 유저명
'password', // 비밀번호
{
host: 'host', // 데이터베이스 호스트. 기본값은 'localhost'
dialect: 'mysql' // 사용할 데이터베이스 종류
}
);
// 나의 index.js
const Sequelize = require("sequelize");
const config = require("../config/db.config");
const sequelize = new Sequelize(
config.database,
config.username,
config.password,
{
host: config.host,
port: config.port,
dialect: "mysql",
}
);
-
저는 따로 config를 설정해서 개발환경에 따라 연동되는 DB가 동적으로 변경되게끔 작성했습니다.
-
host는 default값이 localhost이므로 로컬 내에서만 실행하고 싶다면 따로 option 내에 지정해주지 않아도 됩니다.
-
option값 설명 : https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor
- 주의할 점
- Sequelize는 라이브러리 자체를 지칭합니다.
- sequelize는 하나의 데이터베이스에 대한 연결을 나타내는 Sequelize의 인스턴스입니다.
- 물론 변수명을 다르게 지을 수 있지만, 대부분의 documentation과 참고 자료에서 해당 convention을 유지합니다.
sequelize
.authenticate()
.then(function(err) {
console.log('Connection has been established successfully.');
})
.catch(function (err) {
console.log('Unable to connect to the database:', err);
});
Sequelize는 기본적으로 연결을 유지한 채 모든 쿼리에 대해 동일한 연결을 사용합니다. 만약 연결을 종료해야 하는 경우 sequelize.close()
를 사용하시면 됩니다. 비동기식으로 Promise를 return하게 되는데, 사실 그럴 일은 없으므로 패스하겠습니다.
index.js
내부에서 모델을 생성해도 되지만 모듈을 분리하는 것이 여러모로 좋습니다. 따라서, 동일한 디렉토리 내에 각 테이블 별로 js파일을 만들어줍니다.
models
├── category.js
├── history.js
├── index.js
├── payment.js
└── user.js
모델을 정의하는 방법은 두 가지가 있는데, 그중에서 sequelize.define
를 이용하고자 합니다.
sequelize.define(modelName, attributes, options)
//user.js
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define(
"user",
{
uid: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: "users",
underscored: true,
}
);
return user;
};
자바스크립트 내의 user
라는 객체에 sequelize.define
메서드를 이용하여 실제 데이터베이스 내 테이블과 매핑하는 작업을 하는 과정입니다. 자세히 풀어써본다면, 객체 user
에 "user"
라는 모델을 정의할 것인데, 그 모델의 속성값에 대한 내용은 두 번째 인자에 있는 object에 담겨있다는 것입니다. 또한 추가적으로 option까지 줬는데, option은 다양하기 때문에 여기를 참고해주시면 될 것 같습니다.
또한, 모듈을 분리했기 때문에 module.exports
로 감싸줘야 합니다. 관계까지 정의하고 나서 각 모델을 어떤 식으로 활용하는지 안내할 예정인데, index.js
에서 각 모델을 활용하기 위해서는 module.exports
를 해줘야 합니다.
-
object형식의 key-value 구조로 구성되어 있습니다. key는 "user" 모델에 만들고자 하는 column을 의미하고, value은 해당 column과 관련한 옵션을 의미합니다.
-
value 안에는 다양한 옵션들이 존재하는데 공부하면서 자주 썼던 것들에 대해 간략히 기재하겠습니다.
- type : 데이터 타입 (자세한 설명은 여기를 참고해주세요)
- allowNull : null을 허용할 것인지에 대한 옵션입니다. 기본값은
true
입니다. - primaryKey : 기본키로 설정한 것인지에 대한 옵션입니다. 기본값은
false
입니다. - autoIncrement : mySQL의 AUTO_INCREMENT와 동일한 것으로, 자동으로 증가하는 INTEGER로 설정한 것인지에 대한 옵션입니다. 기본값은
false
입니다.
-
자세한 내용 : 여기를 들어가서
Ctrl+F
를 눌러 검색창을 키신 후 'public static init' 을 입력하시면 하단에 테이블이 보입니다. 테이블 내에 존재하는attributes.column.
로 시작하는 파라미터들이 전부 column 속성에 대한 옵션입니다.
- 위 소스코드에서 저는 두 개의 옵션을 부여했는데, 그에 대한 설명과 함께
timestamps
라는 옵션에 대해서도 간략히 기재하겠습니다.- tableName : 직접 테이블명을 설정하는 옵션입니다. 따로 설정해주지 않는다면 Sequelize 내부적으로 테이블명을 설정하게 되는데, 모델명으로부터 정해진 규칙에 의해 변형을 일으킨 후 테이블명을 설정해줍니다. 자세한 내용은 여기를 참고해주시면 됩니다.
-
underscored : 테이블 내 컬럼의 네이밍 컨벤션을 스네이크 케이스로 변경해주는 설정입니다. 기본값은
false
입니다. 데이터베이스 네이밍 컨벤션에 따르면 테이블의 이름은 스네이크 케이스가 옳다고 판단되어true
로 설정해줬습니다. -
timestamps : 기본적으로 Sequelize는 모든 모델에 자동으로
createdAt
과updateAt
컬럼을 추가합니다. 두 컬럼의 데이터 타입은DataTypes.DATE
이며,createdAt
컬럼에는 레코드가 만들어진 시점이 기록되고,updateAt
컬럼에는 레코드가 수정된 시점이 기록됩니다. 기본값은true
이며 불필요하다고 여겨지신다면timestamps : false
옵션을 통해 제거해줄 수도 있습니다. 공식문서를 참고하면 더욱 다양한 활용을 할 수 있으니 필요하시다면 참고바랍니다.
- 자세한 내용 : 여기를 들어가서
Ctrl+F
를 눌러 검색창을 키신 후 'public static init' 을 입력하시면 하단에 테이블이 보입니다. 테이블 내에 존재하는options.
로 시작하는 파라미터들이 전부 column 옵션에 관한 내용입니다.
sequelize에서는 아래와 같은 관계를 지원해줍니다.
- One-To-One (1:1)
- One-To-Many (1:N)
- Many-To-Many (N:M)
위 관계를 정의하기 위해서는 sequelize에서 지원하는 관계 타입 HasOne
, BelongsTo
, HasMany
, BelongsToMany
를 사용하면 됩니다. 네 타입 모두 비슷한 방식으로 사용되므로, 사용 방법을 한 번만 제대로 익혀두시면 금방 활용할 수 있습니다.
sequelize는 Many-To-One은 지원하지 않고, One-To-Many만 지원하고 있습니다. One-To-Many에서는 단방향 매핑을 하는 것보다 양방향 매핑을 하는 것이 관리 측면이나 성능 측면에서 더 좋습니다. sequelize에서도 One-To-Many의 양방향 매핑을 권장하고 있습니다.
본격적으로 관계를 정의하기 전에 기존에 생성한 user
모델과 관계를 맺을 모델 하나를 추가로 생산해봅시다.
// history.js
module.exports = (sequelize, DataTypes) => {
const history = sequelize.define(
"history",
{
hid: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
price: {
type: DataTypes.INTEGER,
allowNull: false,
},
content: {
type: DataTypes.STRING,
},
},
{
tableName: "history",
underscored: true,
}
);
return history;
};
한 명의 사용자는 다수의 결제 내역을 가지고 있을 수 있으며, 하나의 결제 내역은 한 명의 사용자와 관계를 맺을 수 있습니다. 따라서 user테이블과 history테이블은 One-To-Many (1:N)
관계를 가진다고 할 수 있습니다.
이때 관계를 정의하는 것은 다음과 같습니다.
user.hasMany(history);
history.belongsTo(user);
또한, 관계를 정의할 때 옵션을 추가할 수 있는데 외래키와 on delete, on update를 설정할 수 있습니다. 기본적으로 One-To-One 관계와 One-To-Many 관계의 제약조건은 ON DELETE SET NULL
, ON UPDATE CASCADE
입니다. 저는 다음과 같이 설정했습니다.
user.hasMany(history, {
foreignKey: {name: }
});
history.belongsTo(user, {
});
본격적으로 관계를 정의하기 전에 기존에 생성한 user
모델과 관계를 맺을 모델 하나를 추가로 생산해봅시다.
// payment.js
module.exports = (sequelize, DataTypes) => {
const payment = sequelize.define(
"payment",
{
paymentName: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
},
},
{
tableName: "payment",
underscored: true,
}
);
return payment;
};
한 명의 사용자는 다양한 결제 수단을 가지고 있을 수 있으며, 하나의 결제수단은 다양한 사용자에게 사용되어질 수 있습니다. 따라서 user테이블과 payment테이블은 Many-To-Many (N:M)
관계를 가진다고 할 수 있습니다.