在工作中遇到一個任務,需要讀取.sql文件中的sql語句,對其進行分析,比如,得到這條sql語句是對哪個表進行什麼操作,該操作有沒有相應的where語句等。這其實是一個對sql語句進行詞法分析、語法分析的過程。如果認真研究,那會是大學所學的編譯原理的內容了。
在此,爲了完成任務,我在網上找了一圈,發現在解析sql語法上,有兩個常用的工具:
1. sqlparser
此工具應該是很強大的,本人沒用過,因爲要收費=_=
具體可以參考官網 :http://www.sqlparser.com/
2. Druid
Druid 是阿里巴巴的開源項目,免費,也是本文想講的工具。
本文主要是本人在實踐中總結出來的關於此工具的用法,如有紕漏,請評論指正。
本人也力求通過這一篇文章,就可以讓讀者能快速掌握此sql解析工具的實戰。
利用 Druid 解析 sql語法
首先在項目中得引入jar包,maven如下:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
Druid是一個數據源,解析sql語法 ,只是裏面一個小小功能而已。
廢話不多說,直接看具體的常用操作~
一、格式化sql語句
格式化sql語句,具體是把sql中的關鍵字變成大寫,如下代碼:
@Test
public void testFormatMysql(){
String sql = "insert into table_test_1 select * from table_test_2;";
String result = SQLUtils.format(sql, JdbcConstants.MYSQL);
System.out.println("格式化後輸出:\n" + result);
}
輸入結果如下:
格式化後輸出:
INSERT INTO table_test_1
SELECT *
FROM table_test_2;
這對於後續處理還是比較重要的,因爲sql的關鍵字,大小寫並不敏感,通過這個方法就可以把關鍵字統一變成大寫。
二、獲取表名和操作
@Test
public void testGetTableNameAndOperation(){
String sql = "SELECT * FROM table_test_1;";
// String sql = "insert into table_test_1 select * from table_test_2;";
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement sqlStatement = parser.parseStatement();
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
sqlStatement.accept(visitor);
Map<TableStat.Name, TableStat> tableStatMap = visitor.getTables();
for(Map.Entry<TableStat.Name, TableStat> tableStatEntry: tableStatMap.entrySet()){
System.out.println("表名:" + tableStatEntry.getKey().getName());
System.out.println("操作名:" + tableStatEntry.getValue());
}
}
上述代碼得到的結果是:
表名:table_test_1
操作名:Select
值得一提的是,如果一條sql語句中有多個表,或者多個操作,如:
insert into table_test_1 select * from table_test_2;
那麼,返回結果是:
表名:table_test_1
操作名:Insert
表名:table_test_2
操作名:Select
這是得到多個表名和多個操作名的辦法 。
注意:
1. MySqlStatementParser parser = new MySqlStatementParser(sql)
MySqlStatementParser 繼承 SQLStatementParser,點到SQLStatementParser類中,可以發現該類下有很多子類,常用的數據庫都有一個對應的解析器。這裏,我們所用的數據庫是mysql,所以這裏用了MySqlStatementParser。
2. SQLStatement statement = parser.parseStatement()
這裏的 SQLStatement 是一個接口,他的子類很多,對於進一步分析sql語句的內容至關重要,我們在下面第三點詳細介紹此接口。
三、關於 SQLStatement
SQLStatement 接口的子類衆多,如 MySqlInsertStatement, SQLAlterTableStatement, SQLSelectStatement 等,可以說,每一種操作,都能在 SQLStatement 中找到對應的子類。正是通過這些子類,我們可以深入分析相應的sql語句。
下面舉幾個例子。
1. MySqlInsertStatement
處理insert語句,我們可能通過這個類,獲取insert 語句所操作的columns,以及插入的value的各項值。看下面代碼:
@Test
public void testInsert(){
String sql = "insert into table_test_2 (farendma, hesuandm, hesuanmc, weihguiy, weihjigo, weihriqi, shijchuo) values\n" +
"('99996','HS205301','代碼1','S####','101001','20140101',1414673101376), \n" +
"('99996','HS205401','代碼2','S####','101001','20140101',1414673101376);";
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement sqlStatement = parser.parseStatement();
MySqlInsertStatement insertStatement = (MySqlInsertStatement)sqlStatement;
//獲取列的名稱
List<SQLExpr> columnExprs = insertStatement.getColumns();
System.out.println("列的名稱爲:");
for(SQLExpr expr : columnExprs){
System.out.print(expr + "\t");
}
System.out.println();
//獲取插入的值
List<SQLInsertStatement.ValuesClause> valuesClauseList = insertStatement.getValuesList();
System.out.println("值分別是:");
for(SQLInsertStatement.ValuesClause valuesClause : valuesClauseList){
List<SQLExpr> valueExprList = valuesClause.getValues();
for(SQLExpr expr : valueExprList){
System.out.print(expr + "\t");
}
System.out.println();
}
}
輸出結果:
列的名稱爲:
farendma hesuandm hesuanmc weihguiy weihjigo weihriqi shijchuo
值分別是:
'99996' 'HS205301' '代碼1' 'S####' '101001' '20140101' 1414673101376
'99996' 'HS205401' '代碼2' 'S####' '101001' '20140101' 1414673101376
有一點值得注意,我們這裏得到的列名稱、值都是SQLExpr對象,如果你想得到該對象對應的String字符串,可以用 toString()方法。
但用 toString()方法總是有點不太優雅,畢竟,toString只是展示此對象的字符串形式,且以後Druid升級的過程中,toString可能會發生一些小變化。
在此,可以用SQLExpr類中的output()方法實現我們所需的功能,把列、值用字符串表示出來。
對獲取列的代碼進行小改動:
//獲取列的名稱
List<SQLExpr> columnExprs = insertStatement.getColumns();
System.out.println("列的名稱爲:");
for(SQLExpr expr : columnExprs){
StringBuffer buffer = new StringBuffer();
expr.output(buffer);
System.out.print(buffer + "\t");
}
System.out.println();
可以看到,output()需要一個StringBuffer來接收SQLExpr的字符串形式。
2. MySqlUpdateStatement & MySqlDeleteStatement
對於update 和 delete,最主要的,可能是獲取他們的where 字句,以 MySqlUpdateStatement 爲例:
@Test
public void testUpdate(){
String sql = "UPDATE table_test_3 SET run_title='maple' WHERE run_id = '1';";
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement sqlStatement = parser.parseStatement();
MySqlUpdateStatement updateStatement = (MySqlUpdateStatement)sqlStatement;
SQLExpr whereExpr = updateStatement.getWhere();
if(whereExpr instanceof SQLInListExpr){
// SQLInListExpr 指 run_id in ('1', '2') 這一情況
SQLInListExpr inListExpr = (SQLInListExpr)whereExpr;
List<SQLExpr> valueExprs = inListExpr.getTargetList();
for(SQLExpr expr : valueExprs){
System.out.print(expr + "\t");
}
} else {
// SQLBinaryOpExpr 指 run_id = '1' 這一情況
SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) whereExpr;
System.out.println(binaryOpExpr.getLeft() + " --> " + binaryOpExpr.getRight());
}
}
3. SQLAlterTableStatement
對於alter操作,我們可以通過此子類獲取其修改的索引名、列名等。
因爲一個ALTER語句可以有多個子修改語句,所以,要獲取它的修改列表,遍歷其修改項。
比如:
ALTER table test_check1
ADD INDEX ind1(farendma),
ADD co2 VARCHAR(20),
DROP INDEX id3,
DROP COLUMN co1;
這個ALTER語句就有4個子修改語句。
下面是代碼展示:
@Test
public void testAlter() {
String sql = "alter table test_check1 ADD index ind1(farendma), add co2 VARCHAR(20), drop INDEX id3, drop column co1;";
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement statement = parser.parseStatement();
SQLAlterTableStatement alter = (SQLAlterTableStatement)statement;
for (SQLAlterTableItem item : alter.getItems()) {
if (item instanceof SQLAlterTableDropIndex) {
SQLAlterTableDropIndex dropIndex = (SQLAlterTableDropIndex) item;
System.out.println("刪除的索引名爲: " + dropIndex.getIndexName());
} else if (item instanceof SQLAlterTableDropColumnItem){
SQLAlterTableDropColumnItem dropColumn = (SQLAlterTableDropColumnItem)item;
System.out.println("刪除的列名爲: " + dropColumn.getColumns());
} else if (item instanceof SQLAlterTableAddIndex) {
SQLAlterTableAddIndex addIndex = (SQLAlterTableAddIndex) item;
if (addIndex.getName() != null) {
String indexName = addIndex.getName().getSimpleName();
System.out.println("新增的索引名爲 : " + indexName);
}
} else if (item instanceof SQLAlterTableAddColumn) {
SQLAlterTableAddColumn addColumn = (SQLAlterTableAddColumn) item;
System.out.println("新增的列爲: " + addColumn.getColumns());
}
}
}
上面用到的SQLAlterTableStatement有四個:
SQLAlterTableDropIndex 通過ALTER 刪除索引
SQLAlterTableDropColumnItem 通過ALTER刪除列
SQLAlterTableAddIndex 通過ALTER 添加索引
SQLAlterTableAddColumn 通過ALTER 添加列
以上便是對Druid 分析 sql 語句的實戰總結。