관련성을 가지며 중복이 없는 데이터의 집합을 의미한다.
이런 데이터 베이스를 관리하는 시스템을 DBMS(Database Management System)라고 부른다.
key와 value들의 간단한 관계를 테이블화 시킨 데이터 베이스
Table
Attribytes (속성)
column 값을 의미한다.
Tuple (행)
row 값을 의미한다.
Primary Key (기본키)
다른 데이터와 구별할 수 있는 식별자
한 테이블에는 하나 혹은 그 이상의 Primary Key가 있어야 한다.
Not null, unique 한 값이다.
Index
데이터의 색인 기능
관계형 데이터 베이스에서 검색 속도를 높이기 위한 도구이다.
unique 해서 특정 데이터를 대표하는데 사용한다.
(주로 기본키로 사용한다)
Foreign Key (외래키)
한 테이블의 column 중 다른 테이블의 Row를 식별할 수 있는 Key
1 : 1 관계 (일대일 관계)
한 쪽 대체가 다른 쪽 개체가 반드시 하나로 매핑되어 있어야 한다.
ex) 이름과 주민번호
1 : N 관계 (일대다 관계)
한 쪽 개체가 관계를 맺은 개체 쪽의 여러 객체를 가질 수 있는 것을 의미한다.
🚫데이터 중복 가능성이 있다🚫
ex) 이름과 이메일
M : N 관계 (다대다 관계)
양쪽 개체 모두에서 1 : N 관계가 존재할 때 나타나는 모습.
즉 서로가 서로를 1 : N 관계로 보고 있다.
🚫데이터 중복 가능성이 있다🚫
ex) 이름과 스터디
관계형 데이터 베이스를 논리적이고 직관적으로 만드는 과정
각 로우마다 컬럼의 값이 1개씩만 있어야 한다.
= 컬럼이 원자 값을 갖는다.
아직 이상현상이 발생할 수 있다.
1차 정규화를 만족하고 테이블의 모든 컬럼이 완전 함수적 종속을 만족하는 것
기본키 중에 특정 컬럼에만 종속된 컬럼(부분적 종속)이 없어야 한다.
모든 테이블이 기본키에 함수적 종속
아직 이상현상이 발생할 수 있다.
2차 정규화를 만족하고 이행적 함수 종속을 제거
정규화된 스키마는 입력, 삭제, 수정할 때 관계를 맺고 있는 다른 테이블에서 JOIN을 이용한다.
저장된 자료가 많으면 JOIN 시 성능이 떨어진다.
역 정규화라는 시스템의 퍼포먼스를 위해 정규화에 위배되는 행위를 하는 것
NodeJS에서 자바스크립트의 객체와 데이터베이스의 릴레이션을 매핑해주는 라이브러리
SQL을 사용하지 않고 자바스크립트로 데이터베이스에 접근하여 데이터를 Access
SQL을 사용하면 커넥션풀을 생성해서 SQL으로 CREATE를 사용하지만
Sequelize는 ORM을 사용해서 CREATE한다.
객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것을 말한다.
객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다.
npm install -g sequelize-cli sequelize mysql2
sequelize init
{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"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"
}
}
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
테이블 컬럼을 추자/제거 할 때 Migrations 파일을 통해서 실제 DB에 반영한다.
Migrations 파일을 통해 Rollback도 가능하다.
시드 데이터를 생성하는 곳, 주로 샘플 데이터를 생성하고 지울 때 (테스트 환경) 사용한다.
"development": {
"username": "admin",
"password": "~비밀번호~",
"database": "SOPT_27",
"host": "db-sopt-server.ctmbn9ymizbs.ap-northeast-2.rds.amazonaws.com",
"dialect": "mysql"
},
처음에 생성된 내용 지우고 다음 내용을 적는다.
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
db.sequelize = sequelize; db.Sequelize = Sequelize;
module.exports = db;
const { sequelize } = require('./models');
sequelize.sync({ alter: false })
.then(() => {
console.log('데이터베이스 연결 성공.');
})
.catch((error) => {
console.error(error);
})
Sequelize에서 모델이란?
데이터 베이스 테이블의 스키마를 표현하는 수단!
sequelize.define('모델이름',{/*스키마*/},{/*스키마옵션*/});
위를 이용해서 정의한다!
const { sequelize } = require(".");
module.exports = (sequelize, DataTypes) =>{
return sequelize.define('User',{
// 모델의 attributes (column)
email : {
type : DataTypes.STRING(30),
unique : true,
allowNull : false,
},
userName :{
type: DataTypes.STRING(20),
allowNull: false,
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
},
salt: {
type: DataTypes.STRING(200),
allowNull: false,
},
},{
// 모델의 옵션
freezeTableName : true, // true면 모델명과 테이블 이름을 동일하게 설정함.
timestamps : false,
})
}
MySQL | Sequelize |
---|---|
VARCHAR(100) | STRING(100) |
INT | INTEGER |
TINYINT | BOOLEAN |
DATETIME | DATE |
NOT NULL | allowNull : false |
UNIQUE | unique : true |
DEFAULT now() | defaultValue : Sequelize.NOW |
❗️Sequelize에서 PK를 지정해 주지 않으면 자동으로 id라는 컬럼의 PK 값이 생성된다❗️
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.User = require('./user')(sequelize,Sequelize); // 추가
module.exports = db;
정의한 User 모델이 실제 데이터 베이스에 대응되어 테이블이 생성된다.
app.js의 sequelize.sync({ alter: false })
부분에서 모델 동기화가 일어난다.
sequelize.sync({})
: 테이블이 존재하지 않는 경우 생성한다.(이미 존재하면 아무 작업 수행 ❌)sequelize.sync({force : ture})
: 테이블이 생성되고 이미 존재하는 경우 먼저 삭제 후 생성한다.sequelize.sync({alter : true})
: 데이터 베이스에 있는 테이블의 현재 상태(컬럼, 데이터 유형)을 확인한 후 테이블에서 필요한 변경을 수행하여 모델과 일치시킨다.router.post('/signup', async (req, res) => {
console.log("dsf")
// 1. req.body에서 데이터 가져오기
const { email, password, userName } = req.body;
// 2. request data 확인하기, email, password, userName data가 없다면 NullValue 반환
if(!email || !password || !userName){
console.log('필요한 값이 없습니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
}
try {
//3. 존재하는 이메일인지 확인하기. 이미 존재하는 이메일면 ALREADY ID 반환
const alreadyEmail = await User.findOne({
where : {
email : email,
}
});
if(alreadyEmail){
console.log('이미 존재하는 이메일 입니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST,responseMessage.ALREADY_ID));
}
//4. salt 생성
const salt = crypto.randomBytes(64).toString('base64');
//5. 2차 세미나때 배웠던 pbkdf2 방식으로 (비밀번호 + salt) => 암호화된 password
const hashedPassword = crypto.pbkdf2Sync(password,salt,10000,64,'sha512').toString('base64');
//6. User email, 암호화된 password, salt, userName 생성!
const user = await User.create({
email : email,
password : hashedPassword,
userName : userName,
salt : salt,
});
console.log(user)
//7. status: 200 message: SING_UP_SUCCESS, data: id, email, userName 반환! (비밀번호, salt 반환 금지!!)
return res.status(statusCode.OK).send(util.success(statusCode.OK,responseMessage.SIGN_IN_SUCCESS, {id : user.id, email,userName}));
} catch(error){
console.error(error);
return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR,responseMessage.SIGN_IN_FAIL));
}
})
router.post('/signin', async (req, res) => {
// 1. req.body에서 데이터 가져오기
const { email, password } = req.body;
// 2. request data 확인하기, email, password, userName data가 없다면 NullValue 반환
if (!email || !password){
console.log('필요한 값이 없습니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
}
try{
// 3. 존재하는 아이디인지 확인하기. 존재하지 않는 아이디면 NO USER 반환
const alreadyEmail = await User.findOne({
where : {
email : email,
},
});
console.log(alreadyEmail);
if (!alreadyEmail){
console.log('없는 이메일 입니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST,responseMessage.NO_USER));
}
// 4. 비밀번호 확인하기 - 로그인할 email의 salt를 DB에서 가져와서 사용자가 request로 보낸 password와 암호화를 한후 디비에 저장되어있는 password와 일치하면 true
// 일치하지 않으면 Miss Match password 반환
const {id, userName, salt, password :hashedPassword} = alreadyEmail;
const inputPassword = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64');
if (inputPassword !== hashedPassword){
console.log('비밀번호가 일치하지 않습니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.OK, responseMessage.MISS_MATCH_PW));
}
// 5. status: 200 ,message: SIGN_IN_SUCCESS, data: id, email, userName 반환
return res.status(statusCode.OK).send(util.success(statusCode.OK,responseMessage.SIGN_IN_SUCCESS,{id, email, userName}));
} catch(error){
console.error(error);
return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR,responseMessage.SIGN_IN_FAIL));
}
})
성공
router.get('/', async (req, res) => {
//1. 모든 사용자 정보 (id, email, userName ) 리스폰스!
// status: 200, message: READ_USER_ALL_SUCCESS, data: id, email, userName 반환
try {
const users = await User.findAll({
attributes : ['id', 'email', 'userName'],
});
console.log(users);
return res.status(statusCode.OK).send(util.success(statusCode.OK,responseMessage.READ_USER_ALL_SUCCESS,users));
} catch(error){
console.error(error);
return res.status(statusCode.INTERNAL_SERVER_ERROR).send(statusCode.INTERNAL_SERVER_ERROR,responseMessage.READ_USER_ALL_FAIL);
}
})
router.get('/:id', async (req, res) => {
// 1. parameter로 id값을 받아온다! (id값은 인덱스값)
const {id} = req.params;
// 2. id값이 유효한지 체크! 존재하지 않는 아이디면 NO_USER 반환
try {
const user = await User.findOne({
where : {
id : id,
},
attributes : ['id','email','userName'],
});
if(!user){
console.log('존재하지 않는 아이디 입니다.');
return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST,responseMessage.NO_USER));
}
// 3. status:200 message: READ_USER_SUCCESS, id, email, userName 반환
return res.status(statusCode.OK).send(util.success(statusCode.OK,responseMessage.READ_USER_SUCCESS,user));
} catch(error){
console.error(error);
return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.READ_USER_ALL_FAIL));
}
})
시퀄라이즈 쿼리를 이용해서 CRUD 작업을 진행한다.
Action | SQL | Sequelize Query |
---|---|---|
생성 | INSERT | create |
조회 | SELECT | find |
수정 | UPDATE | update |
삭제 | DELETE | destroy |
Sequelize 객체 내부의 Op 객체를 불러와 특수한 연산 가능
where
옵션 안에서 사용
Model.update({/*수정할 내용*/}, {/*어떤 로우를 수정할지*/});
Model.destroy({/*삭제할 로우*/})
📚Sequelize 공식 문서📚
냥이에 이끌려 왔다 ㅠㅠ