7장. MySQL

My_Code·2024년 1월 23일

Node.js 교과서

목록 보기
7/11
post-thumbnail

다음 내용은 인프런에서 공부한 내용을 복습하는 차원에서 기록한 것입니다.
출처 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-js-%EA%B5%90%EA%B3%BC%EC%84%9C


💻 7.1 데이터베이스

📌 Express 소개

✏️ 지금까지는 데이터를 서버 메모리에 저장했음

  • 서버를 재시작하면 데이터도 사라져버림 -> 영구적으로 저장할 공간 필요

✏️ MySQL 관계형 데이터베이스 사용

  • 데이터베이스: 관련성을 가지며 중복이 없는 데이터들의 집합
  • DBMS: 데이터베이스를 관리하는 시스템
  • RDBMS: 관계형 데이터베이스를 관리하는 시스템
  • 서버의 하드 디스크나 SSD 등의 저장 매체에 데이터를 저장
  • 서버 종료 여부와 상관 없이 데이터를 계속 사용할 수 있음
  • 여러 사람이 동시에 접근할 수 있고, 권한을 따로 줄 수 있음
  • 그렇다고 모든 데이터를 데이터베이스에 저장하진 않음
  • 클라이언트에 저장해도 되는 데이터들은 클라이언트에 저장
  • 정형화되어 있고 관계가 있는 데이터를 관리할 때는 SQL 사용
  • 데이터 수집과 같은 데에 목적이 있을 때는 NoSQL 사용


💻 7.2 데이터베이스, 테이블 생성하기

📌 데이터베이스 생성하기

✏️ 콘솔에서 MySQL 프롬프트에 접속

  • CREATE SCHEMA nodejs;로 nodejs 데이터베이스 생성
    - Character set은 utf8mbt(이모티콘 지원)
    - Collate는 Character set을 어떻게 정렬할 지에 관한 것
  • use nodejs;로 생성한 데이터베이스 선택

📌 테이블 생성하기

✏️ MySQL 프롬프트에서 테이블 생성

  • CREATE TABLE [데이터베이스명.테이블명]으로 테이블 생성
  • 사용자 정보를 저장하는 테이블

✏️ comments 테이블 생성


📌 컬럼과 로우

✏️ 나이, 결혼 여부, 성별같은 정보가 컬럼

✏️ 실제로 들어가는 데이터는 로우


📌 컬럼 옵션들

✏️ id INT NOT NULL AUTO_INCREMENT

  • 컬럼명 옆의 것들은 컬럼에 대한 옵션들

  • INT: 정수 자료형(FLOAT, DOUBLE은 실수)

  • VARCHAR: 문자열 자료형, 가변 길이(CHAR은 고정 길이)

  • TEXT: 긴 문자열은 TEXT로 별도 저장

  • DATETIME: 날짜 자료형 저장

  • TINYINT: -128에서 127까지 저장하지만 여기서는 1 또는 0만 저장해 불 값 표현

  • NOT NULL: 빈 값은 받지 않는다는 뜻(NULL은 빈 값 허용)

  • AUTO_INCREMENT: 숫자 자료형인 경우 다음 로우가 저장될 때 자동으로 1 증가

  • UNSIGNED: 0과 양수만 허용

  • ZEROFILL: 숫자의 자리 수가 고정된 경우 빈 자리에 0을 넣음

  • DEFAULT now(): 날짜 컬럼의 기본값을 현재 시간으로


📌 컬럼 옵션들

✏️ PRIMARY KEY(id)

  • id가 테이블에서 로우를 특정할 수 있게 해주는 고유한 값임을 의미
  • 학번, 주민등록번호같은 개념

✏️ UNIQUE INDEX name_UNIQUE (name ASC)

  • 해당 컬럼(name)이 고유해야 함을 나타내는 옵션
  • 빠른 검색을 위해 INDEX 옵션을 사용
  • name_UNIQUE는 이 옵션의 이름(아무거나 다른 걸로 지어도 됨)
  • ASC는 인덱스를 오름차순으로 저장함의 의미(내림차순은 DESC)

📌 테이블 옵션

✏️ COMMENT: 테이블에 대한 보충 설명(필수 아님)

✏️ ENGINE: InnoDB 사용(이외에 MyISAM이 있음, 엔진별로 기능 차이 존재)


📌 테이블 생성되었나 확인하기

✏️ DESC 테이블명


📌 외래키

✏️ 댓글 테이블은 사용자 테이블과 관계가 있음(사용자가 댓글을 달기 때문)

  • 외래키를 두어 두 테이블이 관계가 있다는 것을 표시

  • FOREIGN KEY (컬럼명) REFERENCES 데이터베이스.테이블명 (컬럼)

  • FOREIGN KEY (commenter) REFERENCES nodejs.users (id)

  • 댓글 테이블에는 commenter 컬럼이 생기고 사용자 테이블의 id값이 저장됨

  • ON DELETE CASCADE, ON UPDATE CASCADE

  • 사용자 테이블의 로우가 지워지고 수정될 때 댓글 테이블의 연관된 로우들도 같이 지워지고 수정됨

  • 데이터를 일치시키기 위해 사용하는 옵션(CASCADE 대신 SET NULL과 NO ACTION도 있음)



💻 7.3 CRUD 작업하기

📌 CRUD

✏️ Create, Read, Update, Delete의 두문자어

  • 데이터베이스에서 많이 하는 작업 4가지

📌 Create

✏️ INSERT INTO 테이블 (컬럼명들) VALUES (값들)


📌 Read

✏️ SELECT 컬럼 FROM 테이블명

  • SELECT * 은 모든 컬럼을 선택한다는 의미
  • 컬럼만 따로 추리는 것도 가능

📌 Read 옵션들

✏️ WHERE로 조건을 주어 선택 가능

  • AND로 여러가지 조건을 동시에 만족하는 것을 찾음
  • 컬럼만 따로 추리는 것도 가능

📌 정렬해서 찾기

✏️ ORDER BY로 특정 컬럼 값 순서대로 정렬 가능

  • DESC는 내림차순, ASC 오름차순

📌 Update

✏️ 데이터베이스에 있는 데이터를 수정하는 작업

  • UPDATE 테이블명 SET 컬럼=새값 WHERE 조건

📌 Delete

✏️ 데이터베이스에 있는 데이터를 삭제하는 작업

  • DELETE FROM 테이블명 WHERE 조건


💻 7.4 시퀄라이즈 사용하기

📌 시퀄라이즈 ORM

✏️ SQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리

  • ORM: Object Relational Mapping: 객체와 데이터를 매핑(1대1 짝지음)
  • MySQL 외에도 다른 RDB(Maria, Postgre, SQLite, MSSQL)와도 호환됨
  • 자바스크립트 문법으로 데이터베이스 조작 가능

✏️ 시퀄라이즈 예제는 https://github.com/zerocho/nodejs-book/tree/master/ch7/7.6/learn-sequelize

  • 프로젝트 세팅 후, 콘솔을 통해 경로로 이동한 후 package.json 작성

📌 config.json 설정하기

✏️ DB 연결정보 넣기


📌 model/index.js

✏️ new Sequelize(옵션들…)로 DB와 연결 가능

const Sequelize = require('sequelize');
const User = require('./user');
const Comment = require('./comment');

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

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

db.sequelize = sequelize;

db.User = User;
db.Comment = Comment;

User.initiate(sequelize);
Comment.initiate(sequelize);

User.associate(db);
Comment.associate(db);

module.exports = db;

📌 MySQL 연결하기(app.js)

✏️ sequelize.sync로 연결

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const nunjucks = require('nunjucks');

const { sequelize } = require('./models');
const indexRouter = require('./routes');
const usersRouter = require('./routes/users');
const commentsRouter = require('./routes/comments');

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('/', indexRouter);
app.use('/users', usersRouter);
app.use('/comments', commentsRouter);

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'), '번 포트에서 대기 중');
});

📌 유저 모델 생성하기(user.js)

✏️ 테이블에 대응되는 시퀄라이즈 모델 생성

  • static initiate가 모델을 시퀄라이즈와 연결
  • static associate는 모델 간 관계 설정
const Sequelize = require('sequelize');

class User extends Sequelize.Model {
  static initiate(sequelize) {
    User.init({
      name: {
        type: Sequelize.STRING(20),
        allowNull: false,  //Not NULL
        unique: true,
      },
      age: {
        type: Sequelize.INTEGER.UNSIGNED,
        allowNull: false,
      },
      married: {
        type: Sequelize.BOOLEAN,  //true false
        allowNull: false,
      },
      comment: {
        type: Sequelize.TEXT,
        allowNull: true,
      },
      created_at: {
        type: Sequelize.DATE,  //DATETIME, MySQL DATE => Sequelize DateOnly
        allowNull: false,
        defaultValue: Sequelize.NOW,
      },
    }, {
      sequelize,
      timestamps: false,  //createAt, updateAt 활성화 유무
      underscored: false,  //자동으로 만들어지는 객체명에 _ 넣을지 유무
      modelName: 'User',
      tableName: 'users',
      paranoid: false,
      charset: 'utf8',
      collate: 'utf8_general_ci',
    });
  }

  static associate(db) {
    db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
  }
};

module.exports = User;

📌 모델 옵션들

✏️ define 메서드의 세 번째 인자는 테이블 옵션

  • timestamps: true면 createdAt(생성 시간), updatedAt(수정 시간) 컬럼을 자동으로 만듦
    - 예제에서는 직접 created_at 컬럼을 만들었으므로 false로 함
  • paranoid 옵션은 true면 deletedAt(삭제 시간) 컬럼을 만듦, 로우 복구를 위해 완전히 삭제하지 않고 deletedAt에 표시해둠
  • underscored 옵션은 캐멀케이스로 생성되는 컬럼을 스네이크케이스로 생성
  • modelName은 모델 이름, tableName 옵션은 테이블 이름을 설정
  • charset과 collate는 한글 설정을 위해 필요(이모티콘 넣으려면 utf8mb4로)

📌 댓글 모델 생성하기(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;

📌 유저 및 댓글 모델 활성화하기(index.js)

✏️ index.js에 모델 연결

  • initiate으로 sequelize와 연결
  • associate로 관계 설정
const Sequelize = require('sequelize');
const User = require('./user');
const Comment = require('./comment');

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

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

db.sequelize = sequelize;

db.User = User;
db.Comment = Comment;

User.initiate(sequelize);
Comment.initiate(sequelize);

User.associate(db);
Comment.associate(db);

module.exports = db;

📌 관계 정의하기

✏️ users 모델과 comments 모델 간의 관계를 정의

  • 1:N 관계 (사용자 한 명이 댓글 여러 개 작성)
  • 시퀄라이즈에서는 1:N 관계를 hasMany로 표현(사용자.hasMany(댓글))
    - 영어 그대로 '사용자가 많은 댓글들을 가진다'
  • 반대의 입장에서는 belongsTo(댓글.belongsTo(사용자))
    - 영어 그대로 '댓글은 사용자에게 속한다'
  • belongsTo가 있는 테이블에 컬럼이 생김(댓글 테이블에 commenter 컬럼)
  • User 모델(User.js)에서 Comment 모델과 연결시키는 부분
  static associate(db) {
    // User는 다수의 Comment를 가지는데
    // 외부키는 Comment의 commenter라는 컬럼을 사용하고 User의 id 컬럼을 참조한다는 의미
    // foreignKey는 User나 Comment나 같은 것을 사용
    // 하지만 commenter라는 컬럼은 Comment쪽에 생성됨 (belongsTo 쪽에 컬럼이 추가됨)
    db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
  }
  • Comment 모델(Comment.js)에서 User 모델과 연결시키는 부분
  static associate(db) {
    // Comment는 User에 속하는데
    // 외부키로 commenter를 사용하고 User의 id 컬럼을 참조한다는 의미
    // foreignKey는 User나 Comment나 같은 것을 사용
    // 하지만 commenter라는 컬럼은 Comment쪽에 생성됨 (belongsTo 쪽에 컬럼이 추가됨)
    db.Comment.belongsTo(db.User, { foreignKey: 'commenter', targetKey: 'id' });
  }

📌 1대1 관계

✏️ 1대1 관계

User.hashOne(db.Info { foreignKey: "UserId", sourceKey: "id" });
Info.belongsTo(db.User { foreignKey: "UserId", targetKey: "id" });

📌 N대M 관계

✏️ 다대다 관계

  • 예) 게시글과 해시태그 테이블
  • 하나의 게시글이 여러 개의 해시태그를 가질 수 있고 하나의 해시태그가 여러 개의 게시글을 가질 수 있음
  • DB 특성상 다대다 관계는 중간 테이블이 생김
db.Post.belongsToMany(db.Hashtag, { through: "PostHashTag" });
db.Hashtag.belongsToMany(db.Post, { through: "PostHashTag" });
  • 중간 테이블을 사용하지 않고 각 테이블에서 각각에 해당하는 id값들을 넣지 않는 이유는 정규화에서 의해서 단일 값을 사용하기 때문이다.

📌 시퀄라이즈 쿼리

✏️ 윗 줄이 SQL문, 아랫 줄은 시퀄라이즈 쿼리(자바스크립트)

  • 특수한 기능들인 경우 Sequelize.Op의 연산자 사용(gt, or 등)
  • ORDER BY가 2차원 배열인 이유는 정렬할 때 같은 값이면 또다른 조건으로 정렬을 구분하기 위해서
  • EX) [[age, DESC], [createAt, ASC]]
  • 수정
  • 삭제
// Id가 1, 3, 5인 user를 삭제하라는 의미
User.destroy({
  where: { id: { [Op.in]: [1, 3, 5] } }
});

// Id가 5가 아닌 user들을 전부 삭제하라는 의미
User.destroy({
  where: { id: { [Op.ne]: 5 } }
});

📌 관계 쿼리

✏️ 결괏값이 자바스크립트 객체임

const user = await User.findOne({...});
console.log(user.nick); // 사용자 닉네임

✏️ include로 JOIN 과 비슷한 기능 수행 가능(관계 있는 것 엮을 수 있음)

  • 어떤 User의 댓글을 전부 가져오라는 의미(Comment 모델을 통해서)
const user = await User.findOne({
	include: [{
    	model: Comment,
    }]
});
console.log(user.Comments); // 사용자 댓글

✏️ 다대다 모델은 다음과 같이 접근 가능

db.sequelize.models.PostHashtag

✏️ get+모델명으로 관계 있는 데이터 로딩 가능

const user = await User.findOne({ ... });
const comments = await user.getComments();
console.log(comments);  // 사용자 댓글 출력

✏️ as로 모델명 변경 가능

  • 이 기능같은 경우는 헷갈리는 경우가 많으므로 주의 요망
// 관계를 설정할 때 as로 등록
db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id', as: 'Answers' });

// 쿼리할 때는
const user = await User.findOne({ ... });
const comments = await user.getAnswers();
console.log(comments);

✏️ include나 관계 쿼리 메서드에도 where나 attributes

  • 어떤 User의 댓글을 가져오는데, 전부 다 가져오지 말고 id가 1인 댓글들의 id 컬럼을 가져오라는 의미

  • 만약 id가 1인 User의 댓글들을 가져오고 싶을 때는 include 밖에서 where을 사용해 가져와야 함

const user = await User.findOne({
	include: [{
    	model: Comment,
      	attributes: ['id'],
      	where: {
        	id: 1,
        },
    }]
});
// 또는
const comments = await user.getComments({
	where: {
    	id: 1,
    },
  	attributes: ['id'],
});

✏️ 생성 쿼리

const user = await User.findOne({ ... });
const comment = await Comment.create( ... );
await user.addComment(comment);
// 또는
await user.addComment(comment.id);

✏️ 여러 개를 추가할 때는 배열로 추가 가능

const user = await User.findOne({});
const comment1 = await Comment.create();
const comment2 = await Comment.create();
await user.addComment([comment1, comment2]);

✏️ 수정은 set+모델명, 삭제는 remove+모델명


📌 raw 쿼리

✏️ 직접 SQL을 쓸 수 있음

const [result, metadata] = await sequelize.query('SELECT * from comments');
console.log(result);

📌 서버 접속하기

✏️ npm start로 서버 시작

  • localhost:3001 으로 접속하면 시퀄라이즈가 수행하는 SQL문이 콘솔에 찍힘

✏️ 글 등록/수정/삭제 해보기

profile
조금씩 정리하자!!!

0개의 댓글