歡迎光臨
每天分享高質量文章

【死磕Sharding-jdbc】—重寫

點選上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

核心原始碼就在sharding-jdbc-core模組的com.dangdang.ddframe.rdb.sharding.rewrite目錄下,包含兩個檔案SQLBuilderSQLRewriteEngine;測試用例入口為SQLRewriteEngineTest,下麵從SQLRewriteEngineTest中debug原始碼分析sharding-jdbc的重寫是如何實現的:

SQLRewriteEngineTest中某個測試用例如下–主要包括都表名,offset,limit(rowCount)的重寫:

  1. @Test

  2. public void assertRewriteForLimit() {

  3.    selectStatement.setLimit(new Limit(true));

  4.    // offset的值就是limit offset,rowCount中offset的值

  5.    selectStatement.getLimit().setOffset(new LimitValue(2, -1));

  6.    // rowCount的值就是limit offset,rowCount中rowCount的值

  7.    selectStatement.getLimit().setRowCount(new LimitValue(2, -1));

  8.    // TableToken的值表示表名table_x在原始SQL陳述句的偏移量是17的位置

  9.    selectStatement.getSqlTokens().add(new TableToken(17, "table_x"));

  10.    // OffsetToken的值表示offset在原始SQL陳述句的偏移量是33的位置(2就是offset的值)

  11.    selectStatement.getSqlTokens().add(new OffsetToken(33, 2));

  12.    // RowCountToken的值表示rowCount在原始SQL陳述句的偏移量是36的位置(2就是rowCount的值)

  13.    selectStatement.getSqlTokens().add(new RowCountToken(36, 2));

  14.    // selectStatement值模擬過程,實際上是SQL解釋過程(SQL解釋會單獨分析)

  15.    SQLRewriteEngine rewriteEngine = new SQLRewriteEngine(shardingRule, "SELECT x.id FROM table_x x LIMIT 2, 2", selectStatement);

  16.    // 重寫的核心就是這裡了:rewriteEngine.rewrite(true)

  17.    assertThat(rewriteEngine.rewrite(true).toSQL(tableTokens), is("SELECT x.id FROM table_1 x LIMIT 0, 4"));

  18. }

重寫方法核心原始碼: 從這段原始碼可知,sql重寫主要包括對錶名,limit offset, rowNum以及order by的重寫(ItemsToken值對select col1, col2 from… 即查詢結果列的重寫–需要由於ordre by或者group by需要增加一些結果列);

  1. public SQLBuilder rewrite(final boolean isRewriteLimit) {

  2.    SQLBuilder result = new SQLBuilder();

  3.    if (sqlTokens.isEmpty()) {

  4.        result.appendLiterals(originalSQL);

  5.        return result;

  6.    }

  7.    int count = 0;

  8.    // 根據Token的beginPosition即出現的位置排序

  9.    sortByBeginPosition();

  10.    for (SQLToken each : sqlTokens) {

  11.        if (0 == count) {

  12.            // 第一次處理:擷取從原生SQL的開始位置到第一個token起始位置之間的內容,例如"SELECT x.id FROM table_x x LIMIT 2, 2"這條SQL的第一個token是TableToken,即table_x所在位置,所以擷取內容為"SELECT x.id FROM "

  13.            result.appendLiterals(originalSQL.substring(0, each.getBeginPosition()));

  14.        }

  15.        if (each instanceof TableToken) {

  16.            // 看後面的"表名重寫分析"

  17.            appendTableToken(result, (TableToken) each, count, sqlTokens);

  18.        } else if (each instanceof ItemsToken) {

  19.            // 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

  20.            appendItemsToken(result, (ItemsToken) each, count, sqlTokens);

  21.        } else if (each instanceof RowCountToken) {

  22.            // 看後面的"rowCount重寫分析"

  23.            appendLimitRowCount(result, (RowCountToken) each, count, sqlTokens, isRewriteLimit);

  24.        } else if (each instanceof OffsetToken) {

  25.            // 看後面的"offset重寫分析"

  26.            appendLimitOffsetToken(result, (OffsetToken) each, count, sqlTokens, isRewriteLimit);

  27.        } else if (each instanceof OrderByToken) {

  28.            appendOrderByToken(result, count, sqlTokens);

  29.        }

  30.        count++;

  31.    }

  32.    return result;

  33. }

  34. private void sortByBeginPosition() {

  35.    Collections.sort(sqlTokens, new Comparator<SQLToken>() {

  36.        // 生序排列

  37.        @Override

  38.        public int compare(final SQLToken o1, final SQLToken o2) {

  39.            return o1.getBeginPosition() - o2.getBeginPosition();

  40.        }

  41.    });

  42. }

表名重寫分析

  1. private void appendTableToken(final SQLBuilder sqlBuilder, final TableToken tableToken, final int count, final List<SQLToken> sqlTokens) {

  2.    String tableName = sqlStatement.getTables().getTableNames().contains(tableToken.getTableName()) ? tableToken.getTableName() : tableToken.getOriginalLiterals();

  3.    // append表名特殊處理

  4.    sqlBuilder.appendTable(tableName);

  5.    int beginPosition = tableToken.getBeginPosition() + tableToken.getOriginalLiterals().length();

  6.    appendRest(sqlBuilder, count, sqlTokens, beginPosition);

  7. }

  8. // append表名特殊處理,把TableToken也要新增到SQLBuilder中(List segments)

  9. public void appendTable(final String tableName) {

  10.    segments.add(new TableToken(tableName));

  11.    currentSegment = new StringBuilder();

  12.    segments.add(currentSegment);

  13. }

offset重寫分析

  1. private void appendLimitOffsetToken(final SQLBuilder sqlBuilder, final OffsetToken offsetToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {

  2.    // offset的重寫比較簡單:如果要重寫,則offset置為0,否則保留offset的值;

  3.    sqlBuilder.appendLiterals(isRewrite ? "0" : String.valueOf(offsetToken.getOffset()));

  4.    int beginPosition = offsetToken.getBeginPosition() + String.valueOf(offsetToken.getOffset()).length();

  5.    appendRest(sqlBuilder, count, sqlTokens, beginPosition);

  6. }

rowCount重寫分析

  1. private void appendLimitRowCount(final SQLBuilder sqlBuilder, final RowCountToken rowCountToken, final int count, final List<SQLToken> sqlTokens, final boolean isRewrite) {

  2.    SelectStatement selectStatement = (SelectStatement) sqlStatement;

  3.    Limit limit = selectStatement.getLimit();

  4.    if (!isRewrite) {

  5.        // 如果不需要重寫sql中的limit的話(例如select * from t limit 10),那麼,直接append rowCount的值即可;

  6.        sqlBuilder.appendLiterals(String.valueOf(rowCountToken.getRowCount()));

  7.    } else if ((!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems()) {

  8.        // 如果要重寫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,原因在下文分析,請點選連線:

  9.        sqlBuilder.appendLiterals(String.valueOf(Integer.MAX_VALUE));

  10.    } else {

  11.        // 否則只需要將limit offset,rowCount重寫為limit 0, offset+rowCount即可;

  12.        sqlBuilder.appendLiterals(String.valueOf(limit.isRowCountRewriteFlag() ? rowCountToken.getRowCount() + limit.getOffsetValue() : rowCountToken.getRowCount()));

  13.    }

  14.    int beginPosition = rowCountToken.getBeginPosition() + String.valueOf(rowCountToken.getRowCount()).length();

  15.    appendRest(sqlBuilder, count, sqlTokens, beginPosition);

  16. }

appendRest分析

  1. private void appendRest(final SQLBuilder sqlBuilder, final int count, final List<SQLToken> sqlTokens, final int beginPosition) {

  2.    // 如果SQL解析後只有一個token,那麼結束位置(endPosition)就是sql末尾;否則結束位置就是到下一個token的起始位置

  3.    int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();

  4.    sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));

  5. }

所有重寫最後都會呼叫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陳述句;

  1. public String toSQL(final Map<String, String> tableTokens) {

  2.    StringBuilder result = new StringBuilder();

  3.    for (Object each : segments) {

  4.        // 如果是TableToken,並且是分庫分表相關表,那麼append最終的實際表名,例如t_order的實際表名可能是t_order_1

  5.        if (each instanceof TableToken && tableTokens.containsKey(((TableToken) each).tableName)) {

  6.            result.append(tableTokens.get(((TableToken) each).tableName));

  7.        } else {

  8.            result.append(each);

  9.        }

  10.    }

  11.    return result.toString();

  12. }

END

【死磕Sharding-jdbc】---異常處理

【死磕Sharding-jdbc】---EventBus-輕量級行程內事件分發元件

【死磕Sharding-jdbc】---讀寫分離

【死磕Sharding-jdbc】---強制路由

【死磕Sharding-jdbc】---基於ssm

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖