Shading - jdbc 源碼分析(五) - sql 改寫

上一篇文章咱們分析了sharding-jdbc 的路由(路由),今天咱們分析下sql改寫。sql

閒聊:翻了Sharding-Sphere 的文檔,也對SQL【解析、路由、改寫、執行、歸併】引擎作了介紹,你們有興趣能夠看看。編程

主要類:

  • SQLRewriteEngine:SQL重寫引擎,rewrite()方法進行SQL改寫;generateSQL()方法生成SQL語句

執行邏輯:

咱們繼續如下面的SQL爲例:bash

SELECT o.order_id FROM order o WHERE o.order_id = 4app

  • 下面的方法就是重寫的邏輯(也包含路由,路由的邏輯上篇文章已經分析)
public SQLRouteResult route(final String logicSQL, final List<Object> parameters, final SQLStatement sqlStatement) {
        final Context context = MetricsContext.start("Route SQL");
        SQLRouteResult result = new SQLRouteResult(sqlStatement);
        if (sqlStatement instanceof InsertStatement && null != ((InsertStatement) sqlStatement).getGeneratedKey()) {
            processGeneratedKey(parameters, (InsertStatement) sqlStatement, result);
        }
        //路由
        RoutingResult routingResult = route(parameters, sqlStatement);
        //重寫
        SQLRewriteEngine rewriteEngine = new SQLRewriteEngine(shardingRule, logicSQL, sqlStatement);
        //判斷是不是單表路由
        boolean isSingleRouting = routingResult.isSingleRouting();
        //處理limit
        if (null != sqlStatement.getLimit()) {
            sqlStatement.getLimit().processParameters(parameters, !isSingleRouting);
        }
        //構建 SQLBuilder
        SQLBuilder sqlBuilder = rewriteEngine.rewrite(!isSingleRouting);
        if (routingResult instanceof CartesianRoutingResult) {
            for (CartesianDataSource cartesianDataSource : ((CartesianRoutingResult) routingResult).getRoutingDataSources()) {
                for (CartesianTableReference cartesianTableReference : cartesianDataSource.getRoutingTableReferences()) {
                    result.getExecutionUnits().add(new SQLExecutionUnit(cartesianDataSource.getDataSource(), rewriteEngine.generateSQL(cartesianTableReference, sqlBuilder)));
                }
            }
        } else {
            //建立sql最小執行單元
            for (TableUnit each : routingResult.getTableUnits().getTableUnits()) {
                result.getExecutionUnits().add(new SQLExecutionUnit(each.getDataSourceName(), rewriteEngine.generateSQL(each, sqlBuilder)));
            }
        }
        MetricsContext.stop(context);
        logSQLRouteResult(result, parameters);
        return result;
    }
複製代碼
  1. 獲取RoutingResult路由結果: 這是routingResult結果
    路由結果
  2. 構造SQLRewriteEngine
public SQLRewriteEngine(final ShardingRule shardingRule, final String originalSQL, final SQLStatement sqlStatement) {
        this.shardingRule = shardingRule;
        this.originalSQL = originalSQL;
        sqlTokens.addAll(sqlStatement.getSqlTokens());
        tableNames = sqlStatement.getTables().getTableNames();
        limit = sqlStatement.getLimit();
    }
複製代碼
  1. rewrite()構造SQLBuilder:

處理邏輯:把SQL分爲2部分:須要替換的(例如:表名) 和不須要替換的;須要替換的表名替換成真實的表名(利用解析好的TableToken),分別add 到SQLBuilder的segments集合中。這樣生成真實SQL直接foreach segments就能夠了post

/**
     * SQL改寫.
     *
     * @param isRewriteLimit 是否重寫Limit
     * @return SQL構建器
     */
    public SQLBuilder rewrite(final boolean isRewriteLimit) {
        SQLBuilder result = new SQLBuilder();
        //若是沒有sqlTokens,說明沒有須要替換的標記
        if (sqlTokens.isEmpty()) {
            //直接add
            result.appendLiterals(originalSQL);
            return result;
        }
        int count = 0;
        sortByBeginPosition();
        //遍歷SQL標記
        for (SQLToken each : sqlTokens) {
            if (0 == count) {
               //標記的位置是須要替換的開始index,這裏直接add不須要替換的部分
                result.appendLiterals(originalSQL.substring(0, each.getBeginPosition()));
            }
            //須要替換table
            if (each instanceof TableToken) {
                appendTableToken(result, (TableToken) each, count, sqlTokens);
            } else if (each instanceof ItemsToken) {
                appendItemsToken(result, (ItemsToken) each, count, sqlTokens);
            } else if (each instanceof RowCountLimitToken) {
                appendLimitRowCount(result, (RowCountLimitToken) each, count, sqlTokens, isRewriteLimit);
            } else if (each instanceof OffsetLimitToken) {
                appendLimitOffsetToken(result, (OffsetLimitToken) each, count, sqlTokens, isRewriteLimit);
            }
            count++;
        }
        return result;
    }
    //appendTableToken:
    private void appendTableToken(final SQLBuilder sqlBuilder, final TableToken tableToken, final int count, final List<SQLToken> sqlTokens) {
        //獲取真實tableName
        String tableName = tableNames.contains(tableToken.getTableName()) ? tableToken.getTableName() : tableToken.getOriginalLiterals();
        // addTableToken
        sqlBuilder.appendTable(tableName);
        int beginPosition = tableToken.getBeginPosition() + tableToken.getOriginalLiterals().length();
        int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();
        //截取2個token之間,不須要替換的部分
        sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));
    }
複製代碼
  1. 判斷routingResult的路由(是不是笛卡爾積路由),咱們這個SQL是簡單路由: 遍歷路由獲得的TableUnit結果,generateSQL()生成SQL,構造SQLExecutionUnit
for (TableUnit each : routingResult.getTableUnits().getTableUnits()) {
                result.getExecutionUnits().add(new SQLExecutionUnit(each.getDataSourceName(), rewriteEngine.generateSQL(each, sqlBuilder)));
            }
複製代碼
/**
     * 生成SQL語句.
     * 
     * @param tableUnit 路由表單元
     * @param sqlBuilder SQL構建器
     * @return SQL語句
     */
    public String generateSQL(final TableUnit tableUnit, final SQLBuilder sqlBuilder) {
        return sqlBuilder.toSQL(getTableTokens(tableUnit));
    }
    //將邏輯表和真實表以map的形式關聯
     private Map<String, String> getTableTokens(final TableUnit tableUnit) {
        Map<String, String> tableTokens = new HashMap<>();
        tableTokens.put(tableUnit.getLogicTableName(), tableUnit.getActualTableName());
        Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(tableUnit.getLogicTableName());
        if (bindingTableRule.isPresent()) {
            tableTokens.putAll(getBindingTableTokens(tableUnit, bindingTableRule.get()));
        }
        return tableTokens;
    }
    
    /**
     * 生成SQL語句.
     * foreach 每一個segments,生成真正可執行的SQL
     * @param tableTokens 佔位符集合
     * @return SQL語句
     */
    public String toSQL(final Map<String, String> tableTokens) {
        StringBuilder result = new StringBuilder();
        for (Object each : segments) {
            if (each instanceof TableToken && tableTokens.containsKey(((TableToken) each).tableName)) {
                result.append(tableTokens.get(((TableToken) each).tableName));
            } else {
                result.append(each);
            }
        }
        return result.toString();
    }
複製代碼

最後:

小尾巴走一波,歡迎關注個人公衆號,不按期分享編程、投資、生活方面的感悟:)ui

相關文章
相關標籤/搜索