概覽
由於最近的開發功能涉及SQL解析模塊,在網上查詢了一些有關SQL解析器的解析工具,如:ANTLR、Druid,綜合性能、語法支持度、學習成本等因素,選擇Druid作為SQL解析的工具。
訪問者【VISITOR】模式
Druid采用訪問者模式解析SQL,訪問者模式,是行為型設計模式之一。訪問者模式是一種將數據操作與數據結構分離的設計模式。
訪問者模式基本介紹:
- 訪問者模式(Visitor Pattern):封裝一些作用於某種數據結構的各元素的操作,它可以在不改變數據結構的前提下定義作用於這些元素的新的操作
- 訪問者模式主要將數據結構與數據操作分離,解決數據結構和操作耦合性問題
- 訪問者模式的基本工作原理是:在被訪問的類里面加一個對外提供接待訪問者的接口
- 訪問者模式主要應用場景是: 需要對一個對象結構中的對象進行很多不同操作(這些操作彼此沒有關聯),同時需要避免讓這些操作污染這些對象的類,可以選用訪問者模式解決
訪問者模式的原理類圖:
- Visitor 是抽象訪問者,定義訪問者的行為規范
- ConcreteVisitor :是一個具體的訪問者,繼承(或實現) Visitor,實現 Visitor 中定義的每個方法,實現具體的行為邏輯
- Element 定義一個accept 方法,用於接收一個訪問者對象(Visitor 的具體實現類)
- ConcreteElement 為具體元素, 實現了 Element 接口中 accept 方法
- ObjectStructure 能枚舉它里面所包含的元素(Element), 可以提供一個高層的接口,目的是允許訪問者訪問指定的元素
解析器組成
- Parser
- 詞法分析
- 語法分析
- AST(Abstract Syntax Tree,抽象語法樹)
- Visitor
Parser 由兩部分組成,詞法分析和語法分析。
當拿到一條形如 select id, name from user 的 SQL 語句后,首先需要解析出每個獨立的單詞,select,id,name,from,user。這一部分,稱為詞法分析,也叫作 Lexer。
通過詞法分析后,便要進行語法分析了。
經常能聽到很多人在調侃自己英文水平很一般時會說:26個字母我都知道,但是一組合在一起我就不知道是什么意思了。這說明他掌握了詞法分析的技能,卻沒有掌握語法分析的技能。
那么對於 SQL 解析器來說呢,它不僅需要知道每個單詞,而且要知道這些單詞組合在一起后,表達了什么含義。語法分析的職責就是明確一個語句的語義,表達的是什意思。
自然語言和形式語言的一個重要區別是,自然語言的一個語句,可能有多重含義,而形式語言的一個語句,只能有一個語義;形式語言的語法是人為規定的,有了一定的語法規則,語法解析器就能根據語法規則,解析出一個語句的一個唯一含義。
AST 是 Parser 的產物,語句經過詞法分析,語法分析后,它的結構需要以一種計算機能讀懂的方式表達出來,最常用的就是抽象語法樹。
樹的概念很接近於一個語句結構的表示,一個語句,我們經常會對它這樣看待:它由哪些部分組成?其中一個組成部分又有哪些部分組成?例如一條 select 語句,它由 select 列表、where 子句、排序字段、分組字段等組成,而 select 列表則由一個或多個 select 項組成,where 子句又由一個或者多個 where條件組成。
在我們人類的思維中,這種組成結構就是一個總分的邏輯結構,用樹來表達,最合適不過。並且對於計算機來說,它顯然比人類更擅長處理“樹”。
AST 僅僅是語義的表示,但如何對這個語義進行表達,便需要去訪問這棵 AST,看它到底表達什么含義。通常遍歷語法樹,使用 VISITOR 模式去遍歷,從根節點開始遍歷,一直到最后一個葉子節點,在遍歷的過程中,便不斷地收集信息到一個上下文中,整個遍歷過程完成后,對這棵樹所表達的語法含義,已經被保存到上下文了。有時候一次遍歷還不夠,需要二次遍歷。遍歷的方式,廣度優先的遍歷方式是最常見的。
快速上手
- 構建 Parser
- 使用 Parser 解析 SQL,生成 AST
- 構建 Visitor
- 使用 Visitor 訪問 AST
- 獲取解析信息
@Test
@DisplayName("測試Sql解析")
public void doParserSqlDemo() {
// 待解析 SQL
String selectSql = "select id from user_pay group by name";
// 新建 Parser
SQLStatementParser parser = new SQLStatementParser(selectSql, "mysql");
// 使用 Parser 解析 SQL,生成 AST
SQLStatement sqlStatement = parser.parseStatement();
// 生成訪問者
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
// 使用 Visitor 訪問 AST
sqlStatement.accept(visitor);
// 獲取解析信息
System.out.println(visitor.getColumns());
}
結果
自定義VISITOR,獲取SQL解析信息
有時我們需要獲取SQL的一些特殊信息,無法通過已經實現好的VISITOR獲取,通過解析AST語法樹的方式處理起來很麻煩,可以通過visitor的方式去實現,重寫對應的方法,將解析結果存儲在visitor中,對外提供訪問方法即可。比如:需要獲取SQL語句的limit數量,可以通過以下方式重寫visitor的方式實現。
步驟一:定義自定義VISITOR
// 自定義訪問者
class SQLCustomedVisitor extends SQLASTVisitorAdapter {
protected boolean hasLimit = false;
public SQLCustomedVisitor() {
super();
}
@Override
public boolean visit(SQLLimit x) {
System.out.println("Limit限制條數: " + x.getRowCount());
hasLimit = true;
return false;
}
public boolean isHasLimit() {
return hasLimit;
}
}
步驟二:通過自定義訪問者訪問AST
public void doParserSql() {
// SQL
String limitSql = "select name from user limit 100";
// 解析器
SQLStatementParser parser = new SQLStatementParser(limitSql, "mysql");
// 生成AST
SQLStatement sqlStatement = parser.parseStatement(true);
// 構建自定義訪問者
SQLCustomedVisitor sqlCustomedVisitor = new SQLCustomedVisitor();
// 訪問抽象數,獲取數據
sqlStatement.accept(sqlCustomedVisitor);
Assert.assertEquals(true, sqlCustomedVisitor.hasLimit);
System.out.println("SQL語句: " + sqlStatement);
}
結果