Skip to main content

Command Palette

Search for a command to run...

nodeJs mysql (async/await를 이용한 mySql 모듈 만들기)

Updated
5 min read

nodeJs를 이용하여 mysql 혹은 mariaDB 등 RDB를 사용하는 경우가 많다. es7에 제안된 async/await를 사용하여 mysql 모듈을 만들어 볼까 한다.(모듈이라고 하지만 그저 wrapping 한거다.)

기존 사용 했던 mysql 코드

처음 mysql을 썼었을때 pool을 이용하여, 매번 connection을 맺고 끊어주고, 또 트랜젝션을 맺고 롤백과 commit을 해주는 코드를 썼다.

아마 대부분이 아래와 같을 것이다.:

const mysql = require('mysql');
const DBpool  = mysql.createPool({
  connectionLimit : 10,
  host            : 'example.org',
  user            : 'bob',
  password        : 'secret',
  database        : 'my_db'
});

const get = id => {
    DBpool.getConnection((err, con) => {
        if (err) {
            throw err;
        }
        con.query('select * from user where id= ?', [id], (err, data) => {
            con.release();
            ...
        });
    });
}

트랜젝션을 사용:

// pool은 생략
const insert = id => {
    DBpool.getConnection((err, con) => {
        if (err) {
            throw err;
        }
        con.beginTransaction(err => {
            if (err) {
                con.release();
                throw err;
            }

            con.query('select * from user where id = ?', [id], (err, data) => {
                if(err) {
                    return con.rollback(() => {
                        con.release();
                        throw err;
                    });
                }

                con.query('insert into user (name) values (?)', [data[0].name], (err, data) => {
                    if(err) {
                        return con.rollback(() => {
                            con.release();
                            throw err;
                        });
                    }

                    con.commit((err) => {
                        if (err) {
                            return con.rollback(() => {
                                con.release();
                                throw err;
                            });
                        }
                        return con.release();
                    });
                });
                ...
            });
        });
    });
}

이렇게 되면 매번 db 작업을 할때마다 connection 맺어주고 끊어주는 중복된 코드를 작성해야되며, 트렌젝션을 사용할 때는 콜백헬과 좀더 더 긴 코드를 매번 처리해줘야된다.

필자는 이렇게 하는 것이 너무나도 마음에 안들었고 매번 중복된 코드를 쓰는게 너무너무 귀찮아서 아래와 같이 만들어서 사용했다.

1. 시작하기

async/await를 사용하기 위해서는 Babel을 사용하거나 Typescript 같은 것을 사용해야된다. 필자는 Typescript를 사용하기 때문에 Typescript로 진행 하겠다.

기본 설정:

  1. NodeJs 설치
  2. Typescript 설치
  3. Typings 설치

자세한 설정은 Typescript + ExpressJs 시작하기를 참고하여 진행하면 된다.

2. promise-mysql

async/await는 전에 ExpressJs Error에서 설명 했듯이 모든 리턴은 promise로 받아야된다. 그래서 기존 mysql은 callback 기반이기 때문에 사용하지 못하고 npm에 있는 promise-mysql 모듈을 사용한다.

npm install --save promise-mysql

promise-mysql모듈은 typings에 없기 때문에 설치를 하지 않고 진행한다.

3. Module 만들기

기존에는 모든 함수에 connection 맺고 끊는 혹은 콜백하고 커밋하는 코드를 넣어줬다. 이제 그부분을 분리하여, 모듈로 만들 것이다.

1) connection

내가 생각하는 순서는 다음과 같다.:

  1. function을 받는다.
  2. 받은 function의 paramter들을 "...args"를 사용하여 args에 담는다.
  3. connection을 맺고 connection 객체를 생성한다.
  4. 받은 function을 connection객체와 함께 기존 paramter(args)를 넘겨주어 실행 시킨다.
  5. catch를 통해 error가 있을시 connection을 닫아주고 throw error을 해준다.
  6. error가 없을시에는 connection을 닫아주고 실행된 function을 값을 넘겨준다.

위와 같이 생각을 했으면, 아마 아래와 같은 코드가 나올 것이다.

/**
 * 기존 import 하는 방식이 아닌 이유는 promise-mysql은
 * 정의 파일(typings)이 없기 때문에 아래와 같이 쓴다.
 */
const promiseMysql = require('promise-mysql');

const pool  = promiseMysql.createPool({
  connectionLimit : 10,
  host: 'example.org',
  user: 'bob',
  password: 'secret',
  database: 'my_db'
});

export const connect = fn => async (...args) => {
    /* DB 커넥션을 한다. */
    let con: any = await pool.getConnection();
    /* 로직에 con과 args(넘겨받은 paramter)를 넘겨준다. */
    const result = await fn(con, ...args).catch(error => {
        /* 에러시 con을 닫아준다. */
        con.connection.release();
        throw error;
    });
    /* con을 닫아준다. */
    con.connection.release();
    return result;
};

2) 트렌젝션 모듈

트렌젝션 모듈도 위의 connection모듈과 크게 다르지 않을것이다. 그저 롤백과 커밋이 들어간것이다.

  1. function을 받는다.
  2. 받은 function의 paramter들을 "...args"를 사용하여 args에 담는다.
  3. connection을 맺고 connection 객체를 생성한다.
  4. 트렌젝션을 시작하는 코드를 넣는다.
  5. 받은 function을 connection객체와 함께 기존 paramter(args)를 넘겨주어 실행 시킨다.
  6. catch를 통해 error가 있을시 rollback과 connection을 닫아주고 throw error을 해준다.
  7. error가 없을시에는 commit과 connection을 닫아주고 실행된 function을 값을 넘겨준다.

위와 같이 생각을 했으면, 아마 아래와 같은 코드가 나올 것이다.

// pool 생략
export const transaction = fn => async (...args) => {
    /* DB 커넥션을 한다. */
    const con: any = await pool.getConnection();
    /* 트렌젝션 시작 */
    await con.connection.beginTransaction();
    /* 비지니스 로직에 con을 넘겨준다. */
    const result = await fn(con, ...args).catch(async (error) => {
        /* rollback을 진행한다. */
         await con.rollback();
        /* 에러시 con을 닫아준다. */
        con.connection.release();
        throw error;
    });
    /* commit을 해준다. */
    await con.commit();
    /* con을 닫아준다. */
    con.connection.release();
    return result;
}

위와 같이 만든 모듈을 하나로 합치고 mysql모듈이라고 명칭하면 아래와 같다. ```javascript mysql.ts /**

  • 기존 import 하는 방식이 아닌 이유는 promise-mysql은
  • 정의 파일(typings)이 없기 때문에 아래와 같이 쓴다. / const promiseMysql = require('promise-mysql'); import as dotenv from 'dotenv';

dotenv.config({ silent: true, path: '.env' });

const pool = promiseMysql.createPool({ connectionLimit : 10, host: process.env.MYSQL_HOST, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DB });

export module mysql { export const connect = fn => async (...args) => { / DB 커넥션을 한다. / const con: any = await pool.getConnection(); / 로직에 con과 args(넘겨받은 paramter)를 넘겨준다. / const result = await fn(con, ...args).catch(error => { / 에러시 con을 닫아준다. / con.connection.release(); throw error; }); / con을 닫아준다. / con.connection.release(); return result; };

export const transaction = fn => async (...args) => { / DB 커넥션을 한다. / const con: any = await pool.getConnection(); / 트렌젝션 시작 / await con.connection.beginTransaction(); / 비지니스 로직에 con을 넘겨준다. / const result = await fn(con, ...args).catch(async (error) => { / rollback을 진행한다. / await con.rollback(); / 에러시 con을 닫아준다. / con.connection.release(); throw error; }); / commit을 해준다. / await con.commit(); / con을 닫아준다. / con.connection.release(); return result; } }

이렇게 하면 mysql 모듈이 완성이다.

## 4. 사용법
일반 connection 사용:
```javascript
/* 위에 만든 mysql 모듈이다. */
import {mysql} from "mysql"

const get = mysql.connect((con: any, id: string) => con.query('select * from user', [id]));

너무 간단하게 한줄로 끝내버렸다. 물론 단순 select한 값을 리턴했기 때문에 위와 같이 한줄로 나올수 있는 것이다. 만약 다른 비지니스 로직이 있다고 하면 아래와 같다.

/* 위에 만든 mysql 모듈이다. */
import {mysql} from "mysql"

const get = mysql.connect(async (con: any, id: string) => {
        const result = await con.query('select * from user', [id]);

        // ...비지니스로직...

        return result
    });

굳이 동기로 할 필요 없을시에는 async를 빼도 된다.

트랜젝션을 사용:

/* 위에 만든 mysql 모듈이다. */
import {mysql} from "mysql"

const insert = mysql.transaction(async (con: any, id: string) => {
    const user = await con.query('select * from user where id = ?', [id]);
    await con.query('insert into user (name) values (?)', [user[0].name]);
    /* 리턴할 값이 없을시 그냥 return만 써도 된다. */
    return user;
});

트랜젝션을 사용하는 코드는 더욱더 짧아진 코드량을 볼 수 있다.

이 모듈에 대한 예제를 github에 올렸다. 한번 보면 좀더 이해하기 편할 것이다. 도움이 되었다면 위의 별도 한번 눌러 주는 센스!

반말로 블로그를 작성하였는데 이해해주시기 바랍니다. 문의 및 수정 사항은 댓글이나 mayajuni10@gmail.com으로 이메일 보내주시기 바랍니다.

More from this blog

내 문서를 읽는 작은 에이전트를 다시 만들며

내 사이트 dongjun.win에 붙어 있던 작은 AI 어시스턴트를 최근에 다시 손봤다. 방문자가 AI 어시스턴트 페이지에서 질문을 던지면, 내 이력서와 프로젝트 문서, 강점 진단, 리더십 리포트, 버크만(Birkman) 리포트를 바탕으로 답하는 기능이다. 겉으로는 단순하다. "최근 프로젝트는?", "어떤 기술을 쓰나요?", "일하는 방식은 어떤가요?" 같

May 27, 202610 min read

내 실생활에 AI 더하기 (1) — 사진, 영상 하이라이트 만들기

폰 사진 앱을 켜다가 여행 영상 폴더 앞에서 매번 멈춘다. 문제는 "안 본다"가 아니라 "안 보게 된다"였다. 분명히 좋아서 찍었는데, 시간이 지나니 불필요한 컷이 너무 많아서 다시 들어가기가 부담스러운 폴더가 된다. 핵심 장면만 추린 2~3분짜리 메모리 필름이 있다면 한 번에 그 시간을 다시 만날 수 있을 것 같았다. 업무에서는 AI를 매일 많이 쓴다.

May 13, 202611 min read3

법률 AI 검색 실험기 (12) — Lane-based Retrieval 설계와 전체 회고

법률 QA 검색기를 만들면서 거쳐 온 설계 여정의 마지막 이야기다. 벡터 검색의 한계를 마주한 순간부터, 임베딩 선택, selector, rewriter, graph, source-router, 그리고 lane-based retrieval까지. 이 글에서는 최종 단계인 lane 구조 설계를 정리하고, 시리즈 전체를 돌아본다. 검색기 운영 설계의 최종 단계 query-prep 단계를 마무리하면서 자연스럽게 다음 질문이 떠올랐다. prerewri...

May 11, 20266 min read7

법률 AI 검색 실험기 (11) — 오답 분석: 법률 RAG는 왜 자신 있게 틀리는가

틀린 답 하나가 열어준 토끼굴 "중소기업 특별세액감면이 최저한세 적용 대상인가요?" 단순해 보이는 질문이었다. 법령 QA 시스템은 자신 있게 답했다. "조세특례제한법 제132조가 해당 감면 조문을 열거하므로, 최저한세 적용 대상입니다." 조문 번호도 있고, 논리 구조도 있고, 결론도 명확했다. 문제는 하나뿐이었다. 틀렸다는 것. 실제로 제132조의 열거 조문과 해당 감면 조문의 관계를 확인하면, 시스템이 내린 결론과 실제 적용이 달랐다. 세무 ...

May 5, 20264 min read14
D

Dongjun's Blog

28 posts