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
๋ชจ๋ธ ์ต์
โข
โข
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
๋ณต์ฌ