sequelize

ORM

Object-Relational Mapping
๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ด€๊ณ„๋ฅผ ๋งคํ•‘ ํ•ด์ฃผ๋Š” ๋„๊ตฌ
๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ๋•Œ ๊ฐ์ฒด๋ฅผ ๋‹ค๋ฃจ๋“ฏ์ด ์ทจ๊ธ‰๊ฐ€๋Šฅ
์™œ ์‚ฌ์šฉํ• ๊นŒ?
query๊ฐ€ ์•„๋‹Œ ๋ฉ”์„œ๋“œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Œ
๊ฐ€๋…์„ฑ์ด ์ข‹์Œ
์ฟผ๋ฆฌ๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด ์„ฑ๋Šฅ์ด raw query์— ๋น„ํ•ด ๋Š๋ฆผ
ORM ๋น„๊ต

In Node.js

Sequelize

Node.js๊ธฐ๋ฐ˜์˜ ORM์œผ๋กœ Promise ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉ
Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server๋ฅผ Express์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์Œ

Sequelize CLI

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ํˆด๋กœ, CLI์—์„œ ๋ชจ๋ธ์„ ์ƒ์„ฑํ•ด์ฃผ๊ฑฐ๋‚˜, ์Šคํ‚ค๋งˆ ์ ์šฉ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์›€
๋™์ž‘๊ณผ์ •

์‹œ์ž‘ํ•˜๊ธฐ

mysql2, Sequelize, Sequelize CLI ์„ค์น˜ํ•˜๊ธฐ
npm install mysql2 sequelize npm install --save-dev sequelize-cli
JavaScript
๋ณต์‚ฌ
๋นˆ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ
npx sequelize-cli init
JavaScript
๋ณต์‚ฌ
์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋‚˜๋ฉด ํด๋”๊ฐ€ ์ƒ์„ฑ๋จ
โ€ข
config : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • ํŒŒ์ผ
โ€ข
migration : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋ณ€ํ™”ํ•˜๋Š” ๊ณผ์ •๋“ค์„ ์ถ”์ ํ•ด๋‚˜๊ฐ€๋Š” ์ •๋ณด๋กœ, ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜ํ•˜๊ฑฐ๋‚˜ ๋ณ€ํ™”๋ฅผ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ์Œ
โ€ข
models : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ฐ ํ…Œ์ด๋ธ”์˜ ์ •๋ณด๋ฅผ ์ •์˜ํ•˜๊ณ  ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ๋ชจ์Œ
โ€ข
seeders : ํ…Œ์ด๋ธ”์— ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์„ ๋•Œ ์‚ฌ์šฉ
์—ฐ๊ฒฐํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ •๋ณด ์ž…๋ ฅ
{ "development": { "username": "root", "password": null, "database": "database_dev", "host": "127.0.0.1", "port": 3306, // ๊ฐ’ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด 3306๋ฒˆ์ด default "dialect": "mysql" }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "port": 3306, // default "dialect": "mysql" }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "port": 3306, // default "dialect": "mysql" } }
JSON
๋ณต์‚ฌ
config/config.json
ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋“ฑ๋กํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค๋ฉด jsonํ˜•์‹์ด ์•„๋‹Œ js ํŒŒ์ผ ํ˜•์‹์œผ๋กœ ์ž‘์„ฑ ๊ฐ€๋Šฅ

๋ชจ๋ธ ์ •์˜ํ•˜๊ธฐ

๋ชจ๋ธ์„ ์ •์˜ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

์ง์ ‘ ์ •์˜ํ•˜๊ธฐ

models ํด๋” ์•ˆ์— ๋ชจ๋ธ์„ ์ •์˜ํ•  ํŒŒ์ผ ์ƒ์„ฑ ํ›„ sequelize.define()์„ ์ด์šฉํ•ด ๋ชจ๋ธ์„ ์ง์ ‘ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
ex)
module.exports = function(sequelize, DataTypes) { let user = sequelize.define('User', { id: { type: DataTypes.INTEGER, allowNull: false, }, name: { type: DataString.STRING } }, { timestamps: true }) }
JavaScript
๋ณต์‚ฌ
/models/user.js
๋ชจ๋ธ ์˜ต์…˜
โ€ข
type : ๋ฐ์ดํ„ฐ ํƒ€์ž… (ํƒ€์ž… ์ข…๋ฅ˜)
โ€ข
defaultValue : ๊ธฐ๋ณธ๊ฐ’
โ€ข
allowNull: Null๊ฐ’ ํ—ˆ์šฉ ์—ฌ๋ถ€

CLI๋กœ ์ •์˜ํ•˜๊ธฐ

npx sequelize-cli model:generate --name <name> --attributes <field1>:<datatype>,<field2>:<datatype> # attribute ์˜ต์…˜ ๋’ค์— ํ•„๋“œ์™€ ๋ฐ์ดํ„ฐํƒ€์ž…๋“ค์„ ๋„์–ด์“ฐ๊ธฐํ•˜๋ฉด ์˜ค๋ฅ˜๋‚จ. ๋„์–ด์“ฐ๊ธฐ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ''๋กœ ๋ฌถ์–ด์„œ ์‚ฌ์šฉ #example npm sequelize-cli model:generate --name User --attributes
Shell
๋ณต์‚ฌ
๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ž๋™์œผ๋กœ modelsํด๋” ์•ˆ์— ๋ชจ๋ธ ์ด๋ฆ„์œผ๋กœ ๋œ jsํŒŒ์ผ๊ณผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.
'use strict'; const { Model } = require('sequelize'); module.exports = (sequelize, DataTypes) => { class Users 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 } }; Users.init({ id: DataTypes.INTEGER, name: DataTypes.STRING }, { sequelize, modelName: 'user', }); return user; };
JavaScript
๋ณต์‚ฌ
/models/user.js
'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('users', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, ... }); }, down: async (queryInterface, Sequelize) => { await queryInterface.dropTable('~'); } };
JavaScript
๋ณต์‚ฌ
/migration/~~~~.js
โ€ข
up() : ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ
โ€ข
down() : ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ทจ์†Œํ•  ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ

Migration

๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ถ”์ 

Migration ์‹คํ–‰

์‹ค์ œ DB์— ๋ฐ˜์˜ํ•˜๊ธฐ
npx sequelize-cli db:migrate
JavaScript
๋ณต์‚ฌ

Migration ์‹คํ–‰ ์ทจ์†Œ

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆผ
# ๊ฐ€์žฅ ์ตœ๊ทผ์˜ migration ์‹คํ–‰ ์ทจ์†Œ npx sequelize-cli db:migrate:undo # ๋ชจ๋“  migration ์‹คํ–‰ ์ทจ์†Œ npx sequelize-cli db:migrate:undo:all # ํŠน์ • migration์œผ๋กœ ๋˜๋Œ๋ฆฌ๊ธฐ npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js
Shell
๋ณต์‚ฌ

Migration Skeleton ๋งŒ๋“ค์–ด ์Šคํ‚ค๋งˆ ์ˆ˜์ •ํ•˜๊ธฐ

npx sequelize-cli migration:generate --name add-column
Shell
๋ณต์‚ฌ
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { return queryInterface .addColumn( 'ChattingRooms', // name of Target model 'userId2', // name of the key we're adding { type: Sequelize.INTEGER, // setting foreign key relationship references: { model: 'Users', // name of Source model key: 'id', }, // setting when primary key is updated or deleted onUpdate: 'CASCADE', onDelete: 'CASCADE', } ) .then(() => queryInterface.addColumn( 'Users', // name of Target model 'img', // name of the key we're adding { type: Sequelize.STRING, defaultValue: 'https://placeimg.com/140/140/any', } ) ); }, down: (queryInterface, Sequelize) => { return queryInterface .removeColumn( 'ChattingRooms', // name of the Target model 'userId2' // key we want to remove ) .then(() => queryInterface.removeColumn( 'Users', // name of the Target model 'img' // key we want to remove ) ); }, };
JavaScript
๋ณต์‚ฌ

Seed

์ƒ์„ฑ๋œ ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ

Seed ์ƒ์„ฑ

npx sequelize-cli seed:generate --name <name>
JavaScript
๋ณต์‚ฌ
๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด seeders ํด๋” ์•ˆ์— ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.
์ด ํŒŒ์ผ์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ๊ณผ ๊ฐ™์€ ํฌ๋งท์œผ๋กœ ์‹œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด up, ์‹œ๋“œ๋ฅผ ์ทจ์†Œํ•˜๋ฉด down ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

Seed ์‹คํ–‰

ํŒŒ์ผ ์ž‘์„ฑ ํ›„ ์‹œ๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
npx sequelize-cli db:seed:all
JavaScript
๋ณต์‚ฌ

Seed ์‹คํ–‰ ์ทจ์†Œ

# ๊ฐ€์žฅ ์ตœ๊ทผ ์‹œ๋“œ ์ทจ์†Œ npx sequelize-cli db:seed:undo # ํŠน์ • ์‹œ๋“œ ์ทจ์†Œ npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data # ๋ชจ๋“  ์‹œ๋“œ ์ทจ์†Œ npx sequelize-cli db:seed:undo:all
Shell
๋ณต์‚ฌ

Model Querying

findOne
user.findOne({ where: { id: userId } })
JavaScript
๋ณต์‚ฌ
findAll
test.findAll({ attributes: ['id', 'name'] });
JavaScript
๋ณต์‚ฌ
findOrCreate
const [result, created] = await urlModel.findOrCreate({ where: { url๋กœ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์— ์ฐพ๊ณ  url: url }, defaults: { defaults ์˜ต์…˜์œผ๋กœ ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ƒˆ๋กœ์šด ์ธ์Šคํ„ฐ์Šค๋ฅผ ์ƒ์„ฑํ• ๋•Œ title column ์„ ์ƒ์„ฑ title: title } }); if(!created) { res.status(201).json(result) } //๊ณต์‹๋ฌธ์„œ์— ์žˆ๋Š” findOrCreate ์˜ˆ์ œ ์ฝ”๋“œ const [user, created] = await User.findOrCreate({ where: { username: 'sdepold' }, defaults: { job: 'Technical Lead JavaScript' } }); console.log(user.username); // 'sdepold' console.log(user.job); // This may or may not be 'Technical Lead JavaScript' console.log(created); // The boolean indicating whether this instance was just created if (created) { ๋ถ€์šธ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ถ€๋ถ„(์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ์—ฌ๋ถ€ ์ฒดํฌ) console.log(user.job); // This will certainly be 'Technical Lead JavaScript' }
Shell
๋ณต์‚ฌ

Association

๋ชจ๋ธ ์‚ฌ์ด์˜ ๊ด€๊ณ„
model์„ ํ†ตํ•ด association ์ˆ˜์ •
1 : 1
hasOne
BelongsTo
1 : N
hasMany
BelongsTo
N : M
BelongstoMany
// Option 1 Foo.hasOne(Bar, { foreignKey: 'myFooId' }); Bar.belongsTo(Foo); // Option 2 Foo.hasOne(Bar, { foreignKey: { name: 'myFooId' } }); Bar.belongsTo(Foo); // Option 3 Foo.hasOne(Bar); Bar.belongsTo(Foo, { foreignKey: 'myFooId' }); // Option 4 Foo.hasOne(Bar); Bar.belongsTo(Foo, { foreignKey: { name: 'myFooId' } });
JavaScript
๋ณต์‚ฌ

Transaction

Reference