16. sharding-jdbc源码分析之重写

阿飞Javaer,转载请注明原创出处,谢谢!

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

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

@Test
public 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 LIMIT 5, 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();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • pyspark.sql module Module context Spark SQL和DataFrames中的重...
    盗梦者_56f2阅读 5,404评论 0 19
  • 【写在前面】作为一个无推广的小博主,之前的文章收到了很多读者的热爱,在此多谢大家的支持。最近发现国内剽窃现象很严重...
    数据女侠阅读 15,398评论 8 44
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,729评论 0 38
  • 日渐残喘,我试想过无数次你会选择以何种方式离我远去,但却未曾料到竟会是这种悄无声息的诀别,我终究还是败给了眼疾手快...
    vantage_1f41阅读 232评论 2 1
  • 这无知的蛆虫 想爬出复杂的迷宫 这勇敢的蛆虫 想爬进湿润泥土的花园 无知的我 看得明白这简单的迷宫 却一步也走不进...
    奈黛奈蔼阅读 397评论 0 0