티스토리 뷰

Less is more.

Less is more! 단순한 것이 더 아름답다는 의미의 내가 가장 좋아하는 문장이다. 오늘날 Java 진영에서 RDMBS를 다루는데 있어 Hibernate와 같은 거대한 덩치를 가진 ORM부터 MyBatis와 같은 맵핑 라이브러리, jOOQ와 같은 DSL 라이브러리에 이르기까지 오브젝트 기반의 프로그래밍 언어에서 관계형 기반의 데이터베이스를 다루기 위한 많은 노력이 있어왔다. 하지만 때때로 나처럼 단순히 SQL 문을 보다 쉽게 빌딩하는 정도에 만족하는 개발자 또한 존재한다. 나는 개인적으로 Java 기반의 프로젝트를 진행할 때 MyBatisSqlBuilder를 개조하여 Sql2o와 함께 사용하는 것을 즐긴다. 단순하기 때문에 가독성이 뛰어나며 유지보수도 쉽고 실행 속도도 빠르기 때문이다.

JavaScript에도 SqlBuilder가 있었으면...

Node.js가 등장하면서 서버 사이드에서도 JavaScript로 관계형 데이터베이스에 접근하는 코드를 작성하는 것이 가능해졌다. JavaScript 진영에도 다양한 쿼리 빌더가 존재하지만 MyBatisSqlBuilder만큼 단순하면서 직관적인 빌더는 없는 것 같아 SqlBuilderJavaScript 버전을 만들어봤다. 아래는 SELCET 문에 대한 예로 전체 코드가 완성되면 오픈 소스로 공개할 예정이다.

var INSERT_INTO = (function() {
    function I(param) {
        this.table = param;
        this.columns = '';
        this.values = '';
    }

    I.prototype.VALUES = function(columnName, value) {
        if (columnName === null) {
            return this;
        }
        if (this.columns.length > 0) {
            this.columns += ', ';
        }
        this.columns += columnName;
        if (this.values.length > 0) {
            this.values += ', ';
        }
        this.values += value;
        return this;
    };

    I.prototype.bind = function(param, value) {
        var paramValue;
        if (value === null) {
            paramValue = 'NULL';
        } else if (value.constructor === Number) {
            paramValue = value;
        } else {
            paramValue = "'" + value + "'";
        }
        this.values = this.values.split(param).join(paramValue);
        return this;
    };

    I.prototype.toString = function() {
        var query = '';
        if (this.table.length === 0) {
            return query;
        }
        if (this.columns.length === 0) {
            return query;
        }
        if (this.values.length === 0) {
            return query;
        }
        query = 'INSERT INTO ' + this.table + ' (' + this.columns + ') VALUES (' + this.values + ')';
        return query;
    };

    return I;
})();

var SELECT = (function() {
    function S(param) {
        this.select = param;
        this.from = '';
        this.where = '';
        this.innerjoin = '';
        this.leftouterjoin = '';
        this.groupby = '';
        this.having = '';
        this.orderby = '';
    }

    S.prototype.SELECT = function(param) {
        if (this.select.length > 0) {
            this.select += ', ';
        }
        this.select += param;
        return this;
    };

    S.prototype.FROM = function(param) {
        if (this.from.length > 0) {
            this.from += ', ';
        }
        this.from += param;
        return this;
    };

    S.prototype.INNER_JOIN = function(param) {
        if (this.innerjoin.length > 0) {
            this.innerjoin += ' ';
        }
        this.innerjoin += 'INNER JOIN ';
        this.innerjoin += param;
        return this;
    };

    S.prototype.LEFT_OUTER_JOIN = function(param) {
        if (this.leftouterjoin.length > 0) {
            this.leftouterjoin += ' ';
        }
        this.leftouterjoin += 'LEFT OUTER JOIN ';
        this.leftouterjoin += param;
        return this;
    };

    S.prototype.WHERE = function(param) {
        if (this.where.length > 0) {
            this.where += ' AND ';
        }
        this.where += param;
        return this;
    };

    S.prototype.GROUP_BY = function(param) {
        if (this.groupby.length > 0) {
            this.groupby += ', ';
        }
        this.groupby += param;
        return this;
    };

    S.prototype.HAVING = function(param) {
        this.having = param;
        return this;
    };

    S.prototype.ORDER_BY = function(param) {
        if (this.orderby.length > 0) {
            this.orderby += ', ';
        }
        this.orderby += param;
        return this;
    };

    S.prototype.bind = function(param, value) {
        var paramValue;
        if (value === null) {
            paramValue = 'NULL';
        } else if (value.constructor === Number) {
            paramValue = value;
        } else {
            paramValue = "'" + value + "'";
        }
        this.select = this.select.split(param).join(paramValue);
        this.from = this.from.split(param).join(paramValue);
        this.where = this.where.split(param).join(paramValue);
        this.innerjoin = this.innerjoin.split(param).join(paramValue);
        this.leftouterjoin = this.leftouterjoin.split(param).join(paramValue);
        return this;
    };

    S.prototype.toString = function(param) {
        var query = '';
        if (this.select.length > 0) {
            if (this.from.length > 0) {
                query += 'SELECT ' + this.select + ' FROM ' + this.from;
                if (this.innerjoin.length > 0) {
                    query += ' ' + this.innerjoin;
                }
                if (this.leftouterjoin.length > 0) {
                    query += ' ' + this.leftouterjoin;
                }
                if (this.where.length > 0) {
                    query += ' WHERE ' + this.where;
                }
                if (this.groupby.length > 0) {
                    query += ' GROUP BY ' + this.groupby;
                    if (this.having.length > 0) {
                        query += ' HAVING ' + this.having;
                    }
                }
                if (this.orderby.length > 0) {
                    query += ' ORDER BY ' + this.orderby;
                }
            }
        }
        return query;
    };

    return S;
})();

var UPDATE = (function() {
    function U(param) {
        this.update = param;
        this.set = '';
        this.where = '';
    }

    U.prototype.SET = function(columnName, value) {
        if (columnName === null) {
            return this;
        }
        if (columnName.length === 0) {
            return this;
        }
        if (this.set.length > 0) {
            this.set += ', ';
        }
        this.set += columnName + ' = ' + value;
        return this;
    };

    U.prototype.WHERE = function(param) {
        if (this.where.length > 0) {
            this.where += ' AND ';
        }
        this.where += param;
        return this;
    };

    U.prototype.bind = function(param, value) {
        var paramValue;
        if (value === null) {
            paramValue = 'NULL';
        } else if (value.constructor === Number) {
            paramValue = value;
        } else {
            paramValue = "'" + value + "'";
        }
        this.set = this.set.split(param).join(paramValue);
        this.where = this.where.split(param).join(paramValue);
        return this;
    };

    U.prototype.toString = function() {
        var query = '';
        if (this.update.length === 0) {
            return query;
        }
        if (this.set.length === 0) {
            return query;
        }
        if (this.where.length === 0) {
            return query;
        }
        query = 'UPDATE ' + this.update + ' SET ' + this.set + ' WHERE ' + this.where;
        return query;
    };

    return U;
})();

var DELETE_FROM = (function() {
    function D(param) {
        this.from = param;
        this.where = '';
    }

    D.prototype.WHERE = function(param) {
        if (this.where.length > 0) {
            this.where += ' AND ';
        }
        this.where += param;
        return this;
    };

    D.prototype.bind = function(param, value) {
        var paramValue;
        if (value === null) {
            paramValue = 'NULL';
        } else if (value.constructor === Number) {
            paramValue = value;
        } else {
            paramValue = "'" + value + "'";
        }
        this.where = this.where.split(param).join(paramValue);
        return this;
    };

    D.prototype.toString = function() {
        var query = '';
        if (this.from.length === 0) {
            return query;
        }
        if (this.where.length === 0) {
            return query;
        }
        query = 'DELETE FROM ' + this.from + ' WHERE ' + this.where;
        return query;
    };

    return D;
})();

사용 예

앞서 작성한 SqlBuilder 오브젝트는 아래와 같이 사용할 수 있다.

// 조건을 컬럼 단위로 작성할 수 있어 코드 레벨에서 조건에 따른 동적 쿼리를 작성하기가 수월하다.
var q = new SELECT("ID");
q.SELECT("Name");
q.SELECT("Age");
q.FROM("Student");
q.WHERE("Gender = 'Male'");
q.WHERE("Age >= 21");
q.ORDER_BY("Age");
q.ORDER_BY("Name");
var query = q.toString();
// SELECT ID, Name, Age FROM Student WHERE Gender = 'Male' AND Age >= 21 ORDER BY Age, Name

// 아래와 같이 메써드 체이닝도 가능하다.
var query = new SELECT("ID, Name, Age").FROM("Student").WHERE("Gender = 'Male' AND Age >= 21").ORDER_BY("Age, Name").toString();
// SELECT ID, Name, Age FROM Student WHERE Gender = 'Male' AND Age >= 21 ORDER BY Age, Name

추천할만한 관련 라이브러리

참고 글


댓글
댓글쓰기 폼