druid1.2.24解析复杂SelectSQL语句——表名以及别名解析
用于SelectSQL后续的查询优化
在后端业务中涉及到复杂SQL中的Select表名以及别名解析是一个很重要的一个步骤。我们往往要从union,join的语句中提取到自己想要的信息。
关于SQL解析,这里就必须要提到阿里开源的druid。本文将会使用druid相关的SQL解析方法对一个简单的SQL进行解析,同时也会考虑到复杂SQL。
这里我们从第一步开始。首先我们会拿到一个SQL。比如
String sql = "select id from user a where uid = "123" limit 1";
首先我们需要构造一个用于存储每一个表信息的mapList。
List<Map<String,String>> aliasToTableMapList = Lists.newArrayList();
然后我们开始对druid解析SQL的一些方法进行了解。
首先是SQLStatementParser,我们首先需要通过druid提供的SQL解析类SQLParserUtils创建一个SQL语句解析类。具体方法如下。
SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, "mysql");
这里需要将我们的SQL传入这个创建SQL解析类中,这里可以指定类型,这里可以使用druid官方提供的DbType枚举类进行选择,感兴趣的可以自己去进入源码进行了解。也可以自己直接使用字符串进行指定,比如我使用方式。
接下来就是将我们需要解析的SQL语句中的解析语句列表提取出来。
List<SQLStatement> statementList = parser.parseStatementList();
这里由于我们只有一个简单SelectSQL。所以这里解析出来的列表的长度只有1。
接下来就需要我们对获取到的这个statementList中的statement进行递归遍历获取里面的信息。
for (SQLStatement statement : statementList) {
if (statement instanceof SQLSelectStatement) {
SQLSelect select = ((SQLSelectStatement) statement).getSelect();
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) select.getQuery();
processTableSource(queryBlock.getFrom(), aliasToTableMapList);
}
}
因为本文解析SQL是使用的select语句,这里我们需要确保是Select语句,所以我们需要对这个语句进行instanceof判断。然后需要我们将这个语句中的select取出,然后拿到选择块。具体的类中的一些属性感兴趣的可以自行了解,这里不过多深入。然后我们开始进行递归操作,遍历语句中的每一条语句。
private static void processTableSource(SQLTableSource tableSource, List<Map<String, String>> aliasToTableMapList) {
if (tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLExprTableSource) {
com.alibaba.druid.sql.ast.statement.SQLExprTableSource exprTableSource = (com.alibaba.druid.sql.ast.statement.SQLExprTableSource) tableSource;
String alias = exprTableSource.getAlias();
if (alias != null) {
if (exprTableSource.getExpr() instanceof SQLIdentifierExpr) {
SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) exprTableSource.getExpr();
String tableName = identifierExpr.getName();
Map<String,String> aliasMap = Maps.newHashMap();
aliasMap.put("alias", alias);
aliasMap.put("tableName", tableName)
aliasToTableMapList.add(aliasMap);
}else if (exprTableSource.getExpr() instanceof SQLPropertyExpr) {
SQLPropertyExpr propertyExpr = (SQLPropertyExpr) exprTableSource.getExpr();
if (StringUtils.isNotEmpty(propertyExpr.getOwnerName())) {
Map<String,String> aliasMap = Maps.newHashMap();
aliasMap.put("alias", alias);
aliasMap.put("tableName",propertyExpr.getName());
aliasToTableMapList.add(aliasMap);
}
}
}else{
SQLPropertyExpr propertyExpr = (SQLPropertyExpr) exprTableSource.getExpr();
if(StringUtils.isNotEmpty(propertyExpr.getOwnerName())){
Map<String,String> aliasMap = Maps.newHashMap();
aliasMap.put("tableName",propertyExpr.getName());
aliasToTableMapList.add(aliasMap);
}
}
}
在druid解析SQL的过程中,最后都会走到SQLExprTableSource。这是最后SQL执行的语句。因为你可能存在子查询等操作,所以需要遍历到最底层才可以去提取到表的一些信息,别名,表名等。上面添加别名的操作就不详细解释了,主要就是分为两个部分,一个是没有别名的时候的操作,一个是存在别名的时候的操作(这里面也需要区分两种情况)。
然后就是最关键的地方,就是如何更进一步的递归遍历。
else if (tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLJoinTableSource) {
com.alibaba.druid.sql.ast.statement.SQLJoinTableSource joinTableSource = (com.alibaba.druid.sql.ast.statement.SQLJoinTableSource) tableSource;
processTableSource(joinTableSource.getLeft(), aliasToTableMapList);
processTableSource(joinTableSource.getRight(), aliasToTableMapList);
}else if(tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource){
com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource unionQueryTableSource = (com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource) tableSource;
SQLUnionQuery sqlUnionQuery = unionQueryTableSource.getUnion();
List<SQLSelectQueryBlock> relations = sqlUnionQuery.getRelations().stream().map(item -> (SQLSelectQueryBlock) item).collect(Collectors.toList());
relations.forEach(queryBlock -> {processTableSource(queryBlock.getFrom(), aliasToTableMapList);});
}else if(tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock){
com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock queryBlock = (com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock) tableSource;
processTableSource(queryBlock.getFrom(), aliasToTableMapList);
}
在一个SQL中,不会所有的SQL都会像本文提出的例子一样简单,我们这里需要考虑到一个复杂SelectSQL语句中可能出现的情况。本文主要考虑到了是join、union以及查询嵌套出现子查询的情况。这里最为复杂的就是join以及union。join主要的难点在于对左右连接进行遍历。而union的难点在于它查询连接的数目,所以可以看到在上方主要使用了SQLUnionQueryTableSource中的一个属性值unionQueryTableSource.getUnion().getRelations()。它可以获取到每一个连接中使用到的查询块,然后递归的进行遍历,以获取这个查询块中还可能存在的子查询等。
最后会的到一个结果,存储各个表名。当然,具体的需求还需要具体分析。
本文就到此了,比较浅显,需要深入的还需要各位友友自己去了解了。
3218

被折叠的 条评论
为什么被折叠?



