本篇主要介绍Druid采用访问者模式解析SQL,访问者模式,是行为型设计模式之一。访问者模式是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,这里关于访问者模式不做过多解释,有兴趣的童靴可以去网上查阅。
访问者模式中,接口中定义对象元素方法,每一个元素对应一个方法,供访问者访问,Druid visitor顶层结构,选取部分接口中方法
import com.alibaba.druid.sql.ast.SQLArgument;
import com.alibaba.druid.sql.ast.SQLArrayDataType;
import com.alibaba.druid.sql.ast.SQLCommentHint;
import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLDeclareItem;
import com.alibaba.druid.sql.ast.SQLKeep;
import com.alibaba.druid.sql.ast.SQLLimit;
public interface SQLASTVisitor {
void endVisit(SQLAllColumnExpr x);
void endVisit(SQLBetweenExpr x);
void endVisit(SQLBinaryOpExpr x);
boolean visit(SQLDeleteStatement x);
boolean visit(SQLInsertStatement x);
boolean visit(SQLBetweenExpr x);
.....
}
SQLASTVisitor接口中对SQL ast语法树中所有元素提供了方法,从SQLASTVisitor接口引入的类可以看到ast语法树
select * from t where id between 'x' and 'y'
使用visitor模式访问,则会自动访问boolean visit(SQLBetweenExpr x);
druid对visitor提供了默认实现SQLASTVisitorAdapter,这样我们实现自定义visitor时,不用实现所有方法,只需要继承adapter,重写方法就可以
public class SQLASTVisitorAdapter implements SQLASTVisitor {
protected int features;
public void endVisit(SQLAllColumnExpr x) {
}
public void endVisit(SQLBetweenExpr x) {
}
public void endVisit(SQLBinaryOpExpr x) {
}
public void endVisit(SQLCaseExpr x) {
}
public void endVisit(SQLCaseExpr.Item x) {
}
}
Druid已经对不同的数据库实现了默认的visitor,对于符合SQL92标准的SQL,基本不用重写visitor,利用已经实现的就可以获取条件等解析结果,如MySql,仅展示部分代码,具体可以看Druid源码
public class MySqlSchemaStatVisitor extends SchemaStatVisitor implements MySqlASTVisitor {
public MySqlSchemaStatVisitor() {
super (JdbcConstants.MYSQL);
}
public boolean visit(SQLSelectStatement x) {
if (repository != null
&& x.getParent() == null) {
repository.resolve(x);
}
return true;
}
}
从visitor中可以获取解析结果
当默认实现的visitor不满足我们需求时,可以自定义实现visitor,三种方式实现自定义visitor
下面主要将实现通过extends visitor适配SQLASTVisitorAdapter实现visitor
举个简单栗子,比如我们现在的需求是检查SQL中是否含有group by 关键字,如果按照语法树AST解析的话,处理起来很费周折,这块可以用自定义visitor实现
/**
* @author Qi.qingshan
* @date 2020/4/25
*/
public class SQLGroupbyVisitor extends SQLASTVisitorAdapter {
private boolean hasGroupby = false;
@Override
public boolean visit(SQLSelectGroupByClause x) {
this.hasGroupby = true;
return false;
}
public boolean isHasGroupby() {
return hasGroupby;
}
}
看起来很简单吧,如果要是自己通过遍历语法树AST,处理起来很费周折,写个Test测试下
@Test
public void testParse() {
SQLGroupbyVisitor visitor = new SQLGroupbyVisitor();
String sql = "select sum(id),name from t_order group by name";
SQLStatement sqlStatement = parse(sql);
sqlStatement.accept(visitor);
Assert.assertEquals(true, visitor.isHasGroupby());
}
private static SQLStatement parse(final String sql) throws SQLParseException {
try {
SQLStatementParser parser = new SQLStatementParser(sql, "mysql");
return parser.parseStatement(true);
} catch (Throwable e) {
throw new RuntimeException("SQL parse exception ", e);
}
}
上次有个CSDN上读者留言说需要解析SQL中字段的别名,通过解析AST语法树的方式处理起来很麻烦,有没有什么好的办法,这里可以通过visitor的方式去实现,重写对应的方法即可,将解析结果存储在visitor中,对外提供访问方法即可,关于自定义实现visitor就介绍这么多,觉得有用,点个赞再走_,有问题可以留言。