用 Druid 解析 sql 语句

在工作中遇到一个任务,需要读取.sql文件中的sql语句,对其进行分析,比如,得到这条sql语句是对哪个表进行什么操作,该操作有没有相应的where语句等。这其实是一个对sql语句进行词法分析、语法分析的过程。如果认真研究,那会是大学所学的编译原理的内容了。

在此,为了完成任务,我在网上找了一圈,发现在解析sql语法上,有两个常用的工具:

1. sqlparser

此工具应该是很强大的,本人没用过,因为要收费=_=

具体可以参考官网 :http://www.sqlparser.com/

2. Druid

Druid 是阿里巴巴的开源项目,免费,也是本文想讲的工具。

本文主要是本人在实践中总结出来的关于此工具的用法,如有纰漏,请评论指正。

本人也力求通过这一篇文章,就可以让读者能快速掌握此sql解析工具的实战。

 

利用 Druid 解析 sql语法  

首先在项目中得引入jar包,maven如下:


     com.alibaba
     druid
     1.1.21

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 tableStatMap = visitor.getTables();
        for(Map.Entry 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 columnExprs = insertStatement.getColumns();
        System.out.println("列的名称为:");
        for(SQLExpr expr : columnExprs){
            System.out.print(expr + "\t");
        }
        System.out.println();

        //获取插入的值
        List valuesClauseList = insertStatement.getValuesList();
        System.out.println("值分别是:");
        for(SQLInsertStatement.ValuesClause valuesClause : valuesClauseList){
            List 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 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 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 语句的实战总结。

 

 

 

 

 

你可能感兴趣的:(Druid,sql,sql)