1) Node.js
1-1) MySQL 제약조건
1-2) Foreign Key(외래키) 추가 방법
1-3) JOIN
1-4) sequelize를 활용한 코드 구현
1) Node.js
1-1) MySQL 제약조건
기본적으로 제약조건은 세가지가 있는데 "기본키", "고유키", "외래키"이다!!
(1) 기본키(Primary Key)
- 중복되지 않는 고유값
- Null을 허용하지 않음
- 테이블 당 하나의 기본키만 지정 가능함
(2) 고유키(Unique)
- 중복되지 않는 고유값
- Null을 허용함
(3) 외래키(Foreign Key)
자식 테이블에게 field 하나를 추가하는데 이때 추가하는 field는 부모 테이블의 고유한 식별자(Foreign Key)를 추가하면 됨
1:1
--> User 테이블의 데이터 중 회원들로부터 꼭 받지 않아도 되는 데이터의 경우,
따로 User 상세 테이블을 생성하여 데이터가 있는 경우에만 데이터가 쌓이도록 함
(이에 대한 대안으로 Null 값을 허용하는 경우에는 쓸데없이 데이터가 차지하는 문제가 있고,
Null 값을 허용하지 않는 경우에는 회원들로부터 반드시 해당 데이터를 받을 수밖에 없는 문제가 발생함)
1:N(1: 부모 테이블, N: 자식 테이블)
--> "1:N"의 경우, 자식 테이블에 부모 테이블의 고유한 식별자 값(Foreign Key)을 추가해야 한다!
--> 대부분의 경우, 1:N 관계로 처리가 됨!
N:M
--> 해시태그가 좋은 예시이다!
(TIP) DB 스키마 설계 시 테이블 당 field 수(Column 갯수)는 가능한 적게하되
field 별로 그 아래 쌓일 Record 혹은 데이터는 많은 것이 좋다!
ERD --> DB 스키마 설계
ERD(Entity Relationship Diagram) 정의
개체-관계 모델로 테이블 간의 관계를 설명해주는 다이어그램이라고 볼 수 있으며,
이를 통해 프로젝트에서 사용되는 DB의 구조를 한눈에 파악할 수 있다!
즉, API를 효율적으로 뽑아내기 위한 모델 구조도라고 생각하면 된다!
(1) One
- 일대일 혹은 일대다 관계로서 주로 하나의 외래키(Foreign Key)가 걸린 관계라고 보면 됨
(2) Many
- 다대다 관계로 중계 테이블을 통하여 여러 개의 데이터를 바라보고 있을 때 사용함
(3) One(and only one)
- 위의 조건과 동일하게 1:1 관계이나, 하나의 row 끼리만 연결된 데이터임
(4) Zero or one
- 1:1 혹은 1:N 관계를 가지고 있으나, 필수 조건이 아님을 의미함
(5) One or many
- 1:1 혹은 다대다 관계를 가지고 있음을 의미함
- 관계를 가지고 있으나, 참조되는 row 값들이 불명확함을 의미함
(6) Zero or many
- 참조하는 테이블과의 관계가 불명확한 경우임
- 장바구니처럼 row 생성값이 없을 수도, 하나일 수도, 여러 개일 수도 있는 경우임
1-2) Foreign Key(외래키) 추가 방법
테이블 생성
$ npm init -y
$ npm install mysql2 sequelize
디렉토리 구조
| -- models
| ---- index.js
| ---- user.model.js
| ---- board.model.js
| -- config.js
외래키를 추가하는 방법
(1) SQL: 직접 MySQL에 접속하여 테이블을 설정한 다음 외래키를 설정함!
CREATE TABLE User(
userid VARCHAR(30) PRIMARY KEY,
userpw VARCHAR(64) NOT NULL,
username VARCHAR(20) NOT NULL
);
CREATE TABLE Board(
id INT PRIMARY KEY AUTO_INCREMENT,
subject VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
userid VARCHAR(30) NOT NULL,
registerDate DATETIME default now(),
hit INT default 0
);
(2) Sequelize
외래키 설정
ALTER TABLE `Board` <-- 자식 테이블
ADD CONSTRAINT fk_board_userid
FOREIGN KEY(userid) <-- 자식 테이블에서 부모 테이블의 식별자를 담는 필드명(자식 테이블의 외래키 혹은 FK)
REFERENCES User(userid);
-- ON DELETE
-- ON UPDATE
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
SELECT * FROM information_schema.table_constraints
WHERE TABLE_SCHEMA = 'sample' AND TABLE_NAME = 'Board';
ALTER TABLE `Board` DROP CONSTRAINT fk_board_userid;
제약조건을 사용하면 삭제하려는 것과 관련된 테이블의 내용을 한 번에 삭제할 수 있다!
ALTER TABLE `Board`
ADD CONSTRAINT fk_board_userid
FOREIGN KEY(userid)
REFERENCES User(userid)
ON DELETE CASCADE;
-- ON UPDATE
UPDATE Board SET userid = 'admin2' WHERE id = 6;
위와 같이 제약사항을 안 지우고 UPDATE 구문을 실행하면 아래와 같은 에러가 발생함!
ERROR 1452 (23000): Cannot add or update a child row: a foreign key
constraint fails (`sample`.`board`, CONSTRAINT `fk_board_userid` FOREIGN KEY (`userid`)
REFERENCES `user` (`userid`) ON DELETE CASCADE)
DELETE FROM User WHERE userid='hsb7722';
ALTER TABLE `Board`
ADD CONSTRAINT fk_board_userid
FOREIGN KEY(userid)
REFERENCES User(userid)
ON DELETE RESTRICT;
-- ON UPDATE
ALTER TABLE `Board`
ADD CONSTRAINT fk_board_userid
FOREIGN KEY(userid)
REFERENCES User(userid)
ON DELETE SET NULL;
-- ON UPDATE
자식 테이블에서 Foreign Key 내용이 NOT NULL이 되어서는 안 된다!
- CASCADE: 같이 삭제시킴
- RESTRICT: 관련된 Record가 있으면 아예 실행이 안 되게 함(삭제를 못하게 함)!
- SET NULL : 삭제하지 않고 Null로 바꿔줌(삭제하고 싶지 않을 때 사용하는 방법임)
제약사항은 테이블의 record를 넣기 전에 미리 다 구축해놓아야 한다!!
1-3) JOIN
SELECT * FROM Board as A JOIN User as B ON A.userid = B.userid;
SELECT A.id, A.subject, A.registerDate, A.hit, A.userid, B.username FROM Board as A JOIN User as B ON A.userid = B.userid;
SELECT A.id, A.subject, A.registerDate, A.hit, A.userid, B.username FROM Board as A LEFT OUTER JOIN User as B ON A.id = B.userid;
SELECT A.id, A.subject, A.registerDate, A.hit, A.userid, B.username FROM Board as A RIGHT OUTER JOIN User as B ON A.id = B.userid;
1-4) sequelize를 활용한 코드 구현
config.js
const config = {
db: {
username: "root",
password: "Hhsb4114!@",
host: "127.0.0.1",
port: "3306",
database: "sample",
dialect: "mysql",
define: {
freezeTableName: true,
timestamps: false,
},
},
};
module.exports = config;
models/index.js
const config = require("../config")["db"];
const Sequelize = require("sequelize");
const sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
// console.log(config);
// console.log(sequelize);
require("./user.model")(sequelize, Sequelize);
// console.log(sequelize.models); // { User: User }
require("./board.model")(sequelize, Sequelize);
const { models } = sequelize;
// models.User.associate(models);
// models.Board.associate(models);
for (const key in models) {
if (typeof models[key].associate !== "function") continue;
models[key].associate(models);
}
(async () => {
await sequelize.sync({ force: true });
const {
models: { User, Board },
} = sequelize;
await User.create({
userid: "hsb7722",
userpw: "7890",
username: "sangbeom",
});
await User.create({ userid: "admin", userpw: "7890", username: "관리자" });
await Board.create({
subject: "게시글1",
content: "내용~~!!",
userid: "hsb7722",
});
await Board.create({
subject: "게시글2",
content: "내용~~!!",
userid: "hsb7722",
});
await Board.create({
subject: "게시글3",
content: "내용~~!!",
userid: "hsb7722",
});
await Board.create({
subject: "게시글4",
content: "내용~~!!",
userid: "hsb7722",
});
await Board.create({
subject: "게시글5",
content: "내용~~!!",
userid: "admin",
});
await Board.create({
subject: "게시글6",
content: "내용~~!!",
userid: "admin",
});
const board = await Board.findAll({
raw: true,
nest: true,
include: [{ model: User }],
});
console.log(board);
})();
module.exports = {
sequelize,
Sequelize,
};
models/user.model.js
/*
CREATE TABLE `User`(
userid VARCHAR(30) PRIMARY KEY,
userpw VARCHAR(64) NOT NULL,
username VARCHAR(20) NOT NULL
);
*/
module.exports = (sequelize, Sequelize) => {
class User extends Sequelize.Model {
static initialize() {
return this.init(
{
userid: {
type: Sequelize.STRING(30),
primaryKey: true,
},
userpw: {
type: Sequelize.STRING(64), // 영어 기준으로 64글자를 허용한다는 의미
allowNull: false,
},
username: {
type: Sequelize.STRING(20),
allowNull: false,
},
},
{
sequelize,
}
);
}
static associate(models) {
this.hasMany(models.Board, {
foreignKey: "userid",
});
}
}
User.initialize();
return User;
};
models/board.model.js
/*
CREATE TABLE `Board`(
id INT PRIMARY KEY AUTO_INCREMENT,
subject VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
userid VARCHAR(30) NOT NULL,
registerDate DATETIME default now(),
hit INT default 0
);
*/
module.exports = (sequelize, Sequelize) => {
class Board extends Sequelize.Model {
static initialize() {
return this.init(
{
subject: {
type: Sequelize.STRING(100),
allowNull: false,
},
content: {
type: Sequelize.TEXT,
allowNull: false,
},
registerDate: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn("now"),
},
hit: {
type: Sequelize.INTEGER,
defaultValue: 0,
},
userid: {
type: Sequelize.STRING(30),
allowNull: true,
},
},
{
sequelize,
}
);
}
static associate(models) {
this.belongsTo(models.User, {
foreignKey: "userid",
});
}
}
Board.initialize();
return Board;
};