案例技术选型:SpringBoot+Mybatis+ShardingSphere JDBC 4.1.1

前言

当前文章是以ShardingSphere JDBC的4.1.1的starter为例进行分析,目前ShardingSphere release的版本已经达到了5.0.0 alpha,改动还是比较大的,但是核心思想是雷同的,在之后章节会进行5.0.0 alpha的分析。
综上,本章定位为ShardingSphere JDBC的源码初探,之后的探究会随着版本的更新而更新,同时会补充一些版本对比。


下图为ShardingSphere JDBC的SQL路由信息获取流程:
image.png
以下进行源码分析,其中Debug的部分以instert语句和select结合完成。

核心类定位

既然使用Mybatis执行数据库,那么就要定位到mybatis PreparedStatementHandler的update和query方法:
image.png
image.png
执行到execute方法时,经过调试定位到ShardingPreparedStatement方法,其实现了PreparedStatement接口,其内部属性如下:
image.png
因此断定该类的职责是用来执行SQL语句。定位到该类,稍后分析该类的核心方法。

定位核心方法

既然是实现了PreparedStatement接口,那么实现的executor方法必为核心方法,源码如下:

  1. public boolean execute() throws SQLException {
  2. boolean var1;
  3. try {
  4. this.clearPrevious();
  5. this.prepare();
  6. this.initPreparedStatementExecutor();
  7. var1 = this.preparedStatementExecutor.execute();
  8. } finally {
  9. this.clearBatch();
  10. }
  11. return var1;
  12. }

clearPrevious方法为清理缓存,并非核心类,故跳过。

prepare

定位到prepare方法,注意,这里的SQL还是原始的SQL!
image.png
其源码如下:

  1. private void prepare() {
  2. this.executionContext = this.prepareEngine.prepare(this.sql, this.getParameters());
  3. this.findGeneratedKey().ifPresent((generatedKey) -> {
  4. this.generatedValues.add(generatedKey.getGeneratedValues().getLast());
  5. });
  6. }

继续:

  1. public ExecutionContext prepare(String sql, List<Object> parameters) {
  2. List<Object> clonedParameters = this.cloneParameters(parameters);
  3. RouteContext routeContext = this.executeRoute(sql, clonedParameters);
  4. ExecutionContext result = new ExecutionContext(routeContext.getSqlStatementContext());
  5. result.getExecutionUnits().addAll(this.executeRewrite(sql, clonedParameters, routeContext));
  6. if ((Boolean)this.properties.getValue(ConfigurationPropertyKey.SQL_SHOW)) {
  7. SQLLogger.logSQL(sql, (Boolean)this.properties.getValue(ConfigurationPropertyKey.SQL_SIMPLE), result.getSqlStatementContext(), result.getExecutionUnits());
  8. }
  9. return result;
  10. }

executeRoute

这里找到了路由关键字,,即executeRoute,正是本节的目标,其源码如下:

  1. private RouteContext executeRoute(String sql, List<Object> clonedParameters) {
  2. this.registerRouteDecorator();
  3. return this.route(this.router, sql, clonedParameters);
  4. }

根据字面意思就可以看出,先进行了路由的装饰器注册再进行路由,其路由方法是BasePrepareEngine的接口方法实现,其源码如下:
image.png
继续:
image.pngRouteContext即路由上下文,通过executeRoute获得,其源码如下:

  1. private RouteContext executeRoute(String sql, List<Object> parameters, boolean useCache) {
  2. RouteContext result = this.createRouteContext(sql, parameters, useCache);
  3. Entry entry;
  4. for(Iterator var5 = this.decorators.entrySet().iterator(); var5.hasNext(); result = ((RouteDecorator)entry.getValue()).decorate(result, this.metaData, (BaseRule)entry.getKey(), this.properties)) {
  5. entry = (Entry)var5.next();
  6. }
  7. return result;
  8. }

最终的结果通过reateRouteContext方法返回,其源码如下:

  1. private RouteContext createRouteContext(String sql, List<Object> parameters, boolean useCache) {
  2. SQLStatement sqlStatement = this.parserEngine.parse(sql, useCache);
  3. try {
  4. SQLStatementContext sqlStatementContext = SQLStatementContextFactory.newInstance(this.metaData.getSchema(), sql, parameters, sqlStatement);
  5. return new RouteContext(sqlStatementContext, parameters, new RouteResult());
  6. } catch (IndexOutOfBoundsException var6) {
  7. return new RouteContext(new CommonSQLStatementContext(sqlStatement), parameters, new RouteResult());
  8. }
  9. }

它首先创建了个SQLStatement对象,通过parse解析所得,parse源码如下:
image.png
parse0:

  1. private SQLStatement parse0(String sql, boolean useCache) {
  2. if (useCache) {
  3. Optional<SQLStatement> cachedSQLStatement = this.cache.getSQLStatement(sql);
  4. if (cachedSQLStatement.isPresent()) {
  5. return (SQLStatement)cachedSQLStatement.get();
  6. }
  7. }
  8. ParseTree parseTree = (new SQLParserExecutor(this.databaseTypeName, sql)).execute().getRootNode();
  9. SQLStatement result = (SQLStatement)ParseTreeVisitorFactory.newInstance(this.databaseTypeName, VisitorRule.valueOf(parseTree.getClass())).visit(parseTree);
  10. if (useCache) {
  11. this.cache.put(sql, result);
  12. }
  13. return result;
  14. }

别慌!

这里追的有点深的,这里做个简单的小总结,executeRoute方法的根本目的是获得一个路由的上下文,上述的一系列操作涉及到了hook和缓存等概念,这里暂不做深究只需要知道获取路由上下文信息即具体的配置内容信息即可。

继续!回到源码

逐步返回,返回到第二个executeRoute方法上:
image.png
这里调用了decorate方法对路由上下文进行了包装,其源码如下:

  1. public RouteContext decorate(RouteContext routeContext, ShardingSphereMetaData metaData, ShardingRule shardingRule, ConfigurationProperties properties) {
  2. SQLStatementContext sqlStatementContext = routeContext.getSqlStatementContext();
  3. List<Object> parameters = routeContext.getParameters();
  4. ShardingStatementValidatorFactory.newInstance(sqlStatementContext.getSqlStatement()).ifPresent((validator) -> {
  5. validator.validate(shardingRule, sqlStatementContext.getSqlStatement(), parameters);
  6. });
  7. ShardingConditions shardingConditions = this.getShardingConditions(parameters, sqlStatementContext, metaData.getSchema(), shardingRule);
  8. boolean needMergeShardingValues = this.isNeedMergeShardingValues(sqlStatementContext, shardingRule);
  9. if (sqlStatementContext.getSqlStatement() instanceof DMLStatement && needMergeShardingValues) {
  10. this.checkSubqueryShardingValues(sqlStatementContext, shardingRule, shardingConditions);
  11. this.mergeShardingConditions(shardingConditions);
  12. }
  13. ShardingRouteEngine shardingRouteEngine = ShardingRouteEngineFactory.newInstance(shardingRule, metaData, sqlStatementContext, shardingConditions, properties);
  14. RouteResult routeResult = shardingRouteEngine.route(shardingRule);
  15. if (needMergeShardingValues) {
  16. Preconditions.checkState(1 == routeResult.getRouteUnits().size(), "Must have one sharding with subquery.");
  17. }
  18. return new RouteContext(sqlStatementContext, parameters, routeResult);
  19. }

在该过程中得到了ShardingRouteEngine,即路由引擎,其标准属性如下:

  1. // 逻辑表名
  2. private final String logicTableName;
  3. // SQL解析之后的上下文信息
  4. private final SQLStatementContext sqlStatementContext;
  5. // 分片条件
  6. private final ShardingConditions shardingConditions;
  7. private final ConfigurationProperties properties;
  8. // datasoure和table信息
  9. private final Collection<Collection<DataNode>> originalDataNodes = new LinkedList<>();

最终!

返回到了prepare方法:
image.png
经过上述的一系列折腾,路由上下文信息才算完整:
image.png