Node.js/Sequelize

[ORM] ๐Ÿ“š ์‹œํ€„๋ผ์ด์ฆˆ - ๋ชจ๋ธ(ํ…Œ์ด๋ธ”) ์ •์˜ํ•˜๊ธฐ

์ธํŒŒ_ 2021. 11. 10. 12:02

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

 

Mysql ํ…Œ์ด๋ธ” ์ƒ์„ฑ ๊ตฌ๋ฌธ

create schema `nodejs` default character set utf8;
use nodejs;

drop table if exists comments;
drop table if exists users;

create table nodejs.users (
	id int not null primary key auto_increment,
    name varchar(20) not null,
    age smallint unsigned not null,
    married tinyint not null, -- tinyint๋Š” 0๊ณผ 1 ๋ถˆ๋ฆฌ์–ธ ์šฉ
    comment text null, -- ์ž๊ธฐ ์†Œ๊ฐœ
    created_at datetime not null default now(),
    
    unique index name_unique (name asc)
)
	comment = "์‚ฌ์šฉ์ž ์ •๋ณด"
    default character set = utf8
    engine = InnoDB;

create table nodejs.comments (
	id int not null primary key auto_increment,
    commenter int not null,
    comment varchar(100) not null, -- ๋Œ“๊ธ€
    created_at datetime not null default now(),
    
    index commenter_idx(commenter ASC),
    constraint commenter foreign key(commenter) references nodejs.users(id) on delete cascade on update cascade
)
	comment = "๋Œ“๊ธ€"
	default charset = utf8mb4 -- mb4๋Š” ์ด๋ชจํ‹ฐ์ฝ˜๋„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Œ
	engine = InnoDB;

insert into users(name, age, married, comment) values('zero', 24, 0, '์ž๊ธฐ์†Œ๊ฐœ1');
insert into users(name, age, married, comment) values('nero', 32, 1, '์ž๊ธฐ์†Œ๊ฐœ2');

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

์ด์ œ MySQL์—์„œ ์ •์˜ํ•œ ํ…Œ์ด๋ธ”์„ ์‹œํ€„๋ผ์ด์ฆˆ์—์„œ๋„ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

MySQL์˜ ํ…Œ์ด๋ธ”์€ ์‹œํ€„๋ผ์ด์ฆˆ์˜ ๋ชจ๋ธ๊ณผ ๋Œ€์‘๋œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ๋Š” ๋ชจ๋ธ๊ณผ MySQL์˜ ํ…Œ์ด๋ธ”์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

์œ„์—์„œ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ–ˆ์œผ๋‹ˆ,

User ์™€ Comment ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด users ํ…Œ์ด๋ธ”๊ณผ comments ํ…Œ์ด๋ธ”์— ์—ฐ๊ฒฐํ•ด๋ณด์ž.

์‹œํ€„๋ผ์ด์ฆˆ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋ธ ์ด๋ฆ„์€ ๋‹จ์ˆ˜ํ˜• (User), ํ…Œ์ด๋ธ” ์ด๋ฆ„์€ ๋ณต์ˆ˜ํ˜• (users) ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

 

models/user.js

const Sequelize = require('sequelize');

class User extends Sequelize.Model {

   // ์Šคํƒœํ‹ฑ ๋ฉ”์†Œ๋“œ
   // ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์„ค์ •
   static init(sequelize) {

      return super.init(
         {  // ์ฒซ๋ฒˆ์งธ ๊ฐ์ฒด ์ธ์ˆ˜๋Š” ํ…Œ์ด๋ธ” ํ•„๋“œ์— ๋Œ€ํ•œ ์„ค์ •
            name: {
               type: Sequelize.STRING(20),
               allowNull: false,
               unique: true,
            },
            age: {
               type: Sequelize.SMALLINT,
               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, /* static init ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์—ฐ๊ฒฐ๋˜๋Š” ์˜ต์…˜์œผ๋กœ, db.sequelize ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค. */
            timestamps: false, /* true : ๊ฐ๊ฐ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์ƒ์„ฑ, ์ˆ˜์ •๋  ๋•Œ์˜ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์ž…๋ ฅ๋œ๋‹ค. */
            underscored: false, /* ์นด๋ฉœ ํ‘œ๊ธฐ๋ฒ•์„ ์Šค๋„ค์ดํฌ ํ‘œ๊ธฐ๋ฒ•์œผ๋กœ ๋ฐ”๊พธ๋Š” ์˜ต์…˜ */
            modelName: 'User', /* ๋ชจ๋ธ ์ด๋ฆ„์„ ์„ค์ •. */
            tableName: 'users', /* ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„. */
            paranoid: false, /* true : deletedAt์ด๋ผ๋Š” ์ปฌ๋Ÿผ์ด ์ƒ๊ธฐ๊ณ  ์ง€์šด ์‹œ๊ฐ์ด ๊ธฐ๋ก๋œ๋‹ค. */
            charset: 'utf8', /* ์ธ์ฝ”๋”ฉ */
            collate: 'utf8_general_ci'
         }
      );
   }

   // ๋‹ค๋ฅธ ๋ชจ๋ธ๊ณผ์˜ ๊ด€๊ณ„
   static associate(db) { // ์ธ์ž๋กœ index.js์—์„œ ๋งŒ๋“  ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์ด ์ €์žฅ๋˜์–ด์žˆ๋Š” db๊ฐ์ฒด๋ฅผ ๋ฐ›์„ ๊ฒƒ์ด๋‹ค.
      
      db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', onDelete: 'cascade', onUpdate: 'cascade' });
      // db.User (hasMany) db.Comment = 1:N ๊ด€๊ณ„ ์ด๋‹ค.  
      // db.User๋Š” ๊ฐ€์ง€๊ณ ์žˆ๋‹ค. ๋งŽ์ด. db.Comment๋ฅผ
   }
};

module.exports = User;

 

models/comment.js

const Sequelize = require('sequelize');

class Comment extends Sequelize.Model {
   static init(sequelize) {
      return super.init(
         {
            comment: {
               type: Sequelize.STRING(100),
               allowNull: false,
            },
            created_at: {
               type: Sequelize.DATE,
               allowNull: true,
               defaultValue: Sequelize.NOW,
            },
         },
         {
            sequelize,
            timestamps: false,
            modelName: 'Comment',
            tableName: 'comments',
            paranoid: false,
            charset: 'utf8mb4',
            collate: 'utf8mb4_general_ci'
         });
   }

   static associate(db) {
      db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id', onDelete: 'cascade', onUpdate: 'cascade'});
      // db.Comment (belongTo) db.User = N:1 ๊ด€๊ณ„ ์ด๋‹ค.
      // db.Comment๋Š” ์†ํ•ด์žˆ๋‹ค. db.User์—๊ฒŒ
   }
};

module.exports = Comment;

๋ชจ๋ธ์€ Sequelize.Model์„ ํ™•์žฅํ•œ ํด๋ž˜์Šค๋กœ ์„ ์–ธํ•œ๋‹ค.

 

๋ชจ๋ธ์€ static init ๋ฉ”์„œ๋“œ์™€ static associate ๋ฉ”์„œ๋“œ๋กœ ๋‚˜๋‰˜๋Š”๋ฐ,

  init ๋ฉ”์„œ๋“œ์—์„œ๋Š” ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•˜๊ณ ,

  associate ๋ฉ”์„œ๋“œ์—๋Š” ๋‹ค๋ฅธ ๋ชจ๋ธ๊ณผ์˜ ๊ด€๊ณ„(1:1, 1:N)๋ฅผ ์ ๋Š”๋‹ค.

 

init ๋ฉ”์„œ๋“œ์˜ ๋ถ€๋ชจ ์ฝœ๋ฐฑ super.init ๋ฉ”์„œ๋“œ๋Š”

  ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์— ๋Œ€ํ•œ ์„ค์ •์ด๊ณ ,

  ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ํ…Œ์ด๋ธ” ์ž์ฒด์— ๋Œ€ํ•œ ์„ค์ •์ด๋‹ค.

 

์‹œํ€„๋ผ์ด์ฆˆ๋Š” ์•Œ์•„์„œ id๋ฅผ ๊ธฐ๋ณธ ํ‚ค๋กœ ์—ฐ๊ฒฐํ•˜๋ฏ€๋กœ, id ์ปฌ๋Ÿผ์€ ๋”ฐ๋กœ ์ ์–ด์ค„ ํ•„์š”๋Š” ์—†๋‹ค.


์‹œํ€„๋ผ์ด์ฆˆ์˜ ์ž๋ฃŒํ˜•

์‹œํ€„๋ผ์ด์ฆˆ์˜ ์ž๋ฃŒํ˜•์€ MySQL๊ณผ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.

์•„๋ž˜ ๋น„๊ต๋ฅผ ํ†ตํ•ด ๋งž๋Š” ์˜ต์…˜์„ ์ž…๋ ฅํ•˜์ž.

MySQL Sequelize
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
ZEROFILL INTEGER.ZEROFILL

* ์ฐธ๊ณ 

https://sequelize.org/v5/manual/data-types.html

 

Manual | Sequelize

Datatypes Below are some of the datatypes supported by sequelize. For a full and updated list, see DataTypes. Sequelize.STRING // VARCHAR(255) Sequelize.STRING(1234) // VARCHAR(1234) Sequelize.STRING.BINARY // VARCHAR BINARY Sequelize.TEXT // TEXT Sequeliz

sequelize.org


์‹œํ€„๋ผ์ด์ฆˆ ์˜ต์…˜

super.init์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ํ…Œ์ด๋ธ” ์˜ต์…˜์ด๋‹ค.

  • sequelize:
    static init ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์—ฐ๊ฒฐ๋˜๋Š” ์˜ต์…˜์œผ๋กœ, db.sequelize ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.
    ๋‚˜์ค‘์— model/index.js์—์„œ ์—ฐ๊ฒฐํ•œ๋‹ค.
  • timestamps:
    ์ด ์†์„ฑ ๊ฐ’์ด true๋ฉด ์‹œํ€„๋ผ์ด์ฆˆ๋Š” createdAt๊ณผ updatedAt ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๋ฉฐ, ๊ฐ๊ฐ ๋กœ์šฐ๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ์™€ ์ˆ˜์ •๋  ๋•Œ์˜ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์ž…๋ ฅ๋œ๋‹ค.
    (๊ทธ๋Ÿฌ๋‚˜ ์˜ˆ์ œ์—์„  ์ง์ ‘ created_at ์ปฌ๋Ÿผ์„ ๋งŒ๋“ค์—ˆ์œผ๋ฏ€๋กœ ์ง€๊ธˆ์€ timestamps ์†์„ฑ์ด ํ•„์š”์—†๋‹ค. ๋‚˜์ค‘์—” ๊ผญ true๋กœ ํ•˜๊ณ  ํ•˜์ž)
  • underscored
    ์‹œํ€„๋ผ์ด์ฆˆ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ…Œ์ด๋ธ”๋ช…๊ณผ ์ปฌ๋Ÿผ๋ช…์„ ์นด๋ฉœ ํ‘œ๊ธฐ๋ฒ• (camel case) ์œผ๋กœ ๋งŒ๋“ ๋‹ค.
    ์ด๋ฅผ ์Šค๋„ค์ดํฌ ํ‘œ๊ธฐ๋ฒ• (snake case) ์œผ๋กœ ๋ฐ”๊พธ๋Š” ์˜ต์…˜์ด๋‹ค (์˜ˆ๋ฅผ๋“ค์–ด updatedAt ์„ updated_at ์œผ๋กœ).
  • modelName:
    ๋ชจ๋ธ ์ด๋ฆ„์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋…ธ๋“œ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.
  • tableName:
    ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ์ด๋ฆ„.
    ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋ธ ์ด๋ฆ„์˜ ์†Œ๋ฌธ์ž ๋ฐ ๋ณต์ˆ˜ํ˜•์œผ๋กœ ๋งŒ๋“ ๋‹ค.
    ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋ธ ์ด๋ฆ„์ด User ๋ผ๋ฉด ํ…Œ์ด๋ธ” ์ด๋ฆ„์€ users ์ด๋‹ค.
  • paranoid:
    true๋กœ ์„ค์ •ํ•˜๋ฉด deletedAt์ด๋ผ๋Š” ์ปฌ๋Ÿผ์ด ์ƒ๊ธด๋‹ค.
    ๋กœ์šฐ๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์™„์ „ํžˆ ์ง€์šฐ์ง€ ์•Š๊ณ , deletedAt์— ์ง€์šด ์‹œ๊ฐ์ด ๊ธฐ๋ก๋œ๋‹ค.
    ๋กœ์šฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ช…๋ น์„ ๋‚ด๋ ธ์„ ๊ฒฝ์šฐ deletedAt์˜ ๊ฐ’์ด null์ธ ๋กœ์šฐ๋ฅผ ์กฐํšŒํ•œ๋‹ค.
    ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ์ด์œ ๋Š” ํ›„์— ๋กœ์šฐ๋ฅผ ๋ณต์›ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค. ๋กœ์šฐ๋ฅผ ๋ณต์›ํ•ด์•ผ ํ•  ์ƒํ™ฉ์ด ์ƒ๊ธธ ๊ฒƒ ๊ฐ™๋‹ค๋ฉด ๋ฏธ๋ฆฌ true๋กœ ์„ค์ •ํ•ด๋‘์ž.
  • charset / collate:
    ๊ฐ๊ฐ utf8 ๊ณผ utf8_general_ci ๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๊ธ€์ด ์ž…๋ ฅ๋œ๋‹ค.
    ์ด๋ชจํ‹ฐ์ฝ˜๊นŒ์ง€ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด utf8mb4 ์™€ utf8mb4_general_ci ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค.

์ด์ œ models/index.js์— ๋‘ ํ…Œ์ด๋ธ” ํŒŒ์ผ์„ ์—ฐ๊ฒฐ์‹œ์ผœ์ฃผ์ž.

const Sequelize = require('sequelize');

// ํด๋ž˜์Šค๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
const User = require('./user')
const Comment = require('./comment')

const env = process.env.NODE_ENV || 'development';

// config/config.json ํŒŒ์ผ์— ์žˆ๋Š” ์„ค์ •๊ฐ’๋“ค์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
// config๊ฐ์ฒด์˜ env๋ณ€์ˆ˜(development)ํ‚ค ์˜ ๊ฐ์ฒด๊ฐ’๋“ค์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
// ์ฆ‰, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •์„ ๋ถˆ๋Ÿฌ์˜จ๋‹ค๊ณ  ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.
const config = require("../config/config.json")[env]

const db = {};

// new Sequelize๋ฅผ ํ†ตํ•ด MySQL ์—ฐ๊ฒฐ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
const sequelize = new Sequelize(config.database, config.username, config.password, config)

// ์—ฐ๊ฒฐ๊ฐ์ฒด๋ฅผ ๋‚˜์ค‘์— ์žฌ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด db.sequelize์— ๋„ฃ์–ด๋‘”๋‹ค.
db.sequelize = sequelize; 

// ๋ชจ๋ธ ํด๋ž˜์Šค๋ฅผ ๋„ฃ์Œ.
db.User = User;
db.Comment = Comment;

// ๋ชจ๋ธ๊ณผ ํ…Œ์ด๋ธ” ์ข…ํ•ฉ์ ์ธ ์—ฐ๊ฒฐ์ด ์„ค์ •๋œ๋‹ค.
User.init(sequelize); 
Comment.init(sequelize);

// db๊ฐ์ฒด ์•ˆ์— ์žˆ๋Š” ๋ชจ๋ธ๋“ค ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ์„ค์ •๋œ๋‹ค.
User.associate(db);
Comment.associate(db);

// ๋ชจ๋“ˆ๋กœ ๊บผ๋‚ธ๋‹ค.
module.exports = db;

db๋ผ๋Š” ๊ฐ์ฒด์— User๊ณผ Comment ๋ชจ๋ธ์„ ๋‹ด์•˜๋‹ค.

์•ž์œผ๋กœ db ๊ฐ์ฒด๋ฅผ requireํ•˜์—ฌ User๊ณผ Comment ๋ชจ๋ธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๋˜ํ•œ User.init๊ณผ Comment.init์€ ๊ฐ๊ฐ์˜ ๋ชจ๋ธ์˜ static.init ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, init์ด ์‹คํ–‰๋˜์–ด์•ผ ํ…Œ์ด๋ธ”์ด ๋ชจ๋ธ๋กœ ์—ฐ๊ฒฐ๋œ๋‹ค.

๋‹ค๋ฅธ ํ…Œ์ด๋ธ”๊ณผ์˜ ๊ด€๊ณ„๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” associate ๋ฉ”์„œ๋“œ ์—ญ์‹œ ์‹คํ–‰ํ•ด๋‘”๋‹ค.


Sequelize ๊ด€๊ณ„ ์ •์˜ํ•˜๊ธฐ

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

์ด๋ฒˆ์—” users ํ…Œ์ด๋ธ”๊ณผ comments ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ •์˜ํ•ด๋ณด์ž.

์‚ฌ์šฉ์ž ํ•œ ๋ช…์€ ๋Œ“๊ธ€์„ ์—ฌ๋Ÿฌ ๊ฐœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋Œ“๊ธ€ ํ•˜๋‚˜์— ์ž‘์„ฑ์ž๊ฐ€ ์—ฌ๋Ÿฌ ๋ช…์ผ ์ˆ˜๋Š” ์—†๋‹ค.

์ด๋Ÿฌํ•œ ๊ด€๊ณ„๋ฅผ ์ผ๋Œ€๋‹ค (1:N) ๊ด€๊ณ„๋ผ๊ณ  ํ•œ๋‹ค. ์œ„ ๊ด€๊ณ„์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ 1์ด๊ณ  ๋Œ“๊ธ€์ธ N์ด๋‹ค.

 

๋‹ค๋ฅธ ๊ด€๊ณ„๋กœ๋Š” ์ผ๋Œ€์ผ (1:1), ๋‹ค๋Œ€๋‹ค (N:M) ๊ด€๊ณ„๊ฐ€ ์žˆ๋‹ค.

์ผ๋Œ€์ผ ๊ด€๊ณ„๋กœ๋Š” ์‚ฌ์šฉ์ž์™€ ์‚ฌ์šฉ์ž์˜ ๋Œ€ํ•œ ์ •๋ณด ํ…Œ์ด๋ธ”์„ ์˜ˆ๋กœ ๋“ค ์ˆ˜ ์žˆ์œผ๋ฉฐ,

๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋กœ๋Š” ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”๊ณผ ํ•ด์‹œํƒœ๊ทธ (#) ํ…Œ์ด๋ธ” ๊ด€๊ณ„๋ฅผ ์˜ˆ๋กœ ๋“ค ์ˆ˜ ์žˆ๊ฒ ๋‹ค.

ํ•œ ๊ฒŒ์‹œ๊ธ€์— ์—ฌ๋Ÿฌ ํ•ด์‹œํƒœ๊ทธ๊ฐ€ ๋‹ฌ๋ฆด ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•œ ํ•ด์‹œํƒœ๊ทธ ๋˜ํ•œ ์—ฌ๋Ÿฌ ๊ฒŒ์‹œ๊ธ€์— ๋‹ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๊ณ„ ๋ชจ๋ธ์— ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ?

 

[DB] ๐Ÿ“š ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง (1:N ๊ด€๊ณ„) ๐Ÿ“ˆ ERD ๋‹ค์ด์–ด๊ทธ๋žจ

๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง ์ˆœ์„œ ์—…๋ฌด ํŒŒ์•… -> ๊ฐœ๋…์  ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง -> ๋…ผ๋ฆฌ์  ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง -> ๋ฌผ๋ฆฌ์  ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง 1. ์—…๋ฌด ํŒŒ์•… - ๊ธฐํš์„ ์ž‘์„ฑ (๊ฒŒ์‹œํŒ์„ ์˜ˆ๋ฅผ ๋“ค๊ฒ ๋‹ค.) โ€‹ โ€‹ 2. ๊ฐœ๋…์  ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง  - ER

inpa.tistory.com

 

MySQL์—์„œ๋Š” JOIN์ด๋ผ๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ํŒŒ์•…ํ•ด ๊ฒฐ๊ณผ๋ฅผ ๋„์ถœํ•œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ๋Š” JOIN ๊ธฐ๋Šฅ๋„ ์•Œ์•„์„œ ๊ตฌํ˜„ํ•œ๋‹ค.

๋Œ€์‹  ํ…Œ์ด๋ธ” ๊ฐ„์— ์–ด๋– ํ•œ ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š”์ง€ ์‹œํ€„๋ผ์ด์ฆˆ์— ์•Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค.


1:N ๊ด€๊ณ„ (hasMany, belongsTo)

์‹œํ€„๋ผ์ด์ฆˆ์—์„œ๋Š” 1:N ๊ด€๊ณ„๋ฅผ hasMany ๋ฉ”์„œ๋“œ๋กœ ํ‘œํ˜„ํ•œ๋‹ค.

users ํ…Œ์ด๋ธ”์˜ ๋กœ์šฐ ํ•˜๋‚˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ์—ฐ๊ฒฐ๋œ comments ํ…Œ์ด๋ธ”์˜ ๋กœ์šฐ๋“ค๋„ ๊ฐ™์ด ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

 

๋ฐ˜๋Œ€๋กœ belongsTo ๋ฉ”์„œ๋“œ๋„ ์žˆ๋‹ค.

์ด๋Š” comments ํ…Œ์ด๋ธ”์˜ ๋กœ์šฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ์—ฐ๊ฒฐ๋œ users ํ…Œ์ด๋ธ”์˜ ๋กœ์šฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

๋ณดํ†ต ์™ธ๋ž˜ํ‚ค๊ฐ€ ๋ถ™์€ ํ…Œ์ด๋ธ”์ด belongTo๊ฐ€ ๋œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

 

์ด์ œ ๋ชจ๋ธ ๊ฐ๊ฐ์˜ static associate ๋ฉ”์„œ๋“œ์— ์ •์˜๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

/* user.js */

    static associate(db) {
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', onDelete: 'cascade', onUpdate: 'cascade' });
        // db.User (hasMany) db.Comment = 1:N ๊ด€๊ณ„ ์ด๋‹ค.
        // ๋‚จ(db.Comment)์˜ ์ปฌ๋Ÿผ commenter๊ฐ€ ๋‚ด(db.User) ์ปฌ๋Ÿผ id๋ฅผ ์ฐธ์กฐ ํ•˜๊ณ  ์žˆ๋‹ค.  
    }
};
// comment.js

    static associate(db) {
        db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id', onDelete: 'cascade', onUpdate: 'cascade'});
        // db.Comment (belongTo) db.User = N:1 ๊ด€๊ณ„ ์ด๋‹ค.
        // ๋‚ด(db.Comment)์˜ ์ปฌ๋Ÿผ commenter๋Š” ๋‚จ(db.User) ์ปฌ๋Ÿผ id์— ์†ํ•ด ์žˆ๋‹ค.  
    }
};

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

๊ฐ„๋‹จํžˆ User๊ฐ€ ๋งŽ์€ ๋Œ“๊ธ€์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ User.hasMany๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๊ณ ,

Comment๋Š” ํ•œ User์— ์†ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ Comment.belongsTo๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋‘˜์ด ์†Œํ†ตํ•˜๋Š” ํ‚ค๋Š” foreignKey์ธ commenter์ด๋ฉฐ,

User์˜ sourceKey๋Š” ๊ณง Commenter์˜ targetKey๊ฐ€ ๋œ๋‹ค (hasMany์—์„œ๋Š” sourceKey, belongsTo์—์„œ๋Š” targetKey).

 

foreignKey๋ฅผ ๋”ฐ๋กœ ์ง€์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ด๋ฆ„์ด ๋ชจ๋ธ๋ช…+๊ธฐ๋ณธ ํ‚ค์ธ ์ปฌ๋Ÿผ์ด ๋ชจ๋ธ์— ์ƒ์„ฑ๋œ๋‹ค.

์ฆ‰, ์˜ˆ๋ฅผ ๋“ค์–ด ์œ„ ์˜ˆ์ œ์—์„œ commenter๋ฅผ foreignKey๋กœ ์„ค์ •ํ•ด ์ฃผ์ง€ ์•Š์•˜๋‹ค๋ฉด, ์ œ์•ฝ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ชจ๋ธ๋ช…์ธ User๊ณผ ๊ธฐ๋ณธ ํ‚ค์ธ id๊ฐ€ ํ•ฉ์ณ์ง„ UserId๊ฐ€ foreignKey๋กœ ๋”ฐ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

 

 

1:1 ๊ด€๊ณ„ (hasOne, belongsTo)

1:1 ๊ด€๊ณ„์—์„œ๋Š” hasMany ๋Œ€์‹  hasOne์„ ์‚ฌ์šฉํ•œ๋‹ค.

foriegnKey, sourceKey, targetKey์˜ ์‚ฌ์šฉ๋ฒ•์€ 1:N ๊ด€๊ณ„์™€ ๊ฐ™๋‹ค.

 

hasOne๋„ 1:1, belongsTo๋„ 1:1์ด๋ฉด, ๋ˆ„๊ฐ€ ๊ธฐ์ค€์œผ๋กœ ๋ ์ง€ ์• ๋งคํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค.

์ด๋•Œ ์™ธ๋ž˜ํ‚ค๊ฐ€ ๋ถ™์€ ๋ชจ๋ธ์€ belongsTo๋กœ ๊ธฐ์ค€์„ ์žก์•„์ฃผ๋ฉด ๋œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ
์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

 

 

N:M ๊ด€๊ณ„ (belongsToMany)

์‹œํ€„๋ผ์ด์ฆˆ์—๋Š” N:M ๊ด€๊ณ„๋ฅผ belongsToMany ๋ฉ”์„œ๋“œ๋กœ ํ‘œํ˜„ํ•œ๋‹ค.

์ด ๊ฒฝ์šฐ์—” ์–ด๋Š ํ•œ ํ…Œ์ด๋ธ”์ด ์–ด๋Š ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์ข…์†๋˜๋Š” ๊ด€๊ณ„๊ฐ€ ์•„๋‹ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด Post ๋ชจ๋ธ๊ณผ Hashtag ๋ชจ๋ธ์ด ์žˆ๋‹ค๊ณ  ํ•  ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

// Post
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });

// Hashtag
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });

 

N:M ๊ด€๊ณ„์˜ ํŠน์„ฑ์ƒ ์ƒˆ๋กœ์šด ๋ชจ๋ธ์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑ๋˜๋ฉฐ, through ์†์„ฑ์— ๊ทธ ์ด๋ฆ„์„ ์ ์œผ๋ฉด ๋œ๋‹ค.

์ƒˆ๋กœ ์ƒ์„ฑ๋œ PostHashtag ๋ชจ๋ธ์—๋Š” ๊ฒŒ์‹œ๊ธ€๊ณผ ํ•ด์‹œํƒœ๊ทธ์˜ ์•„์ด๋””๊ฐ€ ์ €์žฅ๋œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

N:M์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ์•ผ ํ•œ๋‹ค.

์‹œํ€„๋ผ์ด์ฆˆ-๋ชจ๋ธ

์˜ˆ๋ฅผ ๋“ค์–ด #๋…ธ๋“œ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒŒ์‹œ๋ฌผ์„ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ, ๋จผ์ € #๋…ธ๋“œ ํ•ด์‹œํƒœ๊ทธ๋ฅผ Hashtag ๋ชจ๋ธ์—์„œ ์กฐํšŒํ•˜๊ณ , ๊ฐ€์ ธ์˜จ ํƒœ๊ทธ์˜ ์•„์ด๋””์ธ '1'์„ ๋ฐ”ํƒ•์œผ๋กœ PostHashtag ๋ชจ๋ธ์—์„œ hashtagId๊ฐ€ 1์ธ postId๋“ค์„ ์ฐพ์•„ Post ๋ชจ๋ธ์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.

 

๋˜ํ•œ, ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๋ชจ๋ธ๋“ค๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

db.sequelize.models.PostHashtag

Reference

https://lgphone.tistory.com/86