Druid SQLStatement 인터페이스 완벽 가이드: AST 기반 SQL 파싱과 활용

Druid SQL 파싱 엔진은 SQL 문자열을 구조화된 Java 객체(추상 구문 트리, AST)로 변환합니다. 이 과정에서 최상위 인터페이스가 바로 SQLStatement이며, 모든 SQL 유형(SELECT, INSERT, UPDATE, DELETE 등)은 이 인터페이스의 서브클래스 인스턴스로 표현됩니다. SQLStatement는 SQL AST의 모든 노드가 구현하는 SQLObject 인터페이스를 상속합니다.

1. SQLStatement 클래스 계층 구조

SQLObject (루트 인터페이스: 모든 AST 노드의 기본)
 └── SQLStatement (구문 최상위 인터페이스)
      ├── SQLStatementImpl (구문 기본 추상 구현)
      │
      ├── DML (데이터 조작어)
      │    ├── SQLSelectStatement   (SELECT 쿼리)
      │    ├── SQLInsertStatement   (INSERT 삽입)
      │    ├── SQLUpdateStatement   (UPDATE 갱신)
      │    └── SQLDeleteStatement   (DELETE 삭제)
      │
      ├── DDL (데이터 정의어)
      │    ├── SQLCreateTableStatement    (CREATE TABLE)
      │    ├── SQLAlterTableStatement     (ALTER TABLE)
      │    ├── SQLDropStatement           (DROP TABLE/INDEX)
      │    ├── SQLCreateIndexStatement    (CREATE INDEX)
      │    └── SQLTruncateStatement       (TRUNCATE TABLE)
      │
      ├── DCL (데이터 제어어)
      │    ├── SQLGrantStatement    (권한 부여)
      │    └── SQLRevokeStatement   (권한 회수)
      │
      └── 기타 구문
           ├── SQLSetStatement    (SET 변수)
           ├── SQLCallStatement   (CALL 프로시저)
           └── SQLCommitStatement (COMMIT 트랜잭션)

2. 주요 서브클래스 상세 분석

2.1 SQLSelectStatement (SELECT 쿼리)

SELECT 문의 모든 구조(컬럼, 테이블, 조건, 정렬, 페이지네이션)를 캡슐화합니다.

  • 핵심 속성:
    • select (SQLSelect): 전체 쿼리 본체 (UNION 포함)
    • queryBlock (SQLSelectQueryBlock): 단일 쿼리 블록
    • selectList (List<SQLSelectItem>): 조회 컬럼 목록
    • from (SQLTableSource): 테이블 소스
    • where (SQLExpr): WHERE 조건 표현식
    • orderBy (SQLOrderBy): 정렬 조건
    • limit (SQLLimit): LIMIT 절
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.util.JdbcConstants;

public class SelectParsing {
    public static void main(String[] args) {
        String rawSql = "SELECT id, name, age FROM users WHERE age > 18 ORDER BY id DESC LIMIT 10";
        
        SQLSelectStatement statement = (SQLSelectStatement) SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
        SQLSelectQueryBlock queryBlock = statement.getSelect().getQueryBlock();
        
        System.out.println("대상 테이블: " + queryBlock.getFrom());
        System.out.println("조회 컬럼: " + queryBlock.getSelectList());
        System.out.println("WHERE 조건: " + queryBlock.getWhere());
        System.out.println("정렬: " + queryBlock.getOrderBy());
        System.out.println("페이지네이션: " + queryBlock.getLimit());
    }
}

2.2 SQLInsertStatement (INSERT 삽입)

INSERT INTO ... VALUES 및 INSERT INTO ... SELECT 구문을 캡슐화합니다.

  • 핵심 속성:
    • tableSource (SQLExprTableSource): 대상 테이블
    • columns (List<SQLExpr>): 삽입 컬럼
    • valuesList (List<SQLInsertValuesClause>): VALUES 데이터
    • query (SQLSelect): INSERT ... SELECT의 쿼리
String sql = "INSERT INTO users(id, name) VALUES (1, 'Kim'), (2, 'Lee')";
SQLInsertStatement stmt = (SQLInsertStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);

System.out.println("테이블: " + stmt.getTableSource());
System.out.println("컬럼: " + stmt.getColumns());
System.out.println("데이터: " + stmt.getValuesList());

2.3 SQLUpdateStatement (UPDATE 갱신)

UPDATE ... SET ... WHERE 구문을 캡슐화합니다.

  • 핵심 속성:
    • tableSource (SQLTableSource): 갱신 대상 테이블
    • items (List<SQLUpdateSetItem>): SET 절의 컬럼=값 쌍
    • where (SQLExpr): 갱신 조건
    • limit (SQLLimit): MySQL LIMIT
String sql = "UPDATE users SET name = 'Park' WHERE id = 1";
SQLUpdateStatement stmt = (SQLUpdateStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);

// SET 값 변경
stmt.getItems().get(0).setValue(SQLUtils.toSQLExpr("'Choi'"));
stmt.setWhere(SQLUtils.toSQLExpr("id = 3"));

System.out.println(SQLUtils.toSQLString(stmt));
// 출력: UPDATE users SET name = 'Choi' WHERE id = 3

2.4 SQLDeleteStatement (DELETE 삭제)

DELETE FROM ... WHERE 구문을 캡슐화합니다.

String sql = "DELETE FROM users WHERE age < 20 LIMIT 5";
SQLDeleteStatement stmt = (SQLDeleteStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);

System.out.println("테이블: " + stmt.getTableSource());
System.out.println("조건: " + stmt.getWhere());
System.out.println("제한: " + stmt.getLimit());

2.5 SQLCreateTableStatement (CREATE TABLE)

테이블 생성 구문을 캡슐화하며 컬럼 정의, 기본 키, 인덱스, 엔진 등을 포함합니다.

String sql = "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100)) ENGINE=InnoDB CHARSET=utf8mb4";
SQLCreateTableStatement stmt = (SQLCreateTableStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);

System.out.println("테이블명: " + stmt.getTableSource());
System.out.println("컬럼 정의: " + stmt.getTableElementList());
System.out.println("엔진: " + stmt.getEngine());
System.out.println("문자셋: " + stmt.getCharset());

2.6 기타 유용한 서브클래스

클래스용도핵심 속성
SQLAlterTableStatement테이블 변경items (ALTER 작업 목록)
SQLDropStatement테이블/인덱스 삭제name (대상 이름)
SQLSetStatement변수 설정target, value
SQLCallStatement저장 프로시저 호출procedureName, parameters

3. 유틸리티 메서드 템플릿

// SQL 문장 리스트 파싱
public static List<SQLStatement> parseStatements(String sql) {
    return SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
}

// 타입 안전 검사
public static void inspectStatementType(SQLStatement stmt) {
    if (stmt instanceof SQLSelectStatement) {
        System.out.println("SELECT 문입니다.");
    } else if (stmt instanceof SQLInsertStatement) {
        System.out.println("INSERT 문입니다.");
    } else if (stmt instanceof SQLUpdateStatement) {
        System.out.println("UPDATE 문입니다.");
    } else if (stmt instanceof SQLDeleteStatement) {
        System.out.println("DELETE 문입니다.");
    }
}

4. Visitor 패턴을 활용한 AST 탐색

Druid는 Visitor 패턴을 지원하여 SQL 구조를 유연하게 탐색할 수 있습니다. SQL 감사, 데이터 권한 필터링, 멀티 테넌시 등에 활용됩니다.

import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter;

stmt.accept(new SQLASTVisitorAdapter() {
    @Override
    public boolean visit(SQLExprTableSource tableSource) {
        System.out.println("참조 테이블: " + tableSource.getExpr());
        return true;
    }
});

5. 주의사항

  • 데이터베이스 방언 필수 지정: JdbcConstants.MYSQL, ORACLE 등을 정확히 설정해야 합니다.
  • 타입 캐스팅 전 검증: instanceof 연산자로 타입을 확인 후 캐스팅합니다.
  • SQLUtils 활용: parseSingleStatement()(단일 구문 파싱), toSQLString()(객체→SQL), toSQLExpr()(조건식 생성)을 적극 사용하세요.
  • 스레드 안전성: SQLStatement 객체는 스레드 간 공유가 불가능합니다.

태그: Druid SQLStatement AST SQL파싱 java

6월 18일 21:02에 게시됨