【死磕Sharding-jdbc】---重写

核心源码就在sharding-jdbc-core模块的com.dangdang.ddframe.rdb.sharding.rewrite目录下,包含两个文件SQLBuilderSQLRewriteEngine;测试用例入口为SQLRewriteEngineTest,下面从SQLRewriteEngineTest中debug源码分析sharding-jdbc的重写是如何实现的:

SQLRewriteEngineTest中某个测试用例如下--主要包括都表名,offset,limit(rowCount)的重写:

@Testpublic void assertRewriteForLimit() {    selectStatement.setLimit(new Limit(true));    // offset的值就是limit offset,rowCount中offset的值    selectStatement.getLimit().setOffset(new LimitValue(2, -1));    // rowCount的值就是limit offset,rowCount中rowCount的值    selectStatement.getLimit().setRowCount(new LimitValue(2, -1));    // TableToken的值表示表名table_x在原始SQL语句的偏移量是17的位置    selectStatement.getSqlTokens().add(new TableToken(17, "table_x"));    // OffsetToken的值表示offset在原始SQL语句的偏移量是33的位置(2就是offset的值)    selectStatement.getSqlTokens().add(new OffsetToken(33, 2));    // RowCountToken的值表示rowCount在原始SQL语句的偏移量是36的位置(2就是rowCount的值)    selectStatement.getSqlTokens().add(new RowCountToken(36, 2));    // selectStatement值模拟过程,实际上是SQL解释过程(SQL解释会单独分析)    SQLRewriteEngine rewriteEngine = new SQLRewriteEngine(shardingRule, "SELECT x.id FROM table_x x LIMIT 2, 2", selectStatement);    // 重写的核心就是这里了:rewriteEngine.rewrite(true)    assertThat(rewriteEngine.rewrite(true).toSQL(tableTokens), is("SELECT x.id FROM table_1 x LIMIT 0, 4"));}

重写方法核心源码: 从这段源码可知,sql重写主要包括对表名,limit offset, rowNum以及order by的重写(ItemsToken值对select col1, col2 from... 即查询结果列的重写--需要由于ordre by或者group by需要增加一些结果列);

public SQLBuilder rewrite(final boolean isRewriteLimit) {    SQLBuilder result = new SQLBuilder();    if (sqlTokens.isEmpty()) {        result.appendLiterals(originalSQL);        return result;    }    int count = 0;    // 根据Token的beginPosition即出现的位置排序    sortByBeginPosition();    for (SQLToken each : sqlTokens) {        if (0 == count) {            // 第一次处理:截取从原生SQL的开始位置到第一个token起始位置之间的内容,例如"SELECT x.id FROM table_x x LIMIT 2, 2"这条SQL的第一个token是TableToken,即table_x所在位置,所以截取内容为"SELECT x.id FROM "            result.appendLiterals(originalSQL.substring(0, each.getBeginPosition()));        }        if (each instanceof TableToken) {            // 看后面的"表名重写分析"            appendTableToken(result, (TableToken) each, count, sqlTokens);        } else if (each instanceof ItemsToken) {            // ItemsToken是指当逻辑SQL有order by,group by这样的特殊条件时,需要在select的结果列中增加一些结果列,例如执行逻辑SQL:"SELECT o.* FROM t_order o where o.user_id=? order by o.order_id desc limit 2,3",那么还需要增加结果列o.order_id AS ORDER_BY_DERIVED_0             appendItemsToken(result, (ItemsToken) each, count, sqlTokens);        } else if (each instanceof RowCountToken) {            // 看后面的"rowCount重写分析"            appendLimitRowCount(result, (RowCountToken) each, count, sqlTokens, isRewriteLimit);        } else if (each instanceof OffsetToken) {            // 看后面的"offset重写分析"            appendLimitOffsetToken(result, (OffsetToken) each, count, sqlTokens, isRewriteLimit);        } else if (each instanceof OrderByToken) {            appendOrderByToken(result, count, sqlTokens);        }        count++;    }    return result;}private void sortByBeginPosition() {    Collections.sort(sqlTokens, new Comparator<SQLToken>() {        // 生序排列        @Override        public int compare(final SQLToken o1, final SQLToken o2) {            return o1.getBeginPosition() - o2.getBeginPosition();        }    });}

表名重写分析

private void appendTableToken(final SQLBuilder sqlBuilder, final TableToken tableToken, final int count, final List<SQLToken> sqlTokens) {    String tableName = sqlStatement.getTables().getTableNames().contains(tableToken.getTableName()) ? tableToken.getTableName() : tableToken.getOriginalLiterals();    // append表名特殊处理    sqlBuilder.appendTable(tableName);    int beginPosition = tableToken.getBeginPosition() + tableToken.getOriginalLiterals().length();    appendRest(sqlBuilder, count, sqlTokens, beginPosition);}// append表名特殊处理,把TableToken也要添加到SQLBuilder中(List<Object> segments)public void appendTable(final String tableName) {    segments.add(new TableToken(tableName));    currentSegment = new StringBuilder();    segments.add(currentSegment);}

offset重写分析

private void appendLimitOffsetToken(final SQLBuilder sqlBuilder, final OffsetToken offsetToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {    // offset的重写比较简单:如果要重写,则offset置为0,否则保留offset的值;    sqlBuilder.appendLiterals(isRewrite ? "0" : String.valueOf(offsetToken.getOffset()));    int beginPosition = offsetToken.getBeginPosition() + String.valueOf(offsetToken.getOffset()).length();    appendRest(sqlBuilder, count, sqlTokens, beginPosition);}

rowCount重写分析

private void appendLimitRowCount(final SQLBuilder sqlBuilder, final RowCountToken rowCountToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {    SelectStatement selectStatement = (SelectStatement) sqlStatement;    Limit limit = selectStatement.getLimit();    if (!isRewrite) {        // 如果不需要重写sql中的limit的话(例如select * from t limit 10),那么,直接append rowCount的值即可;        sqlBuilder.appendLiterals(String.valueOf(rowCountToken.getRowCount()));    } else if ((!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems()) {        // 如果要重写sql中的limit的话,且sql中有group by或者有group by & order by,例如""SELECT o.* FROM t_order o where o.user_id=? group by o.order_id order by o.order_id desc limit 2,3"需要",那么重写为Integer.MAX_VALUE,原因在下文分析,请点击连接:        sqlBuilder.appendLiterals(String.valueOf(Integer.MAX_VALUE));    } else {        // 否则只需要将limit offset,rowCount重写为limit 0, offset+rowCount即可;        sqlBuilder.appendLiterals(String.valueOf(limit.isRowCountRewriteFlag() ? rowCountToken.getRowCount() + limit.getOffsetValue() : rowCountToken.getRowCount()));    }    int beginPosition = rowCountToken.getBeginPosition() + String.valueOf(rowCountToken.getRowCount()).length();    appendRest(sqlBuilder, count, sqlTokens, beginPosition);}

appendRest分析

private void appendRest(final SQLBuilder sqlBuilder, final int count, final List<SQLToken> sqlTokens, final int beginPosition) {    // 如果SQL解析后只有一个token,那么结束位置(endPosition)就是sql末尾;否则结束位置就是到下一个token的起始位置    int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();    sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));}

所有重写最后都会调用appendRest(),即附加上余下部分内容,这个余下部分内容是指从当前处理的token到下一个token之间的内容,例如SQL为SELECT x.id FROM table_x x LIMIT5,10,当遍历到table_x,即处理完TableToken后,由于下一个token为OffsetToken,即5,所以appendRest就是append这一段内容:" x LIMIT "--从table_x到5之间的内容;

SQLBuilder.toString()分析

重写完后,调用SQLBuilder的toString()方法生成重写后最终的SQL语句;

public String toSQL(final Map<String, String> tableTokens) {    StringBuilder result = new StringBuilder();    for (Object each : segments) {        // 如果是TableToken,并且是分库分表相关表,那么append最终的实际表名,例如t_order的实际表名可能是t_order_1        if (each instanceof TableToken && tableTokens.containsKey(((TableToken) each).tableName)) {            result.append(tableTokens.get(((TableToken) each).tableName));        } else {            result.append(each);        }    }    return result.toString();}

END

往期精彩

  • 【死磕Sharding-jdbc】---异常处理
  • 【死磕Sharding-jdbc】---EventBus-轻量级进程内事件分发组件
  • 【死磕Sharding-jdbc】---读写分离
  • 【死磕Sharding-jdbc】---强制路由
  • 【死磕Sharding-jdbc】---基于ssm

本文分享自微信公众号 - Java技术驿站(chenssy89)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FSociety

SQL中GROUP BY用法示例

GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

5.2K20
来自专栏Ken的杂谈

【系统设置】CentOS 修改机器名

18430
来自专栏haifeiWu与他朋友们的专栏

复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

31040
来自专栏怀英的自我修炼

考研英语-1-导学

英二图表作文要重视。总体而言,英语一会比英语二难点。不过就写作而言,英语二会比英语一有难度,毕竟图表作文并不好写。

12410
来自专栏前端桃园

知识体系解决迷茫的你

最近在星球里群里都有小伙伴说道自己对未来的路比较迷茫,一旦闲下来就不知道自己改干啥,今天我这篇文章就是让你觉得一天给你 25 个小时你都不够用,觉得睡觉都是浪费...

22740
来自专栏腾讯高校合作

【倒计时7天】2018教育部-腾讯公司产学合作协同育人项目申请即将截止!

16220
来自专栏钱塘大数据

中国互联网协会发布:《2018中国互联网发展报告》

在2018中国互联网大会闭幕论坛上,中国互联网协会正式发布《中国互联网发展报告2018》(以下简称《报告》)。《中国互联网发展报告》是由中国互联网协会与中国互联...

13850
来自专栏钱塘大数据

理工男图解零维到十维空间,烧脑已过度,受不了啦!

让我们从一个点开始,和我们几何意义上的点一样,它没有大小、没有维度。它只是被想象出来的、作为标志一个位置的点。它什么也没有,空间、时间通通不存在,这就是零维度。

35430
来自专栏腾讯社交用户体验设计

ISUX Xcube智能一键生成H5

51620
来自专栏微信公众号:小白课代表

不只是软件,在线也可以免费下载百度文库了。

不管是学生,还是职场员工,下载各种文档几乎是不可避免的,各种XXX.docx,XXX.pptx更是家常便饭,人们最常用的就是百度文库,豆丁文库,道客巴巴这些下载...

45130

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励