๊ธฐ์ดˆ(๋ชจ๋ธ, ๊ด€๊ณ„์ •์˜ & ์ฟผ๋ฆฌ)

1. Sequelize ์…‹์—…

  1. npm i express sequelize sequelize-cli mysql2
  1. npx sequelize init : config, models, migrations, seeders ํด๋” ์ƒ์„ฑ(sequelize-cli๊ฐ€ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์คŒ)
  1. models/index.js ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •
    1. const Sequelize = require('sequelize'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize(config.database, config.username, config.password, config); db.sequelize = sequelize; module.exports = db;
      { "development": { "username": "root", "password": "1234", "database": "nodejs", "host": "127.0.0.1", "dialect": "mysql" }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "mysql" }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" } }
      config.json
  1. app.js์—์„œ sequelize์ด์šฉํ•˜์—ฌ db ์—ฐ๊ฒฐ
    1. const { sequelize } = require('./models'); sequelize.sync({force: false }) .then(() => { console.log('๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ฑ๊ณต'); }) .catch((err) => { console.error(err); });

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

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) {} };
User ๋ชจ๋ธ ์ •์˜
  • User ๋ชจ๋ธ๋กœ ๋งŒ๋“ค๊ณ  exportsํ•จ
  • Sequelize.Model์„ ํ™•์žฅํ•œ ํด๋ž˜์Šค๋กœ ์„ ์–ธ
  • static init ๋ฉ”์„œ๋“œ, static associate ๋ฉ”์„œ๋“œ๋กœ ๊ตฌ์„ฑ๋จ
    • static init ๋ฉ”์„œ๋“œ : ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์„ค์ •
      • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜ : ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์„ค์ •
        • ํƒ€์ž… ๋น„๊ต
          MySQL
          ์‹œํ€„๋ผ์ด์ฆˆ
          ใ…ค
          VARCHAR(100)
          STRING(100)
          ใ…ค
          INT
          INTEGER
          ใ…ค
          TINYINT
          BOOLEAN
          ใ…ค
          DATETIME
          DATE
          ใ…ค
          INT UNSIGNED
          INTEGER.UNSIGNED
          ใ…ค
          NOT NULL
          allowNull: false
          ใ…ค
          UNIQUE
          unique: true
          ใ…ค
          DEFAULT now()
          defaultValue: Sequelize.NOW
      • ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜ : ํ…Œ์ด๋ธ” ์ž์ฒด์— ๋Œ€ํ•œ ์„ค์ •
        • timestamps : true โ‡’ createdAt๊ณผ updatedAt ์ปฌ๋Ÿผ ์ž๋™ ์ถ”๊ฐ€
        • underscored: ํ…Œ์ด๋ธ”๋ช…, ์ปฌ๋Ÿผ๋ช… ๊ธฐ๋ณธ์€ camel case. ์ด๋ฅผ ์Šค๋„ค์ดํฌ ์ผ€์ด์Šค๋กœ ๋ฐ”๊พธ์–ด์คŒ. true๋ฉด
        • paranoid: true๋กœ ์„ค์ •ํ•˜๋ฉด deletedAt ์ด๋ผ๋Š” ์ปฌ๋Ÿผ์ด ์ƒ๊น€. ๋กœ์šฐ๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์™„์ „ํžˆ ์ง€์›Œ์ง€์ง€ ์•Š๊ณ  deletedAt์— ์ง€์šด ์‹œ๊ฐ์ด ๊ธฐ๋ก๋จ
        • charset, collate : utf8, utf8_general_ci๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๊ธ€ ์ž…๋ ฅ๋จ
      • ์•Œ์•„์„œ id๋ฅผ ๊ธฐ๋ณธ ํ‚ค๋กœ ์—ฐ๊ฒฐํ•˜๋ฏ€๋กœ id ์ปฌ๋Ÿผ์€ ์ ์–ด์ค„ ํ•„์š”๊ฐ€ ์—†์Œ
    • associate : ๋‹ค๋ฅธ ๋ชจ๋ธ๊ณผ์˜ ๊ด€๊ณ„

2-1. index.js์™€ ๋ชจ๋ธ ์—ฐ๊ฒฐ

const Sequelize = require('sequelize'); const User = require('./user'); const Comment = require('./comment'); const env = process.env.NODE_ENV || 'development'; const config = require('../config/config')[env]; const db = {}; const sequelize = new Sequelize(config.database, config.username, config.password, config); db.sequelize = sequelize; db.User = User; db.Comment = Comment; User.init(sequelize); Comment.init(sequelize); User.associate(db); Comment.associate(db); module.exports = db;

3. ๊ด€๊ณ„ ์ •์˜

// User : Comment = 1 : N ๊ด€๊ณ„ static associate(db) { db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' }); } static associate(db) { db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id'}); }
  • foreignKey๊ฐ€ ์ƒ๊ธฐ๋Š” ํ…Œ์ด๋ธ”์—์„œ belongsTo๋ฅผ ํ˜ธ์ถœ โ‡’ ์ฆ‰ Comment ํ…Œ์ด๋ธ”์— commenter foreignKey ์ถ”๊ฐ€๋จ
  • sourceKey์˜ id์™€ targetKey์˜ id ๋ชจ๋‘ User ๋ชจ๋ธ์˜ id ์ž„

1:1 ๊ด€๊ณ„

db.User.hasOne(db.Info, { foreignKey: 'UserId', sourceKey: 'id' }); db.Info.belongsTo(db.User, { foreignKey: 'UserId', targetKey: 'id' });
  • belongsTo๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Info ๋ชจ๋ธ์— UserId ์ปฌ๋Ÿผ์ด ์ถ”๊ฐ€๋จ

N:M ๊ด€๊ณ„

db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
  • N : M ๊ด€๊ณ„ ํŠน์„ฑ์ƒ ์ƒˆ๋กœ์šด ๋ชจ๋ธ์ด ์ƒ์„ฑ๋จ. through ์†์„ฑ์— ์ƒˆ๋กœ์šด ๋ชจ๋ธ์˜ ์ด๋ฆ„์ด ๋ช…์‹œ๋จ
  • ์ƒˆ๋กœ ์ƒ์„ฑ๋œ PostHashtag ๋ชจ๋ธ์— ๊ฒŒ์‹œ๊ธ€๊ณผ ํ•ด์‹œํƒœ๊ทธ์˜ ์•„์ด๋””๊ฐ€ ์ €์žฅ๋จ
  • ์ฆ‰ 1: N, M : 1 ๋กœ ํ‘ผ๋‹ค๋Š” ์˜๋ฏธ
 

4. ์ฟผ๋ฆฌ

CRUD

/* db.sequelize = sequelize; db.User = User; db.Comment = Comment; User.init(sequelize); Comment.init(sequelize); User.associate(db); Comment.associate(db); module.exports = db; */ const { User } = require('../models'); // Create User.create({ name: 'zero', age: 24, married: false, comment: '์ž๊ธฐ์†Œ๊ฐœ1', }); // Read User.findAll({}); User.findOne({}); User.findAll({ attributes: ['name', 'married'], }); // select name, married from nodejs.users; const { Op } = require('sequelize'); const { User } = require('../models'); User.findAll({ attributes: ['name', 'age'], where: { married: true, // Mysql ์—์„œ undefined ์ง€์›ํ•˜์ง€ ์•Š์œผ๋‹ˆ, ๋นˆ ๊ฐ’๋„ฃ์œผ๋ ค๋ฉด null๋กœ ๋„ฃ์–ด์•ผ ํ•จ age: { [Op.gt]: 30 }, }, }); User.findAll({ attributes: ['id', 'name'], where: { [Op.or]: [{ married: false }, { age: {[Op.gt]: 30} }], }, }); User.findAll({ attributes: ['id', 'name'], order: ['age', 'DESC'], limit: 1, offset: 1, }); // Update User.update({ comment: '๋ฐ”๊ฟ€ ๋‚ด์šฉ', }, { where: { id : 2}, }); // Delete User.destroy({ where: { id : 2 }, });

Select

  • attributes๋Š” nested array๋ฅผ ํ†ตํ•ด renaming์ด ๊ฐ€๋Šฅํ•จ
    • Model.findAll({ attributes: ['foo', ['bar', 'baz'], 'qux'] });
  • sequelize.fn ์„ ์ด์šฉํ•ด์„œ aggregation ๋„ ๊ฐ€๋Šฅ
    • User.findAll({ attributes: [ 'workspaceId', 'workspaceZoneId', [sequelize.fn('COUNT', 'workspaceId'), 'workspaceCount'], [sequelize.fn('COUNT', 'workspaceZoneId'), 'workspaceZoneCount'], ], group: ['workspaceId', 'workspaceZoneId'], where: { organizationId: organizationId, connected: true, workspaceId: Array.from(workspaceMap.keys()) }, raw: true, // Return raw result. See sequelize.query for more information. }); // according SQL SELECT `workspace_id` AS `workspaceId`, `workspace_zone_id` AS `workspaceZoneId`, COUNT('workspaceId') AS `workspaceCount`, COUNT('workspaceZoneId') AS `workspaceZoneCount` FROM `users` AS `User` WHERE `User`.`connected` = true AND `User`.`organization_id` = 1 AND `User`.`workspace_id` IN (1, 2, 3, 4, 5) GROUP BY `workspaceId`, `workspaceZoneId`;
      sequelize.fn ์„ ์ด์šฉํ•œ aggregation
      raw ์˜ต์…˜ [์ฐธ๊ณ ]
      raw ์˜ต์…˜์ด true ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜๋˜๊ฒŒ ๋˜๊ณ 
      raw : false โ†’ Model ๊ฐ์ฒด๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, get() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœํ•ด์•ผ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•จ
      notion image
      notion image

JOIN

const user = await User.findOne({ include : [{ model : Comment, }] }); // ๋˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ const user = await User.findOne({}); const comments = await user.getComments(); console.log(comments);
  • ์–ด๋–ค ๋ชจ๋ธ๊ณผ ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ include ๋ฐฐ์—ด์— ๋„ฃ์–ด์ฃผ๋ฉด ๋จ

Raw SQL

const [result, metadata] = await sequelize.query('SELECT * from comments'); console.log(result);

Transaction

[ Sequelize ] Transaction
  • Sequelize.transaction() ์œผ๋กœ transaction ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ
  • Model์˜ ๋ฉ”์„œ๋“œ์— options.transaction ์œผ๋กœ ํ•ด๋‹น transaction์„ ๋ช…์‹œํ•ด์„œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฟผ๋ฆฌ ์‹คํ–‰๋˜๋„๋ก ์„ค์ • ๊ฐ€๋Šฅํ•จ
let workspace = await Workspace.findOne({ where: { id: workspaceId, organizationId: organizationId }, transaction: t, });