最近参与一个开源项目,一个功能的实现,用到了 druid 解析器来解析SQL,记录下如果使用 druid 来解析SQL,实现对SQL的拦截改写。

1. 对 insert 语句进行解析:

	private static String convertInsertSQL(String sql){
try{
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement statement = parser.parseStatement();
MySqlInsertStatement insert = (MySqlInsertStatement)statement;
String tableName = StringUtil.removeBackquote(insert.getTableName().getSimpleName());
if(!isGlobalTable(tableName))
return sql;
if(!isInnerColExist(tableName))
return sql; List<SQLExpr> columns = insert.getColumns();
if(columns == null || columns.size() <= 0)
return sql; if(insert.getQuery() != null) // insert into tab select
return sql; StringBuilder sb = new StringBuilder(200) // 指定初始容量可以提高性能
.append("insert into ")
.append(tableName).append("(");
int idx = -1;
for(int i = 0; i < columns.size(); i++) {
if(i < columns.size() - 1)
sb.append(columns.get(i).toString()).append(",");
else
sb.append(columns.get(i).toString());
String column = StringUtil.removeBackquote(insert.getColumns().get(i).toString());
if(column.equalsIgnoreCase(GLOBAL_TABLE_MYCAT_COLUMN))
idx = i;
}
if(idx <= -1)
sb.append(",").append(GLOBAL_TABLE_MYCAT_COLUMN);
sb.append(")"); sb.append(" values");
List<ValuesClause> vcl = insert.getValuesList();
if(vcl != null && vcl.size() > 1){ // 批量insert
for(int j=0; j<vcl.size(); j++){
if(j != vcl.size() - 1)
appendValues(vcl.get(j).getValues(), sb, idx).append(",");
else
appendValues(vcl.get(j).getValues(), sb, idx);
}
}else{ // 非批量 insert
List<SQLExpr> valuse = insert.getValues().getValues();
appendValues(valuse, sb, idx);
} List<SQLExpr> dku = insert.getDuplicateKeyUpdate();
if(dku != null && dku.size() > 0){
sb.append(" on duplicate key update ");
for(int i=0; i<dku.size(); i++){
SQLExpr exp = dku.get(i);
if(exp != null){
if(i < dku.size() - 1)
sb.append(exp.toString()).append(",");
else
sb.append(exp.toString());
}
}
} return sb.toString();
}catch(Exception e){ // 发生异常,则返回原始 sql
LOGGER.warn(e.getMessage());
return sql;
}
}

三行代码就可以解析一条insert语句:

MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement statement = parser.parseStatement();
MySqlInsertStatement insert = (MySqlInsertStatement)statement;

然后使用解析得到的 insert ,就可以获得原始insert语句的各个部分:

List<SQLExpr> columns = insert.getColumns();  // 获得所有列名

insert.getQuery(); // 如果是 insert into select 语句,则可以获取 select查询

如果是批量插入的insert:insert into tab(id,name) values(1,'a'),(2,'b'),(3,'c');

则可以使用:

List<ValuesClause> vcl = insert.getValuesList();

获得素有的 values 子句部分。

非批量插入,则可以使用:

List<SQLExpr> valuse = insert.getValues().getValues();

获得 values 子句。

on duplicate 部分可以使用下面的语句获取:

List<SQLExpr> dku = insert.getDuplicateKeyUpdate();

获得了这些,就而已重组得到原始SQL语句,并且对其进行各种改写。

mysql 中的insert语法如下:

mysql> ? insert
Name: 'INSERT'
Description:
Syntax:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name,...)]
[(col_name,...)]
{VALUES | VALUE} ({expr | DEFAULT},...),(...),...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ] Or: INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name,...)]
SET col_name={expr | DEFAULT}, ...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ] Or: INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name,...)]
[(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ]

2. 解析 update 语句:

public static String convertUpdateSQL(String sql){
try{
MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement stmt = parser.parseStatement();
MySqlUpdateStatement update = (MySqlUpdateStatement)stmt;
SQLTableSource ts = update.getTableSource();
if(ts != null && ts.toString().contains(",")){
System.out.println(ts.toString());
LOGGER.warn("Do not support Multiple-table udpate syntax...");
return sql;
} String tableName = StringUtil.removeBackquote(update.getTableName().getSimpleName());
if(!isGlobalTable(tableName))
return sql;
if(!isInnerColExist(tableName))
return sql; // 没有内部列 StringBuilder sb = new StringBuilder(150); SQLExpr se = update.getWhere();
// where中有子查询: update company set name='com' where id in (select id from xxx where ...)
if(se instanceof SQLInSubQueryExpr){
// return sql;
int idx = sql.toUpperCase().indexOf(" SET ") + 5;
sb.append(sql.substring(0, idx)).append(GLOBAL_TABLE_MYCAT_COLUMN)
.append("=").append(operationTimestamp)
.append(",").append(sql.substring(idx));
return sb.toString();
}
String where = null;
if(update.getWhere() != null)
where = update.getWhere().toString(); SQLOrderBy orderBy = update.getOrderBy();
Limit limit = update.getLimit(); sb.append("update ").append(tableName).append(" set ");
List<SQLUpdateSetItem> items = update.getItems();
boolean flag = false;
for(int i=0; i<items.size(); i++){
SQLUpdateSetItem item = items.get(i);
String col = item.getColumn().toString();
String val = item.getValue().toString(); if(StringUtil.removeBackquote(col)
.equalsIgnoreCase(GLOBAL_TABLE_MYCAT_COLUMN)){
flag = true;
sb.append(col).append("=");
if(i != items.size() - 1)
sb.append(operationTimestamp).append(",");
else
sb.append(operationTimestamp);
}else{
sb.append(col).append("=");
if(i != items.size() -1 )
sb.append(val).append(",");
else
sb.append(val);
}
} if(!flag){
sb.append(",").append(GLOBAL_TABLE_MYCAT_COLUMN)
.append("=").append(operationTimestamp);
} sb.append(" where ").append(where); if(orderBy != null && orderBy.getItems()!=null
&& orderBy.getItems().size() > 0){
sb.append(" order by ");
for(int i=0; i<orderBy.getItems().size(); i++){
SQLSelectOrderByItem item = orderBy.getItems().get(i);
SQLOrderingSpecification os = item.getType();
sb.append(item.getExpr().toString());
if(i < orderBy.getItems().size() - 1){
if(os != null)
sb.append(" ").append(os.toString());
sb.append(",");
}else{
if(os != null)
sb.append(" ").append(os.toString());
}
}
} if(limit != null){ // 分为两种情况: limit 10; limit 10,10;
sb.append(" limit ");
if(limit.getOffset() != null)
sb.append(limit.getOffset().toString()).append(",");
sb.append(limit.getRowCount().toString());
} return sb.toString();
}catch(Exception e){
LOGGER.warn(e.getMessage());
return sql;
}
}

同样三行,解析update语句:

MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement stmt = parser.parseStatement();
MySqlUpdateStatement update = (MySqlUpdateStatement)stmt;

如果是 多表 udpate 语句,可以使用下面的语句进行判断:

SQLTableSource ts = update.getTableSource();
if(ts != null && ts.toString().contains(",")){
   System.out.println(ts.toString());
   LOGGER.warn("Do not support Multiple-table udpate syntax...");
   return sql;
}

如果是单表update语句:

获得 update 语句的 where 部分:

SQLExpr se = update.getWhere();
// where中有子查询: update company set name='com' where id in (select id from xxx where ...)
if(se instanceof SQLInSubQueryExpr){
// return sql;
int idx = sql.toUpperCase().indexOf(" SET ") + 5;
sb.append(sql.substring(0, idx)).append(GLOBAL_TABLE_MYCAT_COLUMN)
.append("=").append(operationTimestamp)
.append(",").append(sql.substring(idx));
return sb.toString();
}
String where = null;
if(update.getWhere() != null)
where = update.getWhere().toString();

如果where 部分由 select 语句,由:se instanceof SQLInSubQueryExpr 来判断。

order by 和 limit 部分分别由:

SQLOrderBy orderBy = update.getOrderBy();
Limit limit = update.getLimit();

获得。

update 对应的 列和值,有下面的代码获得:

boolean flag = false;
for(int i=0; i<items.size(); i++){
  SQLUpdateSetItem item = items.get(i);
  String col = item.getColumn().toString();
  String val = item.getValue().toString();

解析得到了这些部分,就可以重组出原始的 update 语句,并且按照自己的需求进行SQL改写。

3. 解析 alter 语句:

String sql = "alter table t add colomn name varchar(30)";

MySqlStatementParser parser = new MySqlStatementParser(sql);
SQLStatement statement = parser.parseStatement();
MySqlAlterTableStatement alter = (MySqlAlterTableStatement)statement;
SQLExprTableSource source = alter.getTableSource();
String tableName = source.toString();

解析器:

        <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.</version>
</dependency>

利用 druid 解析器解析SQL的更多相关文章

  1. JavaScript使用浏览器内置XML解析器解析DOM对象

    所有现代浏览器都内建了供读取和操作 XML 的 XML 解析器.解析器把 XML 转换为 XML DOM 对象 (可通过 JavaScript 操作的对象). 一.获取DOM对象 XMLHttpReq ...

  2. 使用java自带的xml解析器解析xml

    使用java自带的xml解析器解析xml,其实我不推荐,可以用Dom解析,或其他的方式,因为Java自带的解析器使用不但麻烦,且还有bug出现. 它要求,针对不同的xml(结构不同),必须写对应的ha ...

  3. HandlerMethodReturnValueHandler SpringMVC 参数解析 继承关系以及各解析器解析类型

    I HandlerMethodReturnValueHandler (org.springframework.web.method.support) AbstractMessageConverterM ...

  4. HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型

    HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型 I HandlerMethodArgumentResolver (org.spr ...

  5. Java DOM解析器 - 解析XML文档

    使用DOM的步骤 以下是在使用DOM解析器解析文档使用的步骤. 导入XML相关的软件包. 创建DocumentBuilder 从文件或流创建一个文档 提取根元素 检查属性 检查子元素 导入XML相关的 ...

  6. 安卓使用pull解析器解析XML文件

    学习一下: public class MainActivity extends Activity { List<City> cityList; @Override protected vo ...

  7. 15_采用Pull解析器解析和生成XML内容

    java还提供SAX和DOM用于解析XML Android还集成了Pull解析器——推荐 package cn.itcast.service; import java.io.InputStream; ...

  8. 用PULL解析器解析XML文件

    第一种方式(简洁,直接用pullparser.nextText()来返回下一个String类型的值): 1 package lee.service; 2 3 import java.io.InputS ...

  9. android pull 解析器解析xml文档

    person.xml <?xml version="1.0" encoding="UTF-8"?> <persons> <pers ...

随机推荐

  1. 如何很好的Review自己的代码

    写这篇博文的原因是因为自己写的代码经常会因为返工,delay项目的交付日期.总结了一下引起项目delay的原因,大概有如下几点: 在没有完全深熟悉需求交互细节的情况下:诸如根据不同渠道设置不同的订单状 ...

  2. PHP内核探索之变量(2)-理解引用

    本文主要内容: 引论 符号表与zval 引用原理 回到最初的问题 一.引论 很久之前写了一篇关于引用的文章,当时写的寥寥草草,很多原理都没有说清楚.最近在翻阅Derick Rethans(home: ...

  3. GJM : Lua 语言学习笔记

    Lua笔记 容易与C/C++整合 Lua所提供的机制是C所不善于的:高级语言,动态结构,简洁,易于测试和调试. Lua特有的特征: `1:可扩展性.卓越的扩展性导致了很多人将Lua用作搭建领域语言的工 ...

  4. .NET DLR 上的IronScheme 语言互操作&&IronScheme控制台输入中文的问题

    前言 一直以来对Lisp语言怀有很崇敬的心里,<黑客与画家>对Lisp更是推崇备至,虽然看了不少有关Lisp的介绍但都没有机会去写段程序试试,就像我对C++一样,多少有点敬畏.这个周末花了 ...

  5. C++之面向对象的三个基本特征

    三大特性是:封装,继承,多态 所谓封装 就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏.封装是面向对象的特征之一,是对象和类概念的主要特性. ...

  6. Remodal – 支持 Hash 追踪的响应式模态窗口

    Remodal 是一个扁平化,响应式,轻量而且容易定制的模态窗口插件,支持使用声明状态和 Hash 跟踪.您可以轻松地定义为模态弹窗定义背景景容器(如模糊效果).支持所有现代的浏览器. 您可能感兴趣的 ...

  7. 12款最佳的 WordPress 语法高亮插件推荐

    语法高亮工具增强了代码的可读性,美化了代码,让程序员更容易维护.语法高亮提供各种方式由以提高可读性和文本语境,尤其是对于其中可以结束跨越多个页面的代码,以及让开发者自己的程序中查找错误.在这篇文章中, ...

  8. ie7下<a></a>标签中<input />时不反应

    <a href="News?id=@dr["id"].CInt()" ><input type="button" valu ...

  9. javascript --- 原型初探七日谈(一)

    在javascript中,像原型,闭包这样的概念,只要我们能领悟其中的原理,一切都会显得格外清晰与明了. 原型属性(prototype): 下面我们简单定义一个函数 function her(a, b ...

  10. Arcgis Engine 切割图斑(ITool)

    public override void OnMouseDown(int Button, int Shift, int X, int Y) { // TODO: Add Cut.OnMouseDown ...