在工作中遇到一个任务,需要读取.sql文件中的sql语句,对其进行分析,比如,得到这条sql语句是对哪个表进行什么操作,该操作有没有相应的where语句等。这其实是一个对sql语句进行词法分析、语法分析的过程。如果认真研究,那会是大学所学的编译原理的内容了。
在此,为了完成任务,我在网上找了一圈,发现在解析sql语法上,有两个常用的工具:
此工具应该是很强大的,本人没用过,因为要收费=_=
具体可以参考官网 :http://www.sqlparser.com/
Druid 是阿里巴巴的开源项目,免费,也是本文想讲的工具。
本文主要是本人在实践中总结出来的关于此工具的用法,如有纰漏,请评论指正。
本人也力求通过这一篇文章,就可以让读者能快速掌握此sql解析工具的实战。
首先在项目中得引入jar包,maven如下:
com.alibaba
druid
1.1.21
Druid是一个数据源,解析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 接口的子类众多,如 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 语句的实战总结。