写点什么

SharingSphere 源码解析 -- 真实 SQL 生成探索

用户头像
关注
发布于: 3 小时前

简介

在上一篇文章中,我们探索了 ShardingSphere JDBC Mybatis 示例执行的一个大致的过程,找到了 SQL 处理的关键节点,看看一个逻辑的 SQL 变成真实 SQL 有哪些关键点

源码解析

注:一番探索下来,逻辑 SQL 达到真实数据库的处理有点复杂了,需要花不少时间 debug,放在一篇也太长了,所以进行拆分,这样简单点

SQL 解析生成入口

继续使用上篇文章中示例:ShardingSphere JDBC 语句执行初探


在上文中: executionContext ,是一个非常关键的变量,可以说贯穿全文,经过我们调试发现,其已经具备真实的需要执行的语句,如下图:



那真实 SQL 生成的关键代码就如下,也就是上文中与 Mybatis 承接的部分


# ShardingSpherePreparedStatement.java    @Override    public boolean execute() throws SQLException {        try {            clearPrevious();      // 在这就已经生成了真实的SQL等语句            executionContext = createExecutionContext();            if (metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules().stream().anyMatch(each -> each instanceof RawExecutionRule)) {                // TODO process getStatement                Collection<ExecuteResult> executeResults = rawExecutor.execute(createRawExecutionGroupContext(), executionContext.getLogicSQL(), new RawSQLExecutorCallback());                return executeResults.iterator().next() instanceof QueryResult;            }            if (executionContext.getRouteContext().isFederated()) {                List<QueryResult> queryResults = executeFederatedQuery();                return !queryResults.isEmpty();            }            ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext = createExecutionGroupContext();            cacheStatements(executionGroupContext.getInputGroups());            return driverJDBCExecutor.execute(executionGroupContext,                    executionContext.getLogicSQL(), executionContext.getRouteContext().getRouteUnits(), createExecuteCallback());        } finally {            clearBatch();        }    }
复制代码

SQL 的解析生成路径

如上,下面我们就开始跟踪生成语句: executionContext = createExecutionContext();


    private ExecutionContext createExecutionContext() {        LogicSQL logicSQL = createLogicSQL();        SQLCheckEngine.check(logicSQL.getSqlStatementContext().getSqlStatement(), logicSQL.getParameters(),                 metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules(), connection.getSchemaName(), metaDataContexts.getMetaDataMap(), null);  // 这生成真实的SQL        ExecutionContext result = kernelProcessor.generateExecutionContext(logicSQL, metaDataContexts.getMetaData(connection.getSchemaName()), metaDataContexts.getProps());        findGeneratedKey(result).ifPresent(generatedKey -> generatedValues.addAll(generatedKey.getGeneratedValues()));        return result;    }
复制代码


跟踪来到下面的关键类,通过注释,我们看到其持有原始的 SQL,相关的配置信息,生成了我们想要的 execution context


public final class KernelProcessor {        /**     * Generate execution context.     *     * @param logicSQL logic SQL     * @param metaData ShardingSphere meta data     * @param props configuration properties     * @return execution context     */    public ExecutionContext generateExecutionContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ConfigurationProperties props) {        RouteContext routeContext = route(logicSQL, metaData, props);        SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);        ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult);        logSQL(logicSQL, props, result);        return result;    }}
复制代码


上面这个函数非常非常的核心,下面看看其三个关键的变量: routeContext, rewriteResult, result


routeContext 从名字可以看出,其应该是保存了逻辑 SQL 到真实 SQL 相关路由信息,通过 debug 我们也可以看出,如下图:



从上面标红的东西可以看到,其已经有了数据库和表的相关的路由的信息,很关键


rewriteResult 经过重写的 SQL,得到了真实的 SQL 语句,如下图:



result 拼装 routeContext, rewriteResult 得到了一直贯穿整个 ShardingSphere 语句执行的东西,这个前面的文章和本文的开篇都有涉及,这里就不再赘述了


在: SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext) 继续跟下去:


public final class SQLRewriteEntry {        /**     * Rewrite.     *      * @param sql SQL     * @param parameters SQL parameters     * @param sqlStatementContext SQL statement context     * @param routeContext route context     * @return route unit and SQL rewrite result map     */    public SQLRewriteResult rewrite(final String sql, final List<Object> parameters, final SQLStatementContext<?> sqlStatementContext, final RouteContext routeContext) {        SQLRewriteContext sqlRewriteContext = createSQLRewriteContext(sql, parameters, sqlStatementContext, routeContext);        return routeContext.getRouteUnits().isEmpty()                ? new GenericSQLRewriteEngine().rewrite(sqlRewriteContext) : new RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext);    }        private SQLRewriteContext createSQLRewriteContext(final String sql, final List<Object> parameters, final SQLStatementContext<?> sqlStatementContext, final RouteContext routeContext) {        SQLRewriteContext result = new SQLRewriteContext(schema, sqlStatementContext, sql, parameters);        decorate(decorators, result, routeContext);        result.generateSQLTokens();        return result;    }}
复制代码


在上面的: createSQLRewriteContext 函数中,其实还没有生成真实的 SQL,但其生成非常关键的用于后面真实 SQL 的关键元信息,这部分这里不细说,后面再研究


我们本次走入处理分支: new RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext)


目前我们是有 route,但如果我们没有 route,走: new GenericSQLRewriteEngine().rewrite(sqlRewriteContext) 是什么样的场景呢?这里标记一个,我们先继续跟着主线看:


public final class RouteSQLRewriteEngine {        /**     * Rewrite SQL and parameters.     *     * @param sqlRewriteContext SQL rewrite context     * @param routeContext route context     * @return SQL rewrite result     */    public RouteSQLRewriteResult rewrite(final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {        Map<RouteUnit, SQLRewriteUnit> result = new LinkedHashMap<>(routeContext.getRouteUnits().size(), 1);        for (RouteUnit each : routeContext.getRouteUnits()) {            result.put(each, new SQLRewriteUnit(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(), getParameters(sqlRewriteContext.getParameterBuilder(), routeContext, each)));        }        return new RouteSQLRewriteResult(result);    }}
复制代码


上面的代码中,类:SQLRewriteUnit,直接由一个关键的变量:sql,这个是我们想要的真实的 SQL,而其就是由本次最终的核心代码生成的:new SQLRewriteUnit(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(),其 toSQL 函数就是真实 SQL 生成的位置:


public abstract class AbstractSQLBuilder implements SQLBuilder {        private final SQLRewriteContext context;        @Override    public final String toSQL() {        if (context.getSqlTokens().isEmpty()) {            return context.getSql();        }        Collections.sort(context.getSqlTokens());        StringBuilder result = new StringBuilder();        result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());        for (SQLToken each : context.getSqlTokens()) {            result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));            result.append(getConjunctionText(each));        }        return result.toString();    }        protected abstract String getSQLTokenText(SQLToken sqlToken);
private String getComposableSQLTokenText(final ComposableSQLToken composableSQLToken) { StringBuilder result = new StringBuilder(); for (SQLToken each : composableSQLToken.getSqlTokens()) { result.append(getSQLTokenText(each)); result.append(getConjunctionText(each)); } return result.toString(); }
private String getConjunctionText(final SQLToken sqlToken) { return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken)); }}
复制代码


通过 debug,我们慢慢的看到变量:result,逐渐的变成我们想要的真实的 SQL,到这里我们基本找到了路径了


这里逻辑还是有点多,还没分析完,就留到下篇分解了

总结

总结下我们这边探索的真实 SQL 经历的关键节点


  • ShardingSpherePreparedStatement.java: executionContext = createExecutionContext();

  • ShardingSpherePreparedStatement.java: ExecutionContext result = kernelProcessor.generateExecutionContext(logicSQL, metaDataContexts.getMetaData(connection.getSchemaName()), metaDataContexts.getProps());

  • KernelProcessor.java: generateExecutionContext

  • RouteContext routeContext = route(logicSQL, metaData, props);

  • SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);

  • SQLRewriteEntry:rewrite

  • new GenericSQLRewriteEngine().rewrite(sqlRewriteContext)

  • new RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext)

  • ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult)


本次其实就定位了具体的真实 SQL 的关键代码,逻辑梳理还是比较花时间的,所以就留到下一篇讲解了

发布于: 3 小时前阅读数: 3
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
SharingSphere 源码解析 -- 真实SQL生成探索