案例技术选型: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路由信息获取流程:
以下进行源码分析,其中Debug的部分以instert语句和select结合完成。
核心类定位
既然使用Mybatis执行数据库,那么就要定位到mybatis PreparedStatementHandler的update和query方法:

执行到execute方法时,经过调试定位到ShardingPreparedStatement方法,其实现了PreparedStatement接口,其内部属性如下:
因此断定该类的职责是用来执行SQL语句。定位到该类,稍后分析该类的核心方法。
定位核心方法
既然是实现了PreparedStatement接口,那么实现的executor方法必为核心方法,源码如下:
public boolean execute() throws SQLException {boolean var1;try {this.clearPrevious();this.prepare();this.initPreparedStatementExecutor();var1 = this.preparedStatementExecutor.execute();} finally {this.clearBatch();}return var1;}
clearPrevious方法为清理缓存,并非核心类,故跳过。
prepare
定位到prepare方法,注意,这里的SQL还是原始的SQL!
其源码如下:
private void prepare() {this.executionContext = this.prepareEngine.prepare(this.sql, this.getParameters());this.findGeneratedKey().ifPresent((generatedKey) -> {this.generatedValues.add(generatedKey.getGeneratedValues().getLast());});}
继续:
public ExecutionContext prepare(String sql, List<Object> parameters) {List<Object> clonedParameters = this.cloneParameters(parameters);RouteContext routeContext = this.executeRoute(sql, clonedParameters);ExecutionContext result = new ExecutionContext(routeContext.getSqlStatementContext());result.getExecutionUnits().addAll(this.executeRewrite(sql, clonedParameters, routeContext));if ((Boolean)this.properties.getValue(ConfigurationPropertyKey.SQL_SHOW)) {SQLLogger.logSQL(sql, (Boolean)this.properties.getValue(ConfigurationPropertyKey.SQL_SIMPLE), result.getSqlStatementContext(), result.getExecutionUnits());}return result;}
executeRoute
这里找到了路由关键字,,即executeRoute,正是本节的目标,其源码如下:
private RouteContext executeRoute(String sql, List<Object> clonedParameters) {this.registerRouteDecorator();return this.route(this.router, sql, clonedParameters);}
根据字面意思就可以看出,先进行了路由的装饰器注册再进行路由,其路由方法是BasePrepareEngine的接口方法实现,其源码如下:
继续:
RouteContext即路由上下文,通过executeRoute获得,其源码如下:
private RouteContext executeRoute(String sql, List<Object> parameters, boolean useCache) {RouteContext result = this.createRouteContext(sql, parameters, useCache);Entry entry;for(Iterator var5 = this.decorators.entrySet().iterator(); var5.hasNext(); result = ((RouteDecorator)entry.getValue()).decorate(result, this.metaData, (BaseRule)entry.getKey(), this.properties)) {entry = (Entry)var5.next();}return result;}
最终的结果通过reateRouteContext方法返回,其源码如下:
private RouteContext createRouteContext(String sql, List<Object> parameters, boolean useCache) {SQLStatement sqlStatement = this.parserEngine.parse(sql, useCache);try {SQLStatementContext sqlStatementContext = SQLStatementContextFactory.newInstance(this.metaData.getSchema(), sql, parameters, sqlStatement);return new RouteContext(sqlStatementContext, parameters, new RouteResult());} catch (IndexOutOfBoundsException var6) {return new RouteContext(new CommonSQLStatementContext(sqlStatement), parameters, new RouteResult());}}
它首先创建了个SQLStatement对象,通过parse解析所得,parse源码如下:
parse0:
private SQLStatement parse0(String sql, boolean useCache) {if (useCache) {Optional<SQLStatement> cachedSQLStatement = this.cache.getSQLStatement(sql);if (cachedSQLStatement.isPresent()) {return (SQLStatement)cachedSQLStatement.get();}}ParseTree parseTree = (new SQLParserExecutor(this.databaseTypeName, sql)).execute().getRootNode();SQLStatement result = (SQLStatement)ParseTreeVisitorFactory.newInstance(this.databaseTypeName, VisitorRule.valueOf(parseTree.getClass())).visit(parseTree);if (useCache) {this.cache.put(sql, result);}return result;}
别慌!
这里追的有点深的,这里做个简单的小总结,executeRoute方法的根本目的是获得一个路由的上下文,上述的一系列操作涉及到了hook和缓存等概念,这里暂不做深究只需要知道获取路由上下文信息即具体的配置内容信息即可。
继续!回到源码
逐步返回,返回到第二个executeRoute方法上:
这里调用了decorate方法对路由上下文进行了包装,其源码如下:
public RouteContext decorate(RouteContext routeContext, ShardingSphereMetaData metaData, ShardingRule shardingRule, ConfigurationProperties properties) {SQLStatementContext sqlStatementContext = routeContext.getSqlStatementContext();List<Object> parameters = routeContext.getParameters();ShardingStatementValidatorFactory.newInstance(sqlStatementContext.getSqlStatement()).ifPresent((validator) -> {validator.validate(shardingRule, sqlStatementContext.getSqlStatement(), parameters);});ShardingConditions shardingConditions = this.getShardingConditions(parameters, sqlStatementContext, metaData.getSchema(), shardingRule);boolean needMergeShardingValues = this.isNeedMergeShardingValues(sqlStatementContext, shardingRule);if (sqlStatementContext.getSqlStatement() instanceof DMLStatement && needMergeShardingValues) {this.checkSubqueryShardingValues(sqlStatementContext, shardingRule, shardingConditions);this.mergeShardingConditions(shardingConditions);}ShardingRouteEngine shardingRouteEngine = ShardingRouteEngineFactory.newInstance(shardingRule, metaData, sqlStatementContext, shardingConditions, properties);RouteResult routeResult = shardingRouteEngine.route(shardingRule);if (needMergeShardingValues) {Preconditions.checkState(1 == routeResult.getRouteUnits().size(), "Must have one sharding with subquery.");}return new RouteContext(sqlStatementContext, parameters, routeResult);}
在该过程中得到了ShardingRouteEngine,即路由引擎,其标准属性如下:
// 逻辑表名private final String logicTableName;// SQL解析之后的上下文信息private final SQLStatementContext sqlStatementContext;// 分片条件private final ShardingConditions shardingConditions;private final ConfigurationProperties properties;// datasoure和table信息private final Collection<Collection<DataNode>> originalDataNodes = new LinkedList<>();
最终!
返回到了prepare方法:
经过上述的一系列折腾,路由上下文信息才算完整:
