简介

在上篇文章中,我们找到了一个逻辑SQL转换到真实SQL的关键路径代码,本篇文中,我们就上篇基础上,来探索语句解析生成的一些细节

源码解析

语句的关键解析生成的代码如下:

@RequiredArgsConstructor
public abstract class AbstractSQLBuilder implements SQLBuilder {private final SQLRewriteContext context;@Overridepublic final String toSQL() {if (context.getSqlTokens().isEmpty()) {return context.getSql();}Collections.sort(context.getSqlTokens());StringBuilder result = new StringBuilder();result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());for (SQLToken each : context.getSqlTokens()) {result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));result.append(getConjunctionText(each));}return result.toString();}protected abstract String getSQLTokenText(SQLToken sqlToken);private String getComposableSQLTokenText(final ComposableSQLToken composableSQLToken) {StringBuilder result = new StringBuilder();for (SQLToken each : composableSQLToken.getSqlTokens()) {result.append(getSQLTokenText(each));result.append(getConjunctionText(each));}return result.toString();}private String getConjunctionText(final SQLToken sqlToken) {return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken));}private int getStartIndex(final SQLToken sqlToken) {int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex();return Math.min(startIndex, context.getSql().length());}private int getStopIndex(final SQLToken sqlToken) {int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken);return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex();}
}

从toSQL函数可以看到,基本都死用context变量里面去获取生成真实SQL的,其内容大致如下:

其他的TableToken比较丰富,包含了很多的信息,可能是逻辑表名需要转成真实表名,所有需要这么多的信息

我们看到context.getSqlTokens()的长度是2,那result的就会经过三次添加:

  • 未循环遍历处理前的初始添加:result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
  • 第一次循环添加: TableToken
  • 第二次循环添加:
  • 第三次循环添加

下面我们就仔细跟下这三次添加

初始添加

result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());

上面的语句就是从字符串中截取子字符串添加,从原始的SQL截取的,起点是0,结束点是第一个SQLTokens的startIndex

相应的变化如下:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO

这步感觉对于所有的SQL来说确实是通用的,前面这种语句应该都是不用进行解析变换的(如何获取截取的结束点,这个是SQLToken的生成部分,这次先不看,先跟一下处理流程)

第一次循环添加: TableToken

        for (SQLToken each : context.getSqlTokens()) {result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));result.append(getConjunctionText(each));}

从上面的语句看出,目前有两种处理方式:

  • 组合SQLToken的处理: getComposableSQLTokenText
  • 非组合SQLToken的处理: getSQLTokenText

我们跟踪得到直接走的: getSQLTokenText

跟踪进入下面的函数,如果是 RouteUnitAware,还需要进行处理

public final class RouteSQLBuilder extends AbstractSQLBuilder {@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {return ((RouteUnitAware) sqlToken).toString(routeUnit);}return sqlToken.toString();}
}

看看TableToken是如何ToString的,相关的细节如下

public final class TableToken extends SQLToken implements Substitutable, RouteUnitAware {@Overridepublic String toString(final RouteUnit routeUnit) {// 得到了真实的表名,真实表名从逻辑表到真实表的转换Map中获取的String actualTableName = getLogicAndActualTables(routeUnit).get(tableName.getValue().toLowerCase());// 如果真实表名是null,则获取tableName的值(还能这么写成一句,学到了)// tableName是value值是原始的SQL的 t_orderactualTableName = null == actualTableName ? tableName.getValue().toLowerCase() : actualTableName;return tableName.getQuoteCharacter().wrap(actualTableName);}// 从下面的函数大意就可以看出是构建逻辑表名到真实表的映射转换Map private Map<String, String> getLogicAndActualTables(final RouteUnit routeUnit) {Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames();Map<String, String> result = new HashMap<>(tableNames.size(), 1);for (RouteMapper each : routeUnit.getTableMappers()) {result.put(each.getLogicName().toLowerCase(), each.getActualName());// 为啥还要再加一次,这里没有理解result.putAll(shardingRule.getLogicAndActualTablesFromBindingTable(routeUnit.getDataSourceMapper().getLogicName(), each.getLogicName(), each.getActualName(), tableNames));}return result;}
}

return tableName.getQuoteCharacter().wrap(actualTableName) 核心代码大致如下,额外的加一些东西(关键字保留字段处理之类的?)

@Getter
public enum QuoteCharacter {BACK_QUOTE("`", "`"),SINGLE_QUOTE("'", "'"),QUOTE("\"", "\""),BRACKETS("[", "]"),NONE("", "");private final String startDelimiter;private final String endDelimiter;/*** Wrap value with quote character.* * @param value value to be wrapped* @return wrapped value*/public String wrap(final String value) {return startDelimiter + value + endDelimiter;}
}

相应的变化如下:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0

下面来到: result.append(getConjunctionText(each));

public abstract class AbstractSQLBuilder implements SQLBuilder {private String getConjunctionText(final SQLToken sqlToken) {return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken));}private int getStartIndex(final SQLToken sqlToken) {int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex();return Math.min(startIndex, context.getSql().length());}private int getStopIndex(final SQLToken sqlToken) {int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken);return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex();}
}

大意就是获取开始和结束截取点,算法目前还没领会…,但和SQLToken的关系很大,看后面看SQLToken的时候能不能得到解答

相应的变化如下:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status

第二次循环添加:

我们看看第二次循环添加:GeneratedKeyInsertColumnToken

@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {return ((RouteUnitAware) sqlToken).toString(routeUnit);}// 直接走的这return sqlToken.toString();}
}public final class  extends SQLToken implements Attachable {@Overridepublic String toString() {// 简单粗暴的直接插入一列名return String.format(", %s", column);}
}

从上面的大意看出,就是插入一列名,相应的变化如下:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id

接着: result.append(getConjunctionText(each)),变成:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES

第三次循环添加:

我们接着看第三次循环添加:ShardingInsertValuesToken

public final class RouteSQLBuilder extends AbstractSQLBuilder {@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {// 走的这return ((RouteUnitAware) sqlToken).toString(routeUnit);}return sqlToken.toString();}
}public final class ShardingInsertValuesToken extends InsertValuesToken implements RouteUnitAware {public ShardingInsertValuesToken(final int startIndex, final int stopIndex) {super(startIndex, stopIndex);}@Overridepublic String toString(final RouteUnit routeUnit) {StringBuilder result = new StringBuilder();// 这里得到了:(?, ?, ?, ?),appendInsertValue(routeUnit, result);// 然后又变成了:(?, ?, ?, ?)result.delete(result.length() - 2, result.length());return result.toString();}private void appendInsertValue(final RouteUnit routeUnit, final StringBuilder stringBuilder) {for (InsertValue each : getInsertValues()) {if (isAppend(routeUnit, (ShardingInsertValue) each)) {stringBuilder.append(each).append(", ");}}}private boolean isAppend(final RouteUnit routeUnit, final ShardingInsertValue insertValueToken) {if (insertValueToken.getDataNodes().isEmpty() || null == routeUnit) {return true;}for (DataNode each : insertValueToken.getDataNodes()) {if (routeUnit.findTableMapper(each.getDataSourceName(), each.getTableName()).isPresent()) {return true;}}return false;}
}

对于这个目前不懂,为啥要经过这个处理,应用场景是啥?

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)

接着: result.append(getConjunctionText(each)),变成:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)

到这里就解析生成完毕了

总结

本篇中详细查看了逻辑SQL转真实SQL的处理过程:

  • 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
  • result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)

转换中使用了相应的Token类进行对应的处理,组装成真实的SQL

但算法之类的,目前还是不甚了解的,后面找找官方资料,有没有这方面的

下面应该要去看看SQLToken的生成了,通过上面的跟踪发现,真实SQL的生成和SQLToken是息息相关的

ShardingSphere 语句解析生成初探相关推荐

  1. ShardingSphere LogicSQL 的生成探索

    简介 在上两篇文中,我们探索了SQLToken和真实SQL的生成的想关代码,本文继续来探索最开始的一个LogicSQL的生成,补全这一块拼图 源码探索 继续上面两篇的探索: ShardingSpher ...

  2. Java中如何解析SQL语句、格式化SQL语句、生成SQL语句?

    昨天在群里看到有小伙伴问,Java里如何解析SQL语句然后格式化SQL,是否有现成类库可以使用? 之前TJ没有做过这类需求,所以去研究了一下,并找到了一个不过的解决方案,今天推荐给大家,如果您正要做类 ...

  3. Mybatis动态sql语句的生成

    在XMLLanguageDriver的createSQLSource()方法中,可以将已经经过解析的xml节点传入,并且传入相应的参数类型,开始动态sql语句的生成. @Override public ...

  4. mysql 语句解析_MySQL进阶之语句解析顺序

    概述 一条普通的SQL查询语句它在MySQL数据库中是怎么样被解析和执行的呢?下面一起来了解一下,MySQL是如何解析SQL查询语句的,这对理解MySQL的执行计划也大有益处. 解析顺序 下面是一条普 ...

  5. oracle12测试骤,Oracle中SQL语句解析的步骤

    我们都知道在Oracle中每条SQL语句在执行之前都需要经过解析,这里面又分为软解析和硬解析.那么这两种解析有何不同之处呢?它们又分别是如何进行解析呢?Oracle内部解析的步骤又是如何进行的呢?下面 ...

  6. SAP SD微观研究之销售发票自动生成初探

    SAP SD微观研究之销售发票自动生成初探 SAP项目实践中,销售业务里的开票(BILLING)功能,可以根据业务实际设置成自动生成.笔者经历过的项目里,大致有2种不同的实现方式. 一种方式是,将事务 ...

  7. mxOutlookBarPro中,button是通过for语句动态生成的,如何取得当前单击button的标题呢?...

    mxOutlookBarPro中,button是通过for语句动态生成的,如何取得当前单击button的标题呢? VCL组件开发及应用 http://www.delphi2007.net/Delphi ...

  8. oracle bcp out,SQL Server利用bcp命令把SQL语句结果生成文本文件

    这篇文章主要为大家详细介绍了SQL Server利用bcp命令把SQL语句结果生成文本文件,具有一定的参考价值,可以用来参考一下. 感兴趣的小伙伴,下面一起跟随512笔记的小编两巴掌来看看吧!在SQL ...

  9. mysql offset函数_mysql查询语句解析

    原标题:mysql查询语句解析 一.查询基本知识 select 列1,列2,...,列n from 表名 1.条件查询:where ①比较运算符:=.!=.< >.<= .>= ...

最新文章

  1. SAP SD基础知识之信用范围数据维护
  2. C#中的Dictionary字典类介绍
  3. 全面开启线上参会报名!CNCC线上与现场参会者共赴技术盛宴!
  4. LibSvm python 调试实验
  5. HTTP权威指南记录 ---- Web服务器
  6. HTML的相关路径与绝对路径的问题---通过网络搜索整理
  7. 前端学习(615):变量
  8. 运行shell脚本时怎么知道jdk路径_Shell写脚本关于ssh执行jar包,需要刷新JDK路径的问题...
  9. FreeModbus线圈
  10. Java String、StringBuffer、StringBuilder区别
  11. 炫炫炫的十六进制编辑器
  12. 网狐荣耀之微星棋牌搭建ios苹果APP编译
  13. mysql怎么增加字数_数据库字段如何设置最大字数
  14. html5 监控,基于 HTML5 的 WebGL 3D 智能楼宇监控系统
  15. 使用 Fail2ban 防止 ssh 暴力破解攻击
  16. (转)粒子编辑器Particle designer属性的介绍
  17. Swift 编程语言教程(官方文档)
  18. 算法题练习系列之(一):守形数
  19. Win 98系统启动过程全揭密
  20. iOS POS之3DES加密

热门文章

  1. .net web 点击链接在页面指定位置显示DIV的问题
  2. JavaScript 类型的隐式转换
  3. Vue+Electron下Vuex的Dispatch没有效果的解决方案
  4. Spring Boot统一异常处理实践
  5. Shape Completion using 3D-Encoder-Predictor CNNs and Shape Synthesis 第二部分
  6. 我从吴恩达课堂演讲中学到的一些建议
  7. python解释器用什么写的_用 Python 从零开始写一个简单的解释器(3)
  8. pd生成mysql数据库错误_Err] 1064 - You have an error in your ……pd生成mysql导入失败问题...
  9. 读书笔记——计算机网络CN
  10. LeetCode:10.regular-expression-matching(正则式表达)