테이블간에는
일대일(1:1)
,일대다(1:n)
,다대다(n:n)
등의 관계가 존재
앞선 포스팅에서 설명했듯 관계형 데이터베이스 관리시스템 (RDBMS: Database Management System)
은 테이블간의 관계를 정의하여 관리하는 미들웨어이다.
테이블간의 관계는 models/index.js
파일에서 associate
메서드를 사용해 정의할 수 있다.
또한 관계의 종류에는 일대일(1:1)
, 일대다(1:n)
, 다대다(n:n)
등이 있다.
각각의 관계를 SNS 사용 예시로 설명하면 다음과 같다.
User의 정보는 한명의 고유한 정보이며, 다른 User의 정보가 될 수 없다.
이러한 관계는 hasOne
, belongsTo
을 사용하여 아래와 같이 일대일(1:1)
관계를 표현할 수 있다.
// 유저, 유저정보는 서로 단독으로 존재한다 -> 일대일관계
User.associate = (db) => {
db.User.hasOne(db.UserInfo);
};
UserInfo.associate = (db) => {
db.UserInfo.belongsTo(db.User);
};
한명의 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);
};
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);
};
다대다(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' });
};
테이블, 필드명을 수정할 때 사용하는 일종의 별칭
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' });
};
서로 다른 테이블을 연결하는 데 사용하는
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' });
};
생성된
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
모델 등록작성한 모델을 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;
Sequelize
등록app.js
파일에서 작성한 express
에 Sequelize
를 등록
// 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('서버 실행 중');
});
Database
생성Sequelize
등록한 뒤 node app
명령어로 서버를 실행하면 아래와 같은 에러가 발생한다.
위 에러는 Database
생성하지 않아 발생한 에러로 아래 npm명령어를 통해 Database
를 생성한다.
npx sequelize db:create
Database
를 생성한 뒤 다시 서버를 실행해보면 다음과 같이 정상적으로 테이블이 생성된 것을 확인할 수 있다.
Sequelize는 테이블간 데이터전달시 다양한 옵션을 포함할 수 있다.
대표적인 메서드는 다음과 같으며 자세한 정보는 해당 포스팅에서 확인할 수 있다.
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
를 사용하면 특정 데이터를 제외하여 전달할 수 있다.
사용방법은 다음과 같다.
// 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 만들기 - 제로초