7. MySQL과 Sequelize

최상민·2023년 6월 4일
1

Node.js 교과서

목록 보기
7/9
post-thumbnail

RDBMS의 MySQL과 Sequelize에 대해 알아보자.

데이터베이스

MySQL을 사용하기전 데이터베이스가 무엇인지 먼저 알아보자.

우리는 보통 코드를 작성하고 간단한 데이터를 저장할 때 변수에 대입하고 컴퓨터 메모리에 저장하였다. 그래서 서버가 종료되면 메모리가 정리되어 저장했던 데이터가 사라지게 된다.

많은 데이터를 메모리가 아닌 데이터베이스 즉, 하드디스크나 SSD 등의 저장 매체에 데이터를 저장하여 서버 종료 여부와 상관없이 데이터를 계속 저장하고 사용할 수 있다.

데이터베이스란? 데이터베이스는 관련성을 가지며 중복이 없는 데이터들의 집합이다.

데이터베이스를 관리하는 시스템을 DBMS(데이터베이스 관리 시스템)이라고 하며 DBMS 중에서 RDMS라고 부르는 관계형 DBMS가 많이 사용된다. 대표적인 RDBMS로 Oracle, MySQL, MSSQL 등이 있으며, SQL이라는 언어를 사용해 데이터를 관리한다. RDBMS별로 SQL문이 조금씩 다르다.

우리는 RDBMS중 MySQL을 이용해보자.

MySQL

MySQL 공식 사이트인 https://dev.mysql.com/downloads/installer 에서 MySQL을 설치한다.

설치가 완료된 후 MySQL이 설치된 폴더로 이동한 후, 명령 프롬프트를 통해 MySQL에 접속한다. 설치할 때 따로 경로를 지정해 주지 않았으면 보통 C:\Program Files\MySQL\MySQL Server 8.0\bin에 설치된다.

$ mysql -h localhost -u root -p
Enter password: [비밀번호 입력]
mysql>

mysql -h 뒤에 접속할 주소, -u 뒤에 사용자 이름을 입력한다. localhost는 루프백 호스트명으로, 자신의 컴퓨터를 의미한다. 명령어를 입력하게 되면 비밀번호 입력하는 것이 나오는데 MySQL을 설치할 때 설정했던 비밀번호를 입력하면 된다.

프롬프트가 mysql>로 바뀌면 접속이 완료된 것이다! 앞으로 여기에 SQL 명령어를 입력하면 된다.

MySQL 워크벤치

콘솔은 데이터를 한눈에 보기 힘들기 때문에 워크벤치라는 프로그램을 사용하여 데이터베이스 내부에 저장된 데이터를 시각적으로 관리할 수 있어서 편리하다.

MySQL 공식 사이트인 https://dev.mysql.com/downloads/workbench 에서 설치할 수 있다.

나도 워크벤치가 편해서 대부분 워크벤치를 활용했지만 콘솔, CLI 환경에 익숙해지기 위해 콘솔로 작업하려 노력하고있다.

Sequelize

시퀄라이즈는 ORM(Object-relational Mapping)으로 분류된다. ORM은 Object와 relation 즉, 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.

시퀄라이즈는 MySQL 뿐만아니라 MariaDB, PostgreSQL, SQLite 등 다른 데이터베이스도 같이 쓸 수 있다.

시퀄라이즈를 쓰는 이유는 자바스크립트 구문을 알아서 SQL로 바꿔주기 때문에 SQL 언어를 직접 사용하지 않고도 자바스크립트만으로 MySQL을 조작할 수 있다.

$ npm i sequelize sequelize-cli mysql2

시퀄라이즈를 사용하기 위해 sequelize와 시퀄라이즈 명령어를 실행하기 위한 패키지인 sequelize-cli, 그리고 MySQL과 시퀄라이즈를 이어주는 드라이버 mysql2를 설치한다.

설치 후 sequelize init 명령어를 호출하면 config, models, migrations, seeders 폴더가 생성된다.

시퀄라이즈를 통해 익스프레스 앱과 MySQL을 연결하기 위해 models/index.js를 적절히 수정하고, 서버 코드에 연결 코드를 작성한다.

const { sequelize } = require('./models');

sequelize.sync({ force: false })
  .then(() => {
  	console.log('데이터베이스 연결 성공');
  })
  .catch((err) => {
  	console.error(err);
  });

models의 sequelize를 불러와 sync 메서드를 사용해 서버를 실행할 때 MySQL과 연동되도록 하였고, 내부에 force 옵션은 true로 설정하면 서버를 실행할 때마다 테이블을 재생성한다.

MySQL과 연동할 때는 config 폴더 안의 config.json 정보가 사용되므로 연결하려는 MySQL 커넥션과 일치하게 수정해주면 된다.

모델 정의하기

// models/user.js

const Sequelize = require('sequelize');

class User extends Sequelize.Model {
    static initiate(sequelize) {
        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) {
        db.User.hasMany(db.Comment, { foreignKey: 'commenter', sourceKey: 'id' });
    }
};

module.exports = User;
// 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;

User(유저)와 Comment(댓글) 모델을 만들어 두 테이블을 연결하였다.

static initiate 메서드에는 테이블에 대한 설정을 하고, static associate 메서드에는 다른 모델과의 관계를 적는다.

모델.init 메서드의 첫 번째 인수는 테이블 칼럼에 대한 설정이고, 두 번째 인수는 테이블 자체에 대한 설정이다. 시퀄라이즈는 알아서 id를 기본 키로 연결하므로 id 칼럼은 적을 필요가 없다.

시퀄라이즈는 MySQL 이외에 다른 데이터베이스도 처리할 수 있으므로 위 표와 같이 자료형이 MySQL과 다르다.

모델.init 메서드의 두 번째 인수는 테이블 옵션이다.

  • sequelize: static intiate 메서드의 매개변수와 연결되는 옵션으로 db.sequelize 객체를 넣어야 한다.
  • timestamps: timestamps 속성이 true이면 시퀄라이즈는 createdAt과 updateAt 칼럼을 추가한다. 각각 로우가 생성될 때와 수정될 때의 시간이 자동으로 입력된다.
  • underscored: 시퀄라이즈는 기본적으로 테이블명과 칼럼명을 캐멀 케이스(camel case)로 만든다. 이를 스네이크 케이스로 바꾸는 옵션이다.
  • modelName: 모델 이름을 설정할 수 있다.
  • tableName: 실제 데이터베이스의 테이블 이름이 된다. 기본적으로 모델 이름을 소문자 및 복수형으로 만든다.
  • paranoid: true로 설정하며 deletedAt라는 컬럼이 생긴다. 로우를 삭제할 때 완전히 지워지지 않고 deletedAt에 지운 시각이 기록된다.
  • charset 과 collate: 각각 utf8과 utf_general_ci로 설정해야 한글이 입력된다.

관계 정의하기

1:N 관계

시퀄라이즈에서는 1:N 관계에서 1->N은 hasMany라는 메서드로 표현한다. 반대로 N->1은 belongsTo 메서드로 표현한다.

위 User와 Comment는 1:N 관계이기 때문에 user.js에서 static associate(db) 메서드에 hasMany 메서드를 사용한 것을 볼 수 있고, comment.js에선 belongsTo 메서드를 사용한 것을 볼 수 있다.

어떤 모델에서 hasMany를 쓰고 belongsTo를 쓰는지 헷갈릴 수 있는데, 다른 모델의 정보가 들어가는 테이블에는 belongsTo를 사용하면 된다. 위 코드에서는 commenter 컬럼이 추가되는 Comment 모델에 belongsTo를 사용한 것이다.

1:1 관계

1:1 관계에선 hasMany 메서드 대신 hasOne 메서드를 사용한다. 사용자 정보를 담고 있는 가상의 Info 모델이 있다고 가정하면 다음과 같이 표현할 수 있다.

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

1:1 관계일때도 belongsTo를 사용하는 Info 모델에 UserId 컬럼이 추가되기 때문에 belongsTo와 hasOne이 반대이면 안된다.

N:M 관계

시퀄라이즈에는 N:M 관계를 표현하기 위한 belongsToMany 메서드가 있다. 게시글 정보를 담고 있는 가상의 Post 모델과 해시태그 정보를 담고 있는 가상의 Hashtag 모델이 있다고 하면 다음과 같이 표현할 수 있다.

db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });

양쪽 모델 모두에 belongsToMany 메서드를 사용하며, N:M 관계 특성상 through 속성에 작성한 이름을 가진 새로운 모델이 생성된다.

쿼리 알아보기

쿼리는 프로미스를 반환하므로 then을 붙여 결괏값을 받을 수 있고, async/await 문법과 같이 사용할 수도 있다.

로우 생성 쿼리

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',
});

첫번째는 SQL문이고, 그 밑 코드는 시퀄라이즈 쿼리이며 둘이 동일하다. 항상 자료형에 주의하자.

로우 조회 쿼리

SELECT * FROM nodejs.users;

User.findAll({});
SELECT * FROM nodejs.users LIMIT 1;

User.findOne({});

위 코드는 users 테이블의 모든 데이터를 조회하는 SQL문과 시퀄라이즈 쿼리와 하나의 데이터만 가져오는 SQL문과 시퀄라이즈 쿼리이다.

SELECT name, married FROM nodejs.users ORDER BY age DESC;

User.findAll({
	attributes: ['name', 'married'],
    order: [['age', 'DESC']],
});

위와 같이 attributes 옵션을 사용하여 원하는 컬럼만 가져올 수 있고, order 옵션으로 정렬방식을 선택할 수 있다.

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 },
    },
});

위와 같이 where 옵션으로 조건들을 나열할 수도 있다.

MySQL에서는 undefined라는 자료형을 지원하지 않으므로 where 옵션에는 undefined가 들어가면 안된다. 빈 값을 넣고자 하면 null을 대신 사용하자.

위 코드에서 Op.gt는 연산자를 뜻하며, 여러가지가 있다.

  • Op.gt(초과)
  • Op.gte(이상)
  • Op.lt(미만)
  • Op.lte(이하)
  • Op.ne(같지 않음)
  • Op.or(또는)
  • Op.in(배열 요소 중 하나)
  • Op.notIn(배열 요소와 모두 다름)

로우 수정 쿼리

UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;

User.update({
  comment: '바꿀 내용',
}, {
  where: { id: 2 },
});

update 메서드로 수정할 수 있으며, 첫 번째 인수로 수정할 내용을 적고, 두 번째 인수로 어떤 로우를 수정할지에 대한 조건들을 where 옵션에 적는다.

로우 삭제 쿼리

DELETE FROM nodejs.users WHERE id = 2;
User.destory({
  where: { id: 2 },
});

where 옵션에 조건들을 적고 destory 메서드로 삭제한다.

관계 쿼리

const user = await User.findOne({
  include: [{
  	model: Comment,
    where: {
      id: 1,
  },
    attributes: ['id'],
  }]
});
console.log(user.Comments); //사용자 댓글

특정 사용자를 가져오면서 그 사람의 댓글까지 모두 가져오고 싶다면 include 속성을 사용하여 어떤 모델과 관계가 있는지 include에 넣어주면 된다. 또한 where이나 attributes 같은 옵션을 사용할 수 있다.

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

관계를 설정했다면 getComments(조회) 이외에도 setComments(수정), addComment(하나 생성), addCommets(여러 개 생성), removeComments(삭제) 메서드를 지원한다. 동사 뒤에 모델의 이름을 붙이는 형식이다.

SQl 쿼리하기

시퀄라이즈 쿼리를 사용하기 싫거나 어떻게 해야 할지 모르겠다면 직접 SQL문을 통해 쿼리할 수도 있다.

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

이번 장을 복습하고 실습까지 진행해보았다. ORM은 spring 프레임워크의 JPA 말곤 아는게 없어서 프로젝트를 진행할 때 직접 SQL 쿼리문을 하나하나 짰었다. 이젠 시퀄라이즈에 익숙해 질 수 있도록 연습해야겠다. 물론 SQL도 아직 어렵다고 느껴져서 둘 다 꾸준히 연습해야할듯 ㅎㅎ;

profile
기록으로 복습하자

0개의 댓글

관련 채용 정보