PS C:\Program Files\MySQL\MySQL Server 8.2\bin> 경로로 이동
책의 경우 콘솔에 mysql -h localhost -u root -p를 입력하라 하였으나 ./mysql -h localhost -u root -p를 입력해야 제대로 로그인이 됐다.
- mysql -h 뒤에는 접속할 주소
- -u 뒤에는 사용자 이름
-p는 비밀번호를 사용하겠다는 뜻
프롬프트가 mysql>로 바뀌었다면 접속된 것이고, 다시 콘솔로 돌아가려면 exit 명령어를 입력한다.
데이터베이스를 생성하는 명령어이며 가급적 예약어는 대문자로 쓰는 것이 좋다,
데이터베이스와 스키마는 같은 개념이다.
CREATE SCHEMA [데이터베이스명]
CREATE SCHEMA `nodejs` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;
nodejs라는 이름의 데이터베이스를 생성한 다음, use nodejs; 명령어를 추가로 입력해 앞으로 nodejs 데이터베이스를 사용하겠다는 것을 MySQL에 알린다.
CREATE SCHEMA 뒤에 DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci를 붙여 한글과 이모티콘을 사용할 수 있게 만든다.
SQL 구문을 입력할 때는 마지막에 세미콜론(;)을 붙여야 해당 구문이 실행되며, 세미클론을 붙이지 않으면 프롬프트가 다음 줄로 넘어가서 다른 입력이 들어오기를 기다린다.
테이블이란 데이터가 들어갈 수 있는 틀을 의미하며, 테이블에 맞는 데이터만 들어갈 수 있다.
CREATE TABLE nodejs.users (
-> id INT NOT NULL AUTO_INCREMENT,
-> name VARCHAR(20) NOT NULL,
-> age INT UNSIGNED NOT NULL,
-> married TINYINT NOT NULL,
-> comment TEXT NULL,
-> created_at DATETIME NOT NULL DEFAULT now(),
-> PRIMARY KEY(id),
-> UNIQUE INDEX name_UNIQUE (name ASC))
-> COMMENT = '사용자 정보'
-> ENGINE = InnoDB;
세미콜론(;)을 입력하기 전까지는 실행되지 않기 때문에 엔터를 누른 후 줄을 바꿔서 입력하는 것이 좋다.
- CREATE TABLE [데이터베이스명.테이블명]은 테이블을 생성하는 명령어, use nodejs; 명령어를 실행했으니 데이터베이스명은 생략해도 된다.
- 그 아래에는 한 줄에 하나씩 콤마(,)로 구분해 컬럼들을 만들었다.
- id, name, age, married로 시작되는 세로줄을 컬럼(column)이라고 하고, 미리 column을 정의해 두고 컬럼에 맞춰 데이터를 넣으면 된다.
- 각각의 컬럼 이름 옆에는 INT, VARCHAR, TINYINT, TEXT, DATETIME 등이 적혀 있습니다. 이는 컬럼의 자료형을 뜻한다.
<컬럼>
• INT : 정수
• VARCHAR(자릿수) : 가변 고정 길이, 수백자 이내의 문자열
• TEXT는 긴 글을 저장할 때 사용, 수백자보다 긴 경우 사용
• TINYINT는 -128부터 127까지의 정수를 저장할 때 사용
• DATETIME은 날짜와 시간에 대한 정보를 담음
<옵션>
• NULL과 NOT NULL은 빈칸을 허용할지 여부를 묻는 옵션
• AUTO_INCREMENT은 숫자를 저절로 올리겠다는 뜻 (id값 자동 1 증가)
• UNSIGNED는 숫자 자료형에 적용되는 옵션
• ZEROFILL은 숫자의 자릿수가 고정되어 있을 때 사용할 수 있음
• created_at에는 DEFAULT now()라는 옵션이 있는데, 데이터베이스 저장 시 해당 칼럼에 값이 없을 때 MySQL이 대신 값을 넣음
• 해당 컬럼이 기본 키인 경우에 PRIMARY KEY 옵션을 설정 (id)
• UNIQUE INDEX는 해당 값이 고유해야 하는지에 대한 옵션(이름 값)
• COMMENT는 테이블에 대한 보충 설명
• ENGINE은 MyISAM과 InnoDB가 제일 많이 사용됨
DROP TABLE users;
테이블을 잘못 만들었을 경우 DROP TABLE [테이블명] 명령어를 입력하면 제거되고, 제거 후 다시 테이블을 생성하면 된다.
• PK : 기본 키 여부를 체크
• NN : 빈칸을 허용할지 여부
• UQ : UNIQUE INDEX
• UN : UNSIGNED
• ZF : ZEROFILL
• Default/Expression : 기본 값 설정
CREATE TABLE nodejs.comments (
-> id INT NOT NULL AUTO_INCREMENT,
-> commenter INT NOT NULL,
-> comment VARCHAR(100) NOT NULL,
-> created_at DATETIME NOT NULL DEFAULT now(),
-> PRIMARY KEY(id),
-> INDEX commenter_idx (commenter ASC),
-> CONSTRAINT commenter
-> FOREIGN KEY (commenter) # commenter 컬럼에는 댓글을 작성한 사용자의 id를 저장
-> REFERENCES nodejs.users (id)
-> ON DELETE CASCADE # ON UPDATE와 ON DELETE는 모두 CASCADE로 설정
-> ON UPDATE CASCADE)
-> COMMENT = '댓글'
-> ENGINE=InnoDB;
사용자의 댓글을 저장하는 테이블
comments 테이블에서는 commenter 컬럼과 users 테이블의 id 컬럼을 연결했다
=> 다른 테이블의 기본 키를 저장하는 컬럼을 외래 키(foreign key)라고 한다.
CONSTRAINT [제약조건명] FOREIGN KEY [컬럼명] REFERENCES [참고하는 컬럼명]
- CASCADE : 사용자 정보가 수정되거나 삭제되면 그것과 연결된 댓글 정보도 같이 수정하거나 삭제한다는 뜻
SHOW TABLES;
데이터를 생성해서 데이터베이스에 넣는 작업이다.
INSERT INTO [테이블명] ([컬럼1], [컬럼2], .. .) VALUES ([값1], [값 2], ...)
# INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');
# INSERT INTO nodejs.comments (commenter, comment) VALUES (1, '안녕하세요. zero의 댓글입니다');
데이터베이스에 있는 데이터를 조회하는 작업이다.
SELECT * FROM [테이블명]
# SELECT * FROM nodejs.users;
# SELECT * FROM nodejs.comments;
특정 컬럼만 조회하기 : *을 빼고 조회를 원하는 컬럼을 SELECT 다음에 넣으면 됨
SELECT ([컬럼1], [컬럼2], .. .) FROM [테이블명]
# SELECT name, married FROM nodejs.users; -> name과 married컬럼만 조회
특정 조건을 가진 데이터만 조회하기 : WHERE 절 사용
SELECT ([컬럼1], [컬럼2], .. .) FROM [테이블명] WHERE (조건문)
# 1 ) SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;
# 2 ) SELECT id, name FROM nodejs.users WHERE married = 0 OR age > 30;
1. 결혼을 했고 나이가 30세 이상인 사용자를 조회 (AND)
2. 결혼을 하지 않았거나 나이가 30세 이상인 사용자를 조회 (OR)
특정 컬럼을 기준으로 정렬하기 : ORDER BY [컬럼명] [ASC|DESC] 키워드를 사용
DESC는 내림차순, ASC는 오름차순이다.
SELECT id, name FROM nodejs.users ORDER BY age DESC; # 나이가 많은 순서대로 정렬
# ORDER BY [컬럼명] [ASC|DESC]
조회할 로우 개수를 설정하기 : LIMIT [숫자] 키워드 사용
SELECT id, name FROM nodejs.users ORDER BY age DESC LIMIT 1; # 하나만 조회
# LIMIT [숫자]
로우 개수를 설정하면서 몇 개를 건너뛸지 설정하기 : OFFSET [건너뛸 숫자] 키워드 사용
게시판 등의 페이지네이션 기능을 구현할 때 유용
SELECT id, name FROM nodejs.users ORDER BY age DESC LIMIT 1 OFFSET 1;
# OFFSET [건너뛸 숫자]
데이터베이스에 있는 데이터를 수정하는 작업이다.
UPDATE [테이블명] SET [컬럼명=바꿀 값] WHERE [조건]
# UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;
데이터베이스에 있는 데이터를 삭제하는 작업이다.
DELETE FROM [테이블명] WHERE [조건]
# DELETE FROM nodejs.users WHERE id = 2;
MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리이다.
시퀄라이즈는 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구인 ORM(Object-relational Mapping)으로 분류된다.
자바스크립트 구문을 알아서 SQL로 바꿔준다. -> SQL 언어를 직접 사용하지 않고도 자바스크립트만으로 MySQL을 조작 가능하다.
시퀄라이즈에 필요한 sequelize와 sequelize-cli, mysql2 패키지를 설치
$ npm i express morgan nunjucks sequelize sequelize-cli mysql2
$ npm i -D nodemon
sequelize-cli는 시퀄라이즈 명령어를 실행하기 위한 패키지이고, mysql2는 MySQL과 시퀄라이즈를 이어주는 드라이버이다.
$ npx sequelize init
설치 완료 후 sequelize init 명령어를 호출, 전역 설치 없이 명령어로 사용하도록 npx를 붙였다.
시퀄라이즈를 통해 익스프레스 앱과 MySQL을 연결해야 한다.
// app.js
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');
const { sequelize } = require('./models'); // require('./models/index.js')과 같음
const app = express();
app.set('port', process.env.PORT || 3001);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
sequelize.sync({ force: false })
.then(() => {
console.log('데이터베이스 연결 성공');
})
.catch((err) => {
console.error(err);
});
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error');
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중');
});
config/config.json 자신의 mysql에 맞게 수정
{
"development": {
"username": "root",
"password": "[root 비밀번호]",
"database": "nodejs",
"host": "127.0.0.1",
"dialect": "mysql"
},
...
}
연결이 잘 됐다면 라우터가 없어 포트에 접속할 수는 없지만, 데이터베이스에 연결이 성공됐다는 로그가 출력된다.
MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의해야 한다.
=> User와 Comment 모델을 만들어 users 테이블과 comments 테이블에 연결해야 한다.
models/user.js 생성
const Sequelize = require('sequelize');
class User extends Sequelize.Model { // User 모델은 Sequelize.Model모델을 확장한 클래스로 선언
static initiate(sequelize) { // static initiate 메서드에는 테이블에 대한 설정을 함
User.init({
name: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
},
age: {
type: Sequelize.INTEGER.UNSIGNED,
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,
timestamps: false,
underscored: false,
modelName: 'User',
tableName: 'users',
paranoid: false,
charset: 'utf8',
collate: 'utf8_general_ci',
});
}
static associate(db) {} // static associate 메서드에는 다른 모델들에 대한 관계를 적음
};
module.exports = User; // User 모델을 만들고 모듈로 export함
1. 모델.init 메서드의 첫 번째 인수는 테이블 컬럼에 대한 설정이다.
시퀄라이즈는 알아서 id를 기본 키로 연결하므로 id 컬럼은 적어줄 필요가 없다.
시퀄라이즈는 MySQL 외의 다른 데이터베이스도 처리할 수 있어야 하기 때문에 MySQL과 지정하는 값이 다르다.
- VARCHAR는 STRING
- INT는 INTEGER
- TINYINT는 BOOLEAN
- DATETIME은 DATE
- INTEGER.UNSIGNED은 UNSIGNED 옵션이 적용된 INT이다.
- ZEROFILL을 적용하고 싶다면 INTEGER.UNSIGNED.ZEROFILL를 적어야 한다.
- allowNull은 NOT NULL
- unique는 UNIQUE 옵션
- defaultValue는 기본값(DEFAULT)을 의미, Sequelize.NOW로 현재 시간을 기본값으로 사용
-2. 모델.init 메서드의 두 번째 인수는 테이블 옵션이다.
• sequelize: static initiate 메서드의 매개변수와 연결되는 옵션
• timestamps: 현재 값이 false로 되어 있다. timestamps 속성이 true이면 시퀄라이즈는 createdAt과 updatedAt 컬럼을 추가한다.
• underscored: 시퀄라이즈는 기본적으로 테이블명과 컬럼명을 캐멀 케이스(camel case)(예시: createdAt)로 만든다.
• modelName: 모델 이름을 설정할 수 있다.
• tableName: 실제 데이터베이스의 테이블 이름이 된다.
• paranoid: true로 설정하면 deletedAt이라는 컬럼이 생긴다.
• charset과 collate: 각각 utf8과 utf8_general_ci로 설정해야 한글이 입력된다.
models/comment.js 추가
const Sequelize = require('sequelize');
class Comment extends Sequelize.Model {
static initiate(sequelize) {
Comment.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' });
}
};
module.exports = Comment;
모델을 생성한 후 models/index.js와 연결
db.User = User;
db.Comment = Comment;
User.initiate(sequelize);
Comment.initiate(sequelize);
// 각각의 모델의 static initiate 메서드를 호출하는 것
User.associate(db);
Comment.associate(db);
db라는 객체에 User와 Comment 모델을 담아두었기 때문에 db 객체를 require해서 User와 Comment 모델에 접근할 수 있다.
모델.init이 실행되어야 테이블이 모델로 연결되기 때문에 다른 테이블과의 관계를 연결하는 static associate 메서드도 미리 실행해 둔다.
시퀄라이즈에서는 1:N 관계를 hasMany라는 메서드로 표현한다.
users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들도 같이 불러올 수 있다.
// models.user.js
static associate(db) {
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
}
// models/comment.js
static associate(db) {
db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
}
순환 참조를 방지하기 위해 index.js에서 각 모델을 불러와 db 매개변수로 넘기는 방식을 이용했다.
시퀄라이즈는 위에서 정의한 대로 모델 간 관계를 파악해서 Comment 모델에 foreignKey(외래 키)인 commenter 컬럼을 추가한다.
hasMany 메서드에서는 sourceKey 속성에 id를 넣고, belongsTo 메서드에서는 targetKey 속성에 id를 넣는다.
- sourceKey의 id와 targetKey의 id 모두 User 모델의 id
foreignKey를 따로 지정하지 않는다면, 이름이 ‘모델명+기본 키’인 컬럼이 모델에 생성된다.
belongsToMany 메서드를 이용하여 정의할 수 있다.
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
양쪽 모델에 모두 belongsToMany 메서드를 사용하며, N:M 관계 특성상 새로운 모델이 생성된다.
through 속성에 그 이름을 적으면 된다.
로우를 생성
// INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');
const { User } = require('../models');
User.create({
name: 'zero',
age: 24,
married: false,
comment: '자기소개1',
});
models 모듈에서 User 모델을 불러와 create 메서드를 사용하면 된다.
- 데이터를 넣을 때 MySQL의 자료형이 아니라 시퀄라이즈 모델에 정의한 자료형대로 넣어야 한다
- 퀄라이즈가 알아서 MySQL 자료형으로 바꾼다.
데이터 조회 - findAll() 메서드 사용
// SELECT * FROM nodejs.users;
User.findAll({});
데이터를 하나만 조회 - findOne()
// SELECT * FROM nodejs.users LIMIT 1;
User.findOne({});
원하는 컬럼 가져오기 - attributes 옵션
// SELECT name, married FROM nodejs.users;
User.findAll({
attributes: ['name', 'married'],
});
- 조건들을 나열 - where 옵션
// SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;
const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
attributes: ['name', 'age'],
where: {
married: true,
age: { [Op.gt]: 30 },
},
});
MySQL에서는 undefined라는 자료형을 지원하지 않으므로 where 옵션에는 undefined가 들어가면 안 된다.
시퀄라이즈는 자바스크립트 객체를 사용해서 쿼리를 생성해야 하므로 Op.gt 같은 특수한 연산자들이 사용된다.
- Op.gt(초과)
- Op.gte(이상)
- Op.lt(미만)
- Op.lte(이하)
- Op.ne(같지 않음 )
- Op.or(또는)
시퀼라이즈의 정렬은 order 옵션을 이용한다.
정렬을 컬럼 두 개 이상으로 할 수 있기 때문에 배열 안에 배열이 있다는 점에 주의해야 한다.
SELECT id, name FROM users ORDER BY age DESC;
User.findAll({
attributes: ['id', 'name'],
order: [['age', 'DESC']],
});
LIMIT 1은 limit 옵션을 이용하고, OFFSET을 offset 속성을 이용해 조회할 로우 개수를 설정할 수 있다.
SELECT id, name FROM users ORDER BY age DESC LIMIT 1 OFFSET 1;
User.findAll({
attributes: ['id', 'name'],
order: ['age', 'DESC'],
limit: 1,
offset: 1,
});
update 메서드로 로우를 수정한다.
첫 번째 인수는 수정할 내용이고, 두 번째 인수는 where 옵션에 어떤 로우를 수정할지에 대한 조건을 적는다.
UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;
User.update({
comment: '바꿀 내용',
}, {
where: { id: 2 },
});
destroy 메서드로 로우를 삭제한다.
DELETE FROM nodejs.users WHERE id = 2;
User.destory({
where: { id: 2 },
});
User 모델이 Comment 모델과 hasMany-belongsTo 관계가 맺어진 상황에서 만약 특정 사용자를 가져오면서 그 사람의 댓글까지 모두 가져오고 싶다면 include 속성을 사용한다.
const user = await User.findOne({
include: [{
model: Comment,
}]
});
console.log(user.Comments); // 사용자 댓글
다양한 모델과 관계가 있을 수 있기 때문에 어떤 모델과 관계가 있는지를 include `배열`에 넣어준다.
댓글은 여러 개일 수 있으므로 user.Comments로 접근 가능하다.
const user = await User.findOne({});
const comments = await user.getComments();
console.log(comments); // 사용자 댓글
include 외 이러한 getComments()으로도 댓글에 접근할 수 있다.
getComments(조회) 외에도 setComments(수정), addComment(하나 생성), addComments(여러 개 생성), removeComments(삭제) 메서드를 지원한다.
const user = await User.findOne({
include: [{
model: Comment,
where: {
id: 1,
},
attributes: ['id'],
}]
});
// 또는
const comments = await user.getComments({ // 댓글을 가져올 때
where: {
id: 1, // id가 1인 댓글을 가져오고
},
attributes: ['id'], // 컬럼도 id 컬럼만 가져온다
});
include나 관계 쿼리 메서드에도 where나 attributes 같은 옵션을 사용할 수 있다.
const user = await User.findOne({});
const comment = await Comment.create();
await user.addComment(comment); // 추가할 댓글 모델
// 또는
await user.addComment(comment.id); //추가할 댓글의 id
await user.addComment([comment1, comment2]); // 여러개를 추가할 경우 배열로 추가
수정, 삭제, 생성의 경우 관계 쿼리 메서드의 인수로 추가할 댓글 모델을 넣거나 댓글의 아이디를 넣으면 된다.
빈칸 채우기 문제 (빈칸을 드래그해서 답을 확인해 보세요)
1. nodejs라는 이름의 데이터베이스를 생성한 다음, (use nodejs;) 명령어를 추가로 입력해 앞으로 nodejs 데이터베이스를 사용하겠다는 것을 MySQL에 알릴 수 있다.
2. users 테이블의 모든 데이터를 조회하는 SQL 문은 (SELECT) * (FROM) nodejs.users; 이다.
3. (시퀄라이즈)는 모델과 MySQL의 테이블을 연결해 주는 역할을 한다.
4. 시퀄라이즈에서는 1:N 관계를 (hasMany)라는 메서드로 표현한다.
5. 서로가 서로를 require하는 방식을 (순환 참조)라고 한다.
6. 1:1 관계에서는 (hasOne) 메서드를 사용한다.
7. 데이터를 하나만 가져올 때는 (findOne) 메서드를, 여러 개 가져올 때는 (findAll) 메서드를 사용한다.
8. 다른 테이블의 기본 키를 저장하는 컬럼을 (외래 키)라고 한다.
코드 문제
1. 시퀼라이즈 모델에 name은 Sequelize.STRING(20), age는 Sequelize.INTEGER.UNSIGNED, married는 Sequelize.BOOLEAN, comment는 Sequelize.TEXT로 정의한 경우, 첫 줄의 SQL문과 동일한 시퀼라이즈 쿼리는?
INSERT INTO nodejs.users (name, age, married, comment) VALUES ('corner', 20, 0, '안녕하세요');
const { User } = require('../models');
// 여기에 코드 작성
출처: 조현영 , 『Node.js 교과서』 개정판 3판, 길벗, 7장
User.create({
name: 'corner',
age: 20,
married: false,
comment: '안녕하세요1',
});
2. 특정 사용자를 가져오면서 그 사람의 댓글까지 모두 가져오고 싶을 때 빈칸의 내용은? (include 사용)
const user = await User.findOne({
// 여기에 코드 작성
console.log(user.Comments); // 사용자 댓글
include: [{
model: Comment,
}]
});
[Node.js 1] 9장 익스프레스로 SNS 서비스 만들기 (1) | 2023.12.22 |
---|---|
[노드 1팀] 8장. 몽고디비 (1) | 2023.12.01 |
[Node.js 1] 6장 익스프레스 웹 서버 만들기 (0) | 2023.11.17 |
[노드1] 4장~5장 http 모듈로 서버 만들기 & 패키지 매니저 (0) | 2023.11.10 |
[Node.js 1] 노드 기능 알아보기 (2) (1) | 2023.11.03 |