用 Druid 解析 sql 語句

在工作中遇到一個任務,需要讀取.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 語句的實戰總結。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章