16. sharding-jdbc源碼分析之重寫

阿飛Javaer,轉(zhuǎn)載請注明原創(chuàng)出處遗遵,謝謝瘦材!

核心源碼就在sharding-jdbc-core模塊的com.dangdang.ddframe.rdb.sharding.rewrite目錄下尸疆,包含兩個(gè)文件SQLBuilderSQLRewriteEngine;測試用例入口為SQLRewriteEngineTest硼被,下面從SQLRewriteEngineTest中debug源碼分析sharding-jdbc的重寫是如何實(shí)現(xiàn)的:

SQLRewriteEngineTest中某個(gè)測試用例如下--主要包括表名示损,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值模擬過程嚷硫,實(shí)際上是SQL解釋過程(SQL解釋會單獨(dú)分析)
    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... 即查詢結(jié)果列的重寫--指那些由于ordre by或者group by需要增加的結(jié)果列)仔掸;

public SQLBuilder rewrite(final boolean isRewriteLimit) {
    SQLBuilder result = new SQLBuilder();
    if (sqlTokens.isEmpty()) {
        result.appendLiterals(originalSQL);
        return result;
    }
    int count = 0;
    // 根據(jù)Token的beginPosition即出現(xiàn)的位置排序
    sortByBeginPosition();
    for (SQLToken each : sqlTokens) {
        if (0 == count) {
            // 第一次處理:截取從原生SQL的開始位置到第一個(gè)token起始位置之間的內(nèi)容脆贵,例如"SELECT x.id FROM table_x x LIMIT 2, 2"這條SQL的第一個(gè)token是TableToken,即table_x所在位置起暮,所以截取內(nèi)容為"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是指當(dāng)邏輯SQL有order by卖氨,group by這樣的特殊條件時(shí),需要在select的結(jié)果列中增加一些結(jié)果列,例如執(zhí)行邏輯SQL:"SELECT o.* FROM t_order o where o.user_id=? order by o.order_id desc limit 2,3"筒捺,那么還需要增加結(jié)果列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中有g(shù)roup by或者有g(shù)roup 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,原因在下文分析逾滥,請點(diǎn)擊連接:
        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解析后只有一個(gè)token败匹,那么結(jié)束位置(endPosition)就是sql末尾寨昙;否則結(jié)束位置就是到下一個(gè)token的起始位置
    int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();
    sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));
}

所有重寫最后都會調(diào)用appendRest(),即附加上余下部分內(nèi)容掀亩,這個(gè)余下部分內(nèi)容是指從當(dāng)前處理的token到下一個(gè)token之間的內(nèi)容舔哪,例如SQL為SELECT x.id FROM table_x x LIMIT 5, 10,當(dāng)遍歷到table_x槽棍,即處理完TableToken后捉蚤,由于下一個(gè)token為OffsetToken,即5炼七,所以appendRest就是append這一段內(nèi)容:" x LIMIT "--從table_x到5之間的內(nèi)容缆巧;

SQLBuilder.toString()分析

重寫完后,調(diào)用SQLBuilder的toString()方法生成重寫后最終的SQL語句豌拙;

public String toSQL(final Map<String, String> tableTokens) {
    StringBuilder result = new StringBuilder();
    for (Object each : segments) {
        // 如果是TableToken陕悬,并且是分庫分表相關(guān)表,那么append最終的實(shí)際表名按傅,例如t_order的實(shí)際表名可能是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();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捉超,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子唯绍,更是在濱河造成了極大的恐慌拼岳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件况芒,死亡現(xiàn)場離奇詭異惜纸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門堪簿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痊乾,“玉大人,你說我怎么就攤上這事椭更∧纳螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵虑瀑,是天一觀的道長湿滓。 經(jīng)常有香客問我,道長舌狗,這世上最難降的妖魔是什么叽奥? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮痛侍,結(jié)果婚禮上朝氓,老公的妹妹穿的比我還像新娘主届。我一直安慰自己,他們只是感情好君丁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绘闷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪印蔗。 梳的紋絲不亂的頭發(fā)上扒最,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音喻鳄,去河邊找鬼扼倘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛除呵,可吹牛的內(nèi)容都是我干的再菊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颜曾,長吁一口氣:“原來是場噩夢啊……” “哼纠拔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泛豪,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稠诲,失蹤者是張志新(化名)和其女友劉穎侦鹏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臀叙,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡略水,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劝萤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渊涝。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖床嫌,靈堂內(nèi)的尸體忽然破棺而出跨释,到底是詐尸還是另有隱情,我是刑警寧澤厌处,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布鳖谈,位于F島的核電站,受9級特大地震影響阔涉,放射性物質(zhì)發(fā)生泄漏缆娃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一瑰排、第九天 我趴在偏房一處隱蔽的房頂上張望龄恋。 院中可真熱鬧,春花似錦凶伙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扳肛,卻和暖如春傻挂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挖息。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工金拒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人套腹。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓绪抛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親电禀。 傳聞我的和親對象是個(gè)殘疾皇子幢码,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • pyspark.sql module Module context Spark SQL和DataFrames中的重...
    盜夢者_(dá)56f2閱讀 5,435評論 0 19
  • 【寫在前面】作為一個(gè)無推廣的小博主店雅,之前的文章收到了很多讀者的熱愛贞铣,在此多謝大家的支持。最近發(fā)現(xiàn)國內(nèi)剽竊現(xiàn)象很嚴(yán)重...
    數(shù)據(jù)女俠閱讀 15,430評論 8 44
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,803評論 0 38
  • 日漸殘喘,我試想過無數(shù)次你會選擇以何種方式離我遠(yuǎn)去圣勒,但卻未曾料到竟會是這種悄無聲息的訣別,我終究還是敗給了眼疾手快...
    vantage_1f41閱讀 239評論 2 1
  • 這無知的蛆蟲 想爬出復(fù)雜的迷宮 這勇敢的蛆蟲 想爬進(jìn)濕潤泥土的花園 無知的我 看得明白這簡單的迷宮 卻一步也走不進(jìn)...
    奈黛奈藹閱讀 421評論 0 0