본문 바로가기
Projects/NodeBoard

Query방식에서 Sequelize ORM으로 전환

by DawIT 2021. 5. 12.
320x100

기존에는 db-query.js 파일 내에서 직접 SQL쿼리를 통해 정보를 가져오고 수정하였다.

 

var db = require('./db-connect')
var useCrypto = require('./crypto');

var getList = function(amount,type,page,callback){
    // 전체 글 조회
    if (type=='all'){
        db.query('SELECT COUNT(*) AS COUNT FROM POST',function(err,count){
            if (err)
                console.log('에러 발생 : ' + err);
            else{
                count = count[0].COUNT;
                var maxPage = Math.ceil(count / amount);

                db.query('SELECT * FROM POST ORDER BY ID DESC LIMIT ?, ?',[(page-1)*amount,amount],function(err,result){
                    if (err)
                        console.log('에러 발생 : ' + err);
                    else
                        callback(result,maxPage);
                });
            }
        });
    // 특정 게시판 글 조회
    } else {
        db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE TYPE=?',[type],function(err,count){
            if (err)
                console.log('에러 발생 : ' + err);
            else{
                count = count[0].COUNT;
                var maxPage = Math.ceil(count / amount);

                db.query('SELECT * FROM POST WHERE TYPE=? ORDER BY ID DESC LIMIT ?, ?',[type,(page-1)*amount,amount],function(err,result){
                    if (err)
                        console.log('에러 발생 : ' + err);
                    else
                        callback(result,maxPage);
                });
            }
        });
    }
}

var viewPost = function(id,callback){
    // 조회수 증가
    db.query('UPDATE POST SET VIEWS=VIEWS+1 WHERE ID=?',[id],function(){});
    // 글 조회
    db.query('SELECT * FROM POST WHERE ID=?',[id],function(err,post){
        if(err || post[0] === undefined){
            console.log('에러 발생 : ' + err);
            callback(false);
        } else{
            callback(post[0]);
        }
    });
}

var writePost = function(data,callback){
    // 쿼리에 insert
    db.query('INSERT INTO POST (`TITLE`, `AUTHOR`, `TYPE`, `CONTENT`, `PASSWORD` , `isLogined`) VALUES (?, ?, ?, ?, ?, ?)',
        [data.title,data.author,data.type,data.content,data.password,data.isLogined],function(err){
            if (err){
                console.log(err);
                callback(false);
            }
            else
                callback(true);
        });
}

var checkPassword = function(id,plainPassword,callback){
    useCrypto(plainPassword,function(password){
        db.query('SELECT PASSWORD FROM POST WHERE ID=?',[id],function(err,savedPassword){
            if (savedPassword[0].PASSWORD==password)
                callback(true);
            else
                callback(false);
        });
    });
}

var deletePost = function(id,callback){
    db.query('DELETE FROM POST WHERE ID=?',[id],(err)=>{
        if (err)
            callback(false);
        else
            callback(true);
    });
};

var viewForUpdatePost = function(id,callback){
    db.query('SELECT * FROM POST WHERE ID=?',[id],(err,post)=>{
        if (err)
            callback(false);
        else
            callback(post[0]);
    });
};

var updatePost = function(data,callback){
    db.query('UPDATE POST SET TITLE=? , CONTENT=? WHERE ID=? AND PASSWORD=?',[data.title,data.content,data.id,data.code],(err)=>{
        callback(err);
    });
}

var searchPost = function(data,amount,callback){
    // DB 칼럼명에 맞게 변경
    if (data.mode=='제목')
        data.mode='TITLE';
    else if (data.mode=='작성자')
        data.mode='AUTHOR';
    else if (data.mode=='내용')
        data.mode='CONTENT';


    if(data.type=='all'){
        db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE ?? LIKE ?',[data.mode,"%"+data.keyword+"%"],(err,count)=>{
            if (err){
                console.log(err);
                callback(false);
            }
            else{
                count = count[0].COUNT;
                var maxPage = Math.ceil(count / amount);

                db.query('SELECT * FROM POST WHERE ?? LIKE ? ORDER BY ID DESC LIMIT ?, ?',[data.mode,"%"+data.keyword+"%",(data.page-1)*amount,amount],(err,result)=>{
                    if(err){
                        callback(false);
                    }
                    else{
                        callback(result,maxPage);
                    }
                });
            }
        });
    } else{
        db.query('SELECT COUNT(*) AS COUNT FROM POST WHERE TYPE=? AND ?? LIKE ?',[data.type,data.mode,"%"+data.keyword+"%"],(err,count)=>{
            if (err)
                callback(false);
            else{
                count = count[0].COUNT;
                var maxPage = Math.ceil(count / amount);

                db.query('SELECT * FROM POST WHERE TYPE=? AND ?? LIKE ? ORDER BY ID DESC LIMIT ?, ?',[data.type,data.mode,"%"+data.keyword+"%",(data.page-1)*amount,amount],(err,result)=>{
                    if(err)
                        callback(false);
                    else
                        callback(result,maxPage);
                });
            }
        });
    }
}

var thumbup = function(id,ip,callback){
    // 추천수 카운트 변수
    var count;

    // 해당 글의 추천 수를 새로운 DB에서 카운트 해서 가져오기
    db.query('SELECT COUNT(*) AS COUNT FROM THUMBUPS WHERE POST_ID=?',[id],(err,result)=>{
        if (err){
            errcode = 1
            callback(false,errcode);
            console.log(err);
            return;
        }
        count = result[0].COUNT;
    });

    // 해당 글을 추천한 ip리스트 가져오기
    db.query('SELECT USER_IP FROM THUMBUPS WHERE POST_ID=?',[id],(err,result)=>{
        if (err){
            errcode = 2
            callback(false,errcode);
            console.log(err);
            return;
        }


        // 해당 ip가 이미 추천한 ip인지 확인
        for (let savedIp of result)
            if (savedIp.USER_IP == ip)
                var forDelete = true;

        if (forDelete){
            // 추천 취소
            db.query('DELETE FROM THUMBUPS WHERE POST_ID=? AND USER_IP=?',[id,ip],(err)=>{
                if (err){
                    errcode = 3
                    console.log(err);
                    callback(false,errcode);
                    return;
                }
                
                count--;
                // 해당 글의 추천수 조정
                db.query('UPDATE POST SET THUMBUP=? WHERE ID=?',[count,id],()=>{});
                callback('deleted',count);
            });
        } else{
            // 추천 진행
            db.query('INSERT INTO THUMBUPS (`POST_ID`, `USER_IP`) VALUES (?,?)',[id,ip],(err)=>{
                if (err){
                    errcode = 4
                    callback(false,errcode);
                    console.log(err);
                    return;
                }

            count++;
                // 해당 글의 추천수 조정
                db.query('UPDATE POST SET THUMBUP=? WHERE ID=?',[count,id],()=>{});
                callback('success',count);
            });
        }
    });
}

var innerRight = function(callback){
    db.query('SELECT ID, TITLE FROM POST ORDER BY THUMBUP DESC LIMIT 3',(err,bestPosts)=>{
        if (err){
            callback(false,false);
            console.log('1')
        }
        else{
            db.query('SELECT ID, TITLE FROM POST WHERE TYPE=\'공지사항\' ORDER BY ID DESC LIMIT 3', (err,announcements)=>{
                if (err){
                    callback(false,false);
                    console.log(err)
                }
                else
                    callback(bestPosts,announcements);
            });
        }
    });
};

var getReply = function(postId,callback){
    db.query('SELECT ID,POST_ID,ROOT_REPLY_ID,AUTHOR,CONTENT,isLogined FROM REPLY WHERE POST_ID=?',[postId],(err,result)=>{
        if (err){
            console.log(err);
            callback(false);
        }
        else{
            callback(result);
        }
    });
}

var deleteReply = function(id,plainPassword,callback){
    useCrypto(plainPassword,(password)=>{
        db.query('SELECT PASSWORD FROM REPLY WHERE ID=?',[id],(err,result)=>{
            if (err){
                callback(1);
                console.log(err);
                return;
            }

            if (result[0].PASSWORD == password){
                db.query('DELETE FROM REPLY WHERE ID=?',[id],(err)=>{
                    if (err){
                        callback(2);
                        console.log(err);
                        return;
                    }

                    callback(-1);
                });
            } else{
                callback(0);
            }
        });
    });
};

var writeReply = function(data,callback){
    useCrypto(data.password,(password)=>{
        if (data.rootReplyId == ''){
            db.query('INSERT INTO REPLY (`POST_ID` ,`AUTHOR`, `CONTENT`, `isLogined`, `PASSWORD`) VALUES (?, ?, ?, ?, ?)',[data.postId,data.writer,data.content,data.isLogined,password],(err)=>{
                if(err){
                    callback(1);
                    console.log(err);
                    return;
                } else{
                    callback(-1);
                }
            });
        } else{
            db.query('INSERT INTO REPLY (`POST_ID` ,`AUTHOR`, `CONTENT`, `isLogined`, `ROOT_REPLY_ID`, `PASSWORD`) VALUES (?, ?, ?, ?, ?, ?)',[data.postId,data.writer,data.content,data.isLogined,data.rootReplyId,password],(err)=>{
                if(err){
                    callback(1);
                    console.log(err);
                    return;
                } else{
                    callback(-1);
                }
            });
        }
    });
};

var findUser = function(id,callback){
    db.query('SELECT * FROM USER WHERE USER_CODE=?',[id],(err,result)=>{
        if(err){
            callback(1);
            console.log(err);
            return;
        } else{
            if (result.length == 1){
                callback(result[0])
            } else if (result.length == 0){
                callback(0);
            }
        }
    });
}

var newUser = function(id,nickname,callback){
    db.query('INSERT INTO USER (`USER_CODE`, `NICKNAME`, `GROUP`) VALUES (?,?,?)',[id,nickname,'USER'],(err)=>{
        if(err){
            if (err.errno == 1062)
                callback(1);
            else
                callback(0);
        } else{
            callback(-1);
        }
    })
}

var checkIsLogined = function(postId,callback){
    db.query('SELECT isLogined FROM POST WHERE ID=?',[postId],(err,result)=>{
        if(err){
            callback(-1);
            console.log(err);
            return;
        } else {
            if(result.length == 1){
                callback(result[0].isLogined);
            } else {
                callback(-2);
                return;
            }
        }
    });
}

 

그러나 기존 db-query.js 코드를 보면 알 수 있듯이 매우 가독성이 떨어지고, 오류 처리도 제각각이라 관리하기가 힘들었다. 그래서 객체지향적으로 DB를 관리하기 위해 ORM을 알아보던 중 nodejs에서 mysql을 사용할 때는 Sequelize라는 ORM을 많이 사용한다는 것을 알게 되었다.

 

https://sequelize.org/

 

Sequelize

 

sequelize.org

 

기존의 방식에서 Sequelize 로 전환하기 위해서는 먼저 기존에 존재하던 테이블에 따라 모델을 추출해야 한다.

 

sequelize-auto를 사용하면 기존에 존재하던 테이블을 쉽게 모델로 추출할 수 있다.

 

https://github.com/sequelize/sequelize-auto

 

sequelize/sequelize-auto

Automatically generate bare sequelize models from your database. - sequelize/sequelize-auto

github.com

 

먼저 npm을 통해 sequelize를 설치한다.

 

// Sequelize와 mysql2 설치
npm install sequelize mysql2

// Sequelize cli 설치
npm install -g sequelize-cli

 

이후 프로젝트 폴더에서 초기화를 진행해준다.

 

// Sequelize 초기화
sequelize init

 

그러면 프로젝트 폴더에 Config 파일과 구조 폴더들이 생성된 것을 확인할 수 있다.

 

이제 원래 DB에서 모델을 추출한다.

 

sequelize-auto -h <host> -d <database> -u <user> -x [password] -p [port]  --dialect [dialect] -c [/path/to/config] -o [/path/to/models] -t [tableName]

 

github 에는 위의 명령어를 기본적으로 사용하도록 되어 있다. 내가 사용한 명령어는

 

sequelize-auto -h localhost -d nodeboard -u root -x 111111 -p 3306 -o "./models"

 

테이블명을 입력하지 않으면 모든 테이블을 가져온다. 이러면

 

 

이렇게 모델이 추출된다.

 

const Sequelize = require('sequelize');
module.exports = function(sequelize, DataTypes) {
  return sequelize.define('POST', {
    ID: {
      autoIncrement: true,
      type: DataTypes.INTEGER,
      allowNull: false,
      primaryKey: true
    },
    TITLE: {
      type: DataTypes.STRING(45),
      allowNull: false
    },
    AUTHOR: {
      type: DataTypes.STRING(45),
      allowNull: false
    },
    TYPE: {
      type: DataTypes.STRING(20),
      allowNull: false
    },
    CONTENT: {
      type: DataTypes.TEXT,
      allowNull: false
    },
    VIEWS: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 0
    },
    PASSWORD: {
      type: DataTypes.STRING(100),
      allowNull: true
    },
    isLogined: {
      type: DataTypes.TINYINT,
      allowNull: false,
      defaultValue: 0
    },
    THUMBUP: {
      type: DataTypes.INTEGER,
      allowNull: false,
      defaultValue: 0
    }
  }, {
    sequelize,
    tableName: 'POST',
    timestamps: false,
    indexes: [
      {
        name: "PRIMARY",
        unique: true,
        using: "BTREE",
        fields: [
          { name: "ID" },
        ]
      },
    ]
  });
};

 

각 모델은 대충 이런식으로 되어 있다. 칼럼과 그에 대한 정보가 나와 있다.

 

이제 기존 db-query를 새로운 ORM을 적용함에 따라 코드를 다 뜯어고쳐야 한다.

 

SELECT (findAll, findOne)

// 글 가져오기
async function getList(type,page){
  if (type==='all'){
    return models.POST.findAll({
      attributes:['ID','AUTHOR','isLogined','TITLE','VIEWS','THUMBUP'],
      order:[['ID','DESC']],
      limit: 20,
      offset: (page-1)*20
    });
  } else {
    return models.POST.findAll({
      attributes:['ID','AUTHOR','isLogined','TITLE','VIEWS','THUMBUP'],
      where:{
        type: type
      },
      order:[['ID','DESC']],
      limit: 20,
      offset: (page-1)*20
    });
  }
}

 

기존 SQL의 SELECT문은 Sequelize 의 findAll 혹은 findOne 을 사용하면 된다. 한눈에 봐도 알 수 있듯이 가져올 칼럼, 정렬 기준, 가져올 개수, 스킵할 개수를 옵션으로서 정할 수 있다.

 

// 인증글 여부 확인
async function checkIsLogined(id){
    const check = await models.POST.findOne({
        attributes: ['isLogined'],
        where: {ID: id}
    });

    return check.dataValues.isLogined;
}

 

findOne도 받는 인자는 같다. findAll에서 limit 1이 기본적으로 적용되어 있다고 보면 된다.

 

INSERT (create)

// 댓글 작성
async function writeReply(data){
    if (data.rootReplyId == '') data.rootReplyId = null;
    return models.REPLY.create({
        POST_ID:data.postId,
        ROOT_REPLY_ID:data.rootReplyId,
        AUTHOR:data.writer,
        CONTENT:data.content,
        isLogined:data.isLogined,
        PASSWORD:data.password
    });
}

 

INSERT의 경우 create메서드를 사용하면 된다. create 에는 칼럼명: 데이터, key:value의 방식으로 객체에 정보를 담아서 넘기면 된다.

 

UPDATE (update)

async function updatePost(data){
    return models.POST.update({
        TITLE: data.title,
        CONTENT: data.content
    },{
        where:{
            ID:data.id,
            PASSWORD:data.code
        }
    });
}

 

UPDATE는 sequelize에서도 update이다. create메서드 처럼 key:value로 값을 넘겨주는 객체를 첫 인자로 주고, 두번째 인자로는 row를 특정할 수 있는 옵션(where 등등) 을 넘기면 된다.

 

DELETE (destroy)

// 글 삭제
async function deletePost(id){
    // 댓글 삭제
    await models.REPLY.destroy({
        where: { POST_ID: id}
    });


    // 글 삭제
    return await models.POST.destroy({
        where: { ID: id}
    });
}

 

DELETE는 destory 메서드를 사용하면 된다. where 를 통해 row 만 특정해주면 삭제할 수 있다.

 

 

댓글