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객체는 스레드 간 공유가 불가능합니다.