(Node.js) Sequelize Relationship Setup

Mirrer·2022년 9월 5일
0

Node.js

목록 보기
4/12
post-thumbnail

Sequelize Relationship

테이블간에는 일대일(1:1), 일대다(1:n), 다대다(n:n)등의 관계가 존재

앞선 포스팅에서 설명했듯 관계형 데이터베이스 관리시스템 (RDBMS: Database Management System)은 테이블간의 관계를 정의하여 관리하는 미들웨어이다.

테이블간의 관계는 models/index.js파일에서 associate 메서드를 사용해 정의할 수 있다.

또한 관계의 종류에는 일대일(1:1), 일대다(1:n), 다대다(n:n)등이 있다.

각각의 관계를 SNS 사용 예시로 설명하면 다음과 같다.


일대일(1:1) 관계

User의 정보는 한명의 고유한 정보이며, 다른 User의 정보가 될 수 없다.

이러한 관계는 hasOne, belongsTo을 사용하여 아래와 같이 일대일(1:1) 관계를 표현할 수 있다.

// 유저, 유저정보는 서로 단독으로 존재한다 -> 일대일관계
User.associate = (db) => {
  db.User.hasOne(db.UserInfo);
};

UserInfo.associate = (db) => {
  db.UserInfo.belongsTo(db.User);
};

일대다(1:n) 관계

한명의 User는 다수의 Post를 작성할 수 있다, 하지만 Post에는 한명의 작성자만이 존재한다.

이러한 관계는 일대일(1:1) 관계와 동일하게 hasOne, belongsTo을 사용하여 아래와 같이 일대다(1:n) 관계를 표현할 수 있다.

// 포스트는 특정 유저에 속해있다
Post.associate = (db) => {
 db.Post.belongsTo(db.User);
};

// 유저, 유저정보는 서로 단독으로 존재한다 -> 일대일관계
User.associate = (db) => {
  db.User.hasOne(db.UserInfo);
};

UserInfo.associate = (db) => {
  db.UserInfo.belongsTo(db.User);
};

다대다(n:n) 관계

Post에서는 여러 HashTag가 존재하며, 반대로 특정 HashTag를 선택하면 HashTag가 포함된 여러 Post들이 선택된다.

이러한 관계는 belongsToMany를 사용하여 아래와 같이 다대다(n:n) 관계를 표현할 수 있다.

// 해쉬태그, 포스트는 서로 여려개를 가진다. => 다대다관계
Hashtag.associate = (db) => {
  db.Hashtag.belongsToMany(db.Post);
};

Post.associate = (db) => {
  db.Post.belongsToMany(db.Hashtag);
};

위에서 소개한 belongsTo, belongsToMany관계는 특정 테이블에 속해 있다.

그래서 관계 설정시 이를 구분할 수 있는 컬럼이 테이블에 자동으로 생성되는데 belongsTo는 해당 테이블에, belongsToMany는 매핑 테이블에 생성된다.

// Post테이블은 User테이블에 속해있다.
// Post테이블에는 User테이블의 데이터를 구분할 수 있는 컬럼(UserId)이 생성된다.
Post.associate = (db) => {
 db.Post.belongsTo(db.User);
};

Mapping Table

다대다(n:n) 관계에서 원활한 검색을 위해 생성된 테이블

다대다(n:n) 관계에서는 두 테이블간에 매핑 테이블이라는 새로운 테이블이 생성되어 이를 통해 원활한 검색이 가능하다.

아래와 같이 Member, Product 두 테이블이 다대다(n:n) 관계를 형성한다면 Member Product라는 매핑 테이블이 생성된다.

매핑 테이블의 이름은 2번째 인자 through를 사용해 정의할 수 있으며, 단 두 테이블에서 모두 동일하게 정의 해야한다.

// 매핑 테이블명 : postuser -> Like
User.associate = (db) => {
  db.User.belongsToMany(db.Post, { through: 'Like' });
};

Post.associate = (db) => {
  db.Post.belongsToMany(db.User, { through: 'Like' });
};

Table Alias

테이블, 필드명을 수정할 때 사용하는 일종의 별칭

Sequelize에서 테이블, 또는 필드의 이름이 중복된다면 as(Alias)를 사용하여 동일한 테이블명을 구별하는 별칭을 지정한다.

User.associate = (db) => {
  // 동일한 Post테이블을 사용
  db.User.belongsTo(db.Post); 
  // Post테이블을 구분하기 위해 아래 관계의 Post테이블은 Liked로 변경
  db.User.belongsToMany(db.Post, { through: 'Like', as: 'Liked' });
};

Post.associate = (db) => {
  // 동일한 User테이블을 사용
  db.Post.belongsTo(db.User);
  // User테이블을 구분하기 위해 아래 관계의 User테이블은 Likers로 변경
  db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' });
};

ForeignKey

서로 다른 테이블을 연결하는 데 사용하는 Key이다.

위에서 belongsTo, belongsToMany관계시 이를 구분할 수 있는 컬럼이 테이블에 자동으로 생성된다고 설명했다.

하지만 동일 테이블간 관계를 설정한다면 매핑 테이블에는 동일한 이름의 컬럼이 중복되어 생성된다.

이 때 외래키(foreignKey)를 사용하면 생성된 컬럼명을 구별하는 별칭을 지정할 수 있다.

// 매핑 테이블 Follow에는 UserId 컬럼이 중복 생성
// foreignKey를 사용하여 컬럼의 이름을 변경
User.associate = (db) => {
  db.User.belongsToMany(db.User, { through: 'Follow', as: 'Followers', foreignKey: 'followingId' });
  db.User.belongsToMany(db.User, { through: 'Follow', as: 'Followings', foreignKey: 'followerId' });
};

Relationship Method

생성된 Sequelize 모델을 컨트롤할 수 있는 메서드

관계를 정의하면 두 모델의 인스턴스간 관계를 맺을 수 있는 Relationship Method를 제공한다.

이는 접근자(accessor)라고도 하며, 이 메소드를 통해 두 인스턴스 간의 관계를 만들어 컨트롤한다.

Relationship Method의 종류는 다음과 같다.

Method조작
add관계 테이블에 데이터 추가
get관계 테이블에 데이터 가져오기
set관계 테이블에 데이터 교체
remove관계 테이블에 데이터 삭제

Relationship Method기존모델.관계메서드+관계테이블형태로 사용하며 연결관계에 따라 관계테이블은 단수와 복수를 구분(~s)하여 작성한다.

// belongsTo는 단수
db.Post.belongsTo(db.User); // post.addUser
db.Post.belongsTo(db.Post, { as: 'Retweet' }); // post.addRetweet

// belongsToMany는 복수
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' }); // post.addHashtags
db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' }) // // post.addLikers(post에 좋아요한 사람을 추가)

// hasMany는 복수
db.Post.hasMany(db.Comment); // post.addComments
db.Post.hasMany(db.Image); // post.addImages

Sequelize Sync

Sequelize Sync다음과 같은 방법을 통해 구현한다.


  1. Sequelize 모델 등록

작성한 모델을 index.js에서 불러온뒤 실행하여 시퀄라이즈에 등록

// index.js
const Sequelize = require('sequelize');
// 모델 불러오기
const comment = require('./comment');
const hashtag = require('./hashtag');
const image = require('./image');
const post = require('./post');
const user = require('./user');

const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config);

// 모델 저장
db.Comment = comment;
db.Hashtag = hashtag;
db.Image = image;
db.Post = post;
db.User = user;

// 반복문으로 각 모델을 시퀄라이즈와 연결
Object.keys(db).forEach(modelName => {
  db[modelName].init(sequelize);
});

// 반복문으로 각 모델의 associate를 실행하여 등록한 관계를 설정
Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
  1. Sequelize 등록

app.js파일에서 작성expressSequelize를 등록

// app.js
const express = require('express');
const postRouter = require('./routes/post');
const db = require('./models'); // 모델 함수 불러오기
const app = express();

// 서버실행시 시퀄라이즈 연결
db.sequelize.sync()
  .then(() => {
    console.log('db 연결 성공');
  })
  .catch(console.error);

server.listen(3065, () => {
  console.log('서버 실행 중');
});
  1. Database 생성

Sequelize 등록한 뒤 node app 명령어로 서버를 실행하면 아래와 같은 에러가 발생한다.

위 에러는 Database 생성하지 않아 발생한 에러로 아래 npm명령어를 통해 Database 를 생성한다.

npx sequelize db:create

Database를 생성한 뒤 다시 서버를 실행해보면 다음과 같이 정상적으로 테이블이 생성된 것을 확인할 수 있다.


Passing Table Data

Sequelize테이블간 데이터전달시 다양한 옵션을 포함할 수 있다.

대표적인 메서드는 다음과 같으며 자세한 정보는 해당 포스팅에서 확인할 수 있다.

Include

Include를 사용하면 특정 테이블과의 관계 데이터를 전달할 수 있다.

사용방법은 다음과 같다.

// routes/user.js
const express = require('express');
const bcrypt = require('bcrypt');
const passport = require('passport');

const { User, Post } = require('../models'); // post db 불러오기

const router = express.Router();

router.post('/login', (req, res, next) => 
  passport.authenticate('local', (err, user, info) =>    
    // 다른 테이블과의 관계, 데이터를 합쳐서 전달
    const fullUserWithoutPassword = await User.findOne({
        where: { id: user.id },
        include: [{
          model: Post, // user hasmany(post) -> 내가 작성한 게시글
        }, {
          model: User, // user belongstomany(post) -> 팔로잉 데이터
          as: 'Followings', // as followings
        }, {
          model: User, // user belongstomany(post) -> 팔로워 데이터
          as: 'Followers',  // as followers
        }]			
      })
      return res.status(200).json(user);
    });
  })(req, res, next);
});

module.exports = router;

attributes

attributes를 사용하면 특정 데이터를 제외하여 전달할 수 있다.

사용방법은 다음과 같다.

// routes/user.js
const express = require('express');
const bcrypt = require('bcrypt');
const passport = require('passport');

const { User, Post } = require('../models');

const router = express.Router();

router.post('/login', (req, res, next) => 
	passport.authenticate('local', (err, user, info) =>	          
      const fullUserWithoutPassword = await User.findOne({
        where: { id: user.id },
        // attributes: ['id', 'nickname', 'email'], -> id, nickname, email만 전달
        attributes: { exclude: ['password'] }, // 전체 데이터중 password만 제외하고 전달
        include: [{
            model: Post,
     		attributes: ['id'], // Post 테이블에 id컬럼만 전달
        }, {
            model: User,
            as: 'Followings',
        }, {
            model: User,
            as: 'Followers', 
        }]			
      })
      return res.status(200).json(fullUserWithoutPassword);
    });
  })(req, res, next);
});

module.exports = router;

참고 자료

Node.js 공식문서
Node.js 교과서 - 조현영
React로 NodeBird SNS 만들기 - 제로초

profile
memories Of A front-end web developer

0개의 댓글