개발스토리

Sequelize 본문

node.js

Sequelize

무루뭉 2020. 12. 28. 01:37

시퀄라이즈 사용

MySQL 작업을 쉽게 할 수 있도록 도와주는 라이브러리이다.

  • 시퀄라이즈는 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구이다.
    • MariaDB, PostgreSQL 등 다른 데이터베이스도 같이 쓸 수 있다.

시퀄라이즈에 필요한 sequelize와 sequelize-cli, mysql2 패키지를 설치하자.

  • sequelize-cli는 시퀄라이즈 명령어를 실행하기 위한 패키지이고, mysql2는 MYSQL과 시퀄라이즈를 이어주는 드라이버이다.

그 다음 sequelize init 명령어를 호출한다. 그러면 config, models, migrations, seeders 폴더가 생성된다. models 폴더 안의 index.js 내용을 수정하자.

models/index.js

const Sequelize = require('sequelize');

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;

module.exports = db;
  • Sequelize는 시퀄라이즈 패키지이자 생성자이다. config/config.json에서 데이터베이스 설정을 불러온 후 new Sequelize를 통해 MYSQL 연결 객체를 생성한다. 연결 객체를 나중에 재사용하기 위해 db.sequelize에 넣어두었다.

MySQL 연결

이제 시퀄라이즈를 통해 익스프레스 앱과 MySQL을 연결해보자. app.js를 생성하고 코드 작성하자.

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

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

const app = express();

app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html');
nunjucks.configure('views',{
    express : app,
    watch : true,
});

sequelize.sync({force:false})
    .then(()=>{
        console.log('database connected');
    })
    .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(()=>{
    console.log('server start');
});
  • MySQL과 연동할 때는 config 폴더 안의 config.json 정보가 사용된다. 들어가서 본인의 정보로 수정해주면 된다.

모델 정의

이제 MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의해야 한다. MySQL의 테이블은 시퀄라이즈의 모델과 대응된다. 시퀄라이즈는 모델과 MySQL의 테이블을 연결해주는 역할을 한다.

시퀄라이즈는 기본적으로 모델 이름은 단수형으로 테이블 이름은 복수형으로 사용한다.

예시

const express = require('express');
const { STRING } = require('sequelize');
const Sequelize = require('sequelize');

module.exports = class User extends Sequelize.Model{
    static init(sequelize){
        return super.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 init, static associate로 나뉜다.
  • init 메서드에는 테이블에 대한 설정을 하고, associate 메서드에는 다른 모델과의 관계를 적는다.
  • 위 super.init 메서드의 첫 번째 인수가 테이블 컬럼에 대한 설정이고, 두 번째 인수가 테이블 자체에 대한 설명이다.
    • 시퀄라이즈의 자료형은 MySQL의 자료형과는 조금 다르다.
      • VARCHAR는 STRING, INT는 INTEGER, TINYINIT는 BOOLEAN, DATETIME은 DATE
      • allowNull은 NOT NULL이다.

이제 index.js와 연동한다.

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


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;

User.init(sequelize);

User.associate(db);

module.exports = db;
  • db 객체에 User를 담아두었다. 앞으로 db 객체를 require하여 User 모델에 접근할 수 있다.
  • User.init은 각각의 모델의 static.init 메서드를 호출하는 것.
    • init이 실행되어야 테이블이 모델로 연결된다.

관계 정의

예를 들어, 사용자 한 명은 댓글을 여러 개 작성할 수 있다. 하지만 댓글 하나에 사용자가 여러 명일 수는 없다. 이러한 관계를 일대다 관계라고 한다. 다른 관계로 일대일, 다대다 관계가 있다.

  • MySQL에서는 JOIN이라는 기능으로 여러 테이블간의 관계를 파악해 결과를 도출한다. 시퀄라이즈는 JOIN 기능도 알아서 구현한다. 대신 테이블 간에 어떠한 관계가 있는지 시퀄라이즈에 알려야 한다.

1:N

시퀄라이즈에서는 1:N 관계를 hasMany 메서드로 표현한다.

  • 예를 들어, 사용자 테이블의 로우 하나를 불러올 때 연결된 댓글 테이블의 로우들도 같이 불러올 수 있다.

반대로 belongsTo 메서드도 있다.

  • 댓글 테이블의 로우를 불러올 때 연결된 사용자 테이블의 로우를 가져온다.

다른 모델의 정보가 들어가는 테이블에 belongsTo를 사용한다.

image

  • hasMany에서는 sourceKey를 쓰고, belongsTo에서는 targetKey를 쓴다고 보면 된다.
1:1

1:1 관계에서는 hasOne메서드를 사용한다. 사용자 정보를 담고있는 가상의 info 모델이 있다고 하면 다음과 같이 표현 가능하다.

db.user.hasOne(db.Info, {foreignKey : 'userId', sourceKey:'id'});
db.user.belongsTo(db.user, {foreignKey : 'userId', targetKey:'id'});
  • 1:1 관계라고 해도 belongsTo와 hasOne이 반대면 안된다.
    • belongsTo를 사용하는 Info 모델에 UserId 컬럼이 추가되기 때문이다.
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 속성에 그 이름을 적으면 된다. 새로 생성된 PostHashtag 모델에는 게시글과 해시태그의 아이디가 저장된다.

N:M에서 데이터를 조회할 때 여러 단계를 거쳐야 한다.

예를 들어, #노드 해시태크를 사용한 게시물을 조회하는 경우를 생각해보자.

먼저, #노드 해시태크를 Hashtag 모델에서 조회하고, 가져온 태그의 아이디 (1)를 바탕으로 PostHashtag 모델에서 hashtagId가 1인 postId를 찾아 Post 모델에서 정보를 가져온다.

쿼리 알아보기

시퀄라이즈로 CRUD 작업을 하려면 먼저 시퀄라이즈 쿼리를 알아야 한다. 쿼리는 프로미스를 반환하므로 then을 붙여 결괏값을 받을 수 있다. async/await 문법과 같이 사용하 수도 있다.

로우를 생성하는 쿼리부터 보자. 첫 줄은 SQL문이고, 그 아래는 시퀄라이즈 쿼리이다.

//SQL
INSET INTO nodejs.users (name, age, married, comment) VALUES ('JOO', 24, 0, '하이요');

//Sequelize Query
const { User } = require('../models');
User.create({
    name : 'joo',
    age : 24,
    married : false,
    comment : '하이요',
});

이번에는 로우를 조회하는 쿼리들을 알아보자.

users 테이블의 모든 데이터를 조회하는 SQL문이다. findAll 메서드를 사용하자.

// sql
SELECT * FROM nodejs.users;

// Sequelize Query
User.findAll({});

users 테이블의 데이터 하나만 가져오는 SQL문이다. 데이터를 하나만 가져올 때는 findOne 메서드를, 여러 개 가져올 때는 findAll 메서드를 사용하자.

// sql
SELECT * FROM nodejs.users LIMIT 1;

// Sequelize Query
User.findOne({});

attributes 옵션을 사용해서 원하는 컬럼만 가져올 수 있다.

// sql
SELECT name, married FROM nodejs.users;

// Sequelize Query
User.findAll({
    attributes:['name', 'married'],
});

where 옵션이 조건들을 나열하는 옵션이다.

// sql
SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;

// Sequelize Query
const {Op} = require('sequelize');
const {User} = require('../models');
User.findAll({
    attributes : ['name', 'age'],
    where:{
        married : true,
        age : {[Op.gt]:30},
    },
});
  • Op.gt(초과), Op.gte(이상), Op.lt(미만), Op.lte(이하), Op.ne(같지 않음), Op.or(또는), Op.in(배열 요소 중 하나) 등이 있다.

시퀄라이즈의 정렬 방식은 order 옵션으로 가능하다.

// sql
SELECT id, name FROM users ORDER BY age DESC;

// Sequelize Query
User.findAll({
    attributes : ['id', 'name'],
    order : [['age','DESC']],
});
  • 배열 안에 배열이 있다는 점 주의

로우를 수정하는 쿼리

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

// Sequelize Query
User.update({
    comment : '바꿀 내용',
},{
    where : {id : 2},
});
  • update 메서드로 수정 가능. 첫 번째 인수는 수정할 내용, 두 번째 인수는 어떤 로우를 수정할 지에 대한 조건이다.

로우를 삭제하는 쿼리

// sql
DELETE FROM nodejs.users WHERE id = 2;

// Sequelize Query
User.destory({
    where : {id : 2},
});
  • destroy 메서드로 삭제한다. where 옵션에 조건들을 적는다.

관계 쿼리

findOne이나 findAll 메서드를 호출할 때 프로미스의 결과로 모델을 반환한다.

  • findAll은 모두 찾는 것이므로 모델의 배열을 반환
const user = await User.findOne({});
console.log(user.nick); // user 닉네임

편리한 점은 JOIN 기능을 제공한다는 점이다. 만약 특정 사용자를 가져오면서 그 사용자의 댓글을 모두 가져오고 싶다면 include 속성을 사용하면 된다.

const user = await User.findOne({
    include : [{
        model : Comment,
    }]
});
console.log(user.Comments); // user 댓글
  • 어떤 모델과 관계가 있는지를 include 배열에 넣어주면 된다. 배열인 이유는 다양한 모델과 관계가 있을 수 있기 때문이다.
  • 댓글은 여러 개일 수 있으므로 user.Comments로 접근 가능하다.

또는 아래와 같이 댓글에 접근할 수 있다.

const user = await User.findOne({});
const comments = await user.getComments();
console.log(comments); // 사용자 댓글
  • 관계를 설정했다면 getComments(조회)외에도 setComments(수정), addComment(하나 생성), addComments(여러 개 생성), removeComments(삭제) 메서드를 지원한다.
    • 동사 뒤 모델 이름을 바꾸고 싶다면 관계 설정 시 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); //사용자 댓글
  • as를 설정하면 include시 추가되는 댓글 객체도 user.Answers로 바뀐다.
SQL 쿼리하기

시퀄라이즈의 쿼리를 사용하기 싫거나, 모른다면 직접 SQL문을 통해 쿼리할 수도 있다.

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

'node.js' 카테고리의 다른 글

JWT(JSON Web Token)  (0) 2021.01.01
Mongoose  (0) 2020.12.28
cluster  (0) 2020.12.10
https와 http2  (0) 2020.12.10
쿠키, 세션  (1) 2020.12.10
Comments