Sharding-JDBC系列
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理
9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)
10、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(二)
11、【源码】Sharding-JDBC源码分析之Yaml分片配置转换原理
12、【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理
13、【源码】Sharding-JDBC源码分析之ContextManager创建中mode分片配置信息的持久化存储的原理
14、【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理
15、【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理
16、【源码】Sharding-JDBC源码分析之配置数据库定义的表的元数据解析原理
17、【源码】Sharding-JDBC源码分析之ShardingSphereConnection的创建原理
18、【源码】Sharding-JDBC源码分析之ShardingSpherePreparedStatement的创建原理
19、【源码】Sharding-JDBC源码分析之Sql解析的原理
20、【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由
前言
ShardingSphere透明的为Java应用程序提供了数据库分片功能,只需配置好分片规则,无需关心底层的数据库分片细节。ShardingSphere框架根据配置好的分片规则,自动路由到实际操作的数据库、表中。本文从源码的角度分析 SQL 路由的实现原理。
ShardingSpherePreparedStatement回顾
在【源码】Sharding-JDBC源码分析之ShardingSpherePreparedStatement的创建原理-CSDN博客文章中,介绍了在执行PreparedStatement的executeQuery()方法,进行数据库查询时,会执行createExecutionContext(queryContext)方法,创建执行上下文。代码如下:
/**
* ShardingSphere的PreparedStatement
*/
public final class ShardingSpherePreparedStatement extends AbstractPreparedStatementAdapter {
/**
* 创建执行上下文
* @param queryContext
* @return
*/
private ExecutionContext createExecutionContext(final QueryContext queryContext) {
// 有效性校验
SQLCheckEngine.check(queryContext.getSqlStatementContext(), queryContext.getParameters(),
metaDataContexts.getMetaData().getDatabase(connection.getDatabaseName()).getRuleMetaData().getRules(),
connection.getDatabaseName(), metaDataContexts.getMetaData().getDatabases(), null);
// 创建执行上下文
ExecutionContext result = kernelProcessor.generateExecutionContext(queryContext, metaDataContexts.getMetaData().getDatabase(connection.getDatabaseName()),
metaDataContexts.getMetaData().getGlobalRuleMetaData(), metaDataContexts.getMetaData().getProps(), connection.getConnectionContext());
// insert语句,查找并自动生成key
findGeneratedKey(result).ifPresent(generatedKey -> generatedValues.addAll(generatedKey.getGeneratedValues()));
return result;
}
}
在改方法中,执行kernelProcessor.generateExecutionContext()方法,创建一个ExecutionContext对象。
KernelProcessor
KernelProcessor的源码如下:
package org.apache.shardingsphere.infra.context.kernel;
/**
* 内核处理器
*/
public final class KernelProcessor {
/**
* 生成执行上下文
*/
public ExecutionContext generateExecutionContext(final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingSphereRuleMetaData globalRuleMetaData,
final ConfigurationProperties props, final ConnectionContext connectionContext) {
// 创建路由上下文
RouteContext routeContext = route(queryContext, database, props, connectionContext);
// SQL 重写结果
SQLRewriteResult rewriteResult = rewrite(queryContext, database, globalRuleMetaData, props, routeContext, connectionContext);
// 创建执行上下文
ExecutionContext result = createExecutionContext(queryContext, database, routeContext, rewriteResult);
// 显示日志
logSQL(queryContext, props, result);
return result;
}
/**
* 路由,创建路由上下文
*/
private RouteContext route(final QueryContext queryContext, final ShardingSphereDatabase database, final ConfigurationProperties props, final ConnectionContext connectionContext) {
// 创建SQL路由引擎,执行route(),获取路由上下文
return new SQLRouteEngine(database.getRuleMetaData().getRules(), props).route(connectionContext, queryContext, database);
}
}
在KernelProcessor的generateExecutionContext()方法,执行如下:
1)根据路由规则等信息,创建路由上下文对象 RouteContext;
1.1)创建一个SQLRouteEngine对象;
1.2)执行SQLRouteEngine的route()方法,创建一个RouteContext;
其中:路由上下文对象 RouteContext 中记录了数据源路由映射(逻辑数据源和实际数据源映射)以及表路由映射等信息。
2)根据路由上下文对象,配置的路由规则等,进行SQL重写,生成SQLRewriteResult对象;
3)创建执行单元,并保存到 ExecutionContext 执行上下文中;
4)SQL 日志处理;
SQLRouteEngine
SQLRouteEngine的源码如下:
package org.apache.shardingsphere.infra.route.engine;
/**
* SQL 路由引擎
*/
@RequiredArgsConstructor
public final class SQLRouteEngine {
// 配置的规则
private final Collection<ShardingSphereRule> rules;
// 配置的属性
private final ConfigurationProperties props;
/**
* SQL 路由
*/
public RouteContext route(final ConnectionContext connectionContext, final QueryContext queryContext, final ShardingSphereDatabase database) {
SQLRouteExecutor executor = isNeedAllSchemas(queryContext.getSqlStatementContext().getSqlStatement()) ? new AllSQLRouteExecutor() : new PartialSQLRouteExecutor(rules, props);
return executor.route(connectionContext, queryContext, database);
}
/**
* 判断是否需要全部的schema
*/
private boolean isNeedAllSchemas(final SQLStatement sqlStatement) {
// 针对MySQL数据库的显示表或表状态语句返回true;否则返回false
return sqlStatement instanceof MySQLShowTablesStatement || sqlStatement instanceof MySQLShowTableStatusStatement;
}
}
在route()方法中,执行如下:
1)先获取一个路由执行器;
路由执行器分为 AllSQLRouteExecutor(全路由执行器)和 PartialSQLRouteExecutor(部分路由执行器)。只有对于MySQL数据库的显示表或表状态的SQL语句才使用全路由执行器。
AllSQLRouteExecutor:全路由执行器即对配置的所有数据源执行操作的执行器。
2)执行路由执行器的route()方法,创建路由上下文对象;
PartialSQLRouteExecutor
PartialSQLRouteExecutor的源码如下:
package org.apache.shardingsphere.infra.route.engine.impl;
/**
* 部分SQL路由执行器
*/
public final class PartialSQLRouteExecutor implements SQLRouteExecutor {
// 配置的props
private final ConfigurationProperties props;
// SQL路由器,没有其他配置,默认有ShardingSQLRouter、SingleSQLRouter
@SuppressWarnings("rawtypes")
private final Map<ShardingSphereRule, SQLRouter> routers;
public PartialSQLRouteExecutor(final Collection<ShardingSphereRule> rules, final ConfigurationProperties props) {
this.props = props;
routers = OrderedSPIRegistry.getRegisteredServices(SQLRouter.class, rules);
}
/**
* SQL路由。根据配置的路由器,执行路由器的路由规则。
* 可配置的路由器包括:数据库发现路由器、读写分离路由器、影子库路由器、分片路由器、单表路由器
* @return
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public RouteContext route(final ConnectionContext connectionContext, final QueryContext queryContext, final ShardingSphereDatabase database) {
RouteContext result = new RouteContext();
// 从提示中获取数据源
Optional<String> dataSourceName = findDataSourceByHint(queryContext.getSqlStatementContext(), database.getResourceMetaData().getDataSources());
if (dataSourceName.isPresent()) {
result.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName.get(), dataSourceName.get()), Collections.emptyList()));
return result;
}
// 遍历路由器,创建路由上下文
// 路由器包括:数据库发现路由器、读写分离路由器、影子库路由器、分片路由器、单表路由器
for (Entry<ShardingSphereRule, SQLRouter> entry : routers.entrySet()) {
if (result.getRouteUnits().isEmpty()) {
// 创建路由上下文
result = entry.getValue().createRouteContext(queryContext, database, entry.getKey(), props, connectionContext);
} else {
// 装饰路由上下文
entry.getValue().decorateRouteContext(result, queryContext, database, entry.getKey(), props, connectionContext);
}
}
// 如果没有路由单元,且数据源只有一个,创建一个路由单元,添加到路由上下文中
if (result.getRouteUnits().isEmpty() && 1 == database.getResourceMetaData().getDataSources().size()) {
String singleDataSourceName = database.getResourceMetaData().getDataSources().keySet().iterator().next();
result.getRouteUnits().add(new RouteUnit(new RouteMapper(singleDataSourceName, singleDataSourceName), Collections.emptyList()));
}
return result;
}
/**
* 获取提示指定的数据源
*/
private Optional<String> findDataSourceByHint(final SQLStatementContext<?> sqlStatementContext, final Map<String, DataSource> dataSources) {
Optional<String> result;
// 通过HintManager指定,如指定了写库
if (HintManager.isInstantiated() && HintManager.getDataSourceName().isPresent()) {
result = HintManager.getDataSourceName();
} else {
// 或者通过sql语句中的提示语指定数据源
result = ((CommonSQLStatementContext<?>) sqlStatementContext).findHintDataSourceName();
}
if (result.isPresent() && !dataSources.containsKey(result.get())) {
throw new SQLHintDataSourceNotExistsException(result.get());
}
return result;
}
}
核心route()方法执行如下:
1)执行findDataSourceByHint(),获取提示指定的数据源;
1.1)先从 HintManager 单例中获取(如通过HintManager.setDataSourceName()设置);
1.2)如果没有设置,则解析 SQL 语句,查找 SQL 语句中的提示语中设置的数据源;
2)如果以上没有找到数据源,则遍历设置的路由器,执行路由器创建路由上下文或装饰路由上下文;
2.1)在构造方法中,通过配置的规则,使用 SPI 获取对应的路由器。可配置的路由器包括:数据库发现路由器(DatabaseDiscoverySQLRouter)、读写分离路由器(ReadwriteSplittingSQLRouter)、影子库路由器(ShadowSQLRouter)、分片路由器(ShardingSQLRouter)、单表路由器(SingleSQLRouter);
2.2)每个路由器都有设置了顺序,所以通过SPI获取后,路由的顺序为ShardingSQLRouter、SingleSQLRouter、ReadwriteSplittingSQLRouter、DatabaseDiscoverySQLRouter、ShadowSQLRouter;
3)如果没有配置路由规则,且只配置了一个数据源,则创建一个路由单元,加入到路由上下文中;
4)返回路由上下文RouteContext;
ShardingSQLRouter
如果没有通过提示语指定路由的数据源,则从SPI获取路由器之后,优先执行ShardingSQLRouter分片路由器,即执行路由器的createRouteContext()方法。
详见:【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理-CSDN博客
SingleSQLRouter
ShardingSQLRouter分片SQL路由器执行之后,接着执行SingleSQLRouter单表(此处的单表特指没有设置分片规则的表)SQL路由器,该路由器一定会执行,因为在解析配置规则生成规则对象时,默认会添加SingleRule。在此处进行SQL路由时,其中的routers包含了SingleSQLRouter。
详见:【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理-CSDN博客
SingleSQLRouter的源码如下:
package org.apache.shardingsphere.single.route;
/**
* 单表SQL路由器
*/
public final class SingleSQLRouter implements SQLRouter<SingleRule> {
/**
* 创建路由上下文
* @param queryContext 查询上下文
* @param database 数据库信息
* @param rule 单表规则
* @param props 配置的属性
* @param connectionContext 连接上下文
* @return
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public RouteContext createRouteContext(final QueryContext queryContext, final ShardingSphereDatabase database, final SingleRule rule,
final ConfigurationProperties props, final ConnectionContext connectionContext) {
// 如果只配置了一个数据源
if (1 == database.getResourceMetaData().getDataSources().size()) {
return createSingleDataSourceRouteContext(rule, database);
}
RouteContext result = new RouteContext();
// 获取SQL语句上下文
SQLStatementContext<?> sqlStatementContext = queryContext.getSqlStatementContext();
// 有效性检验
Optional<SingleMetaDataValidator> validator = SingleMetaDataValidatorFactory.newInstance(sqlStatementContext.getSqlStatement());
validator.ifPresent(optional -> optional.validate(rule, sqlStatementContext, database));
// 从SQL语句上下文中获取表,查找在单表规则中配置的表,创建QualifiedTable对象
Collection<QualifiedTable> singleTableNames = getSingleTableNames(sqlStatementContext, database, rule, result);
// 所有表在同一个数据源的校验
if (!singleTableNames.isEmpty()) {
validateSameDataSource(sqlStatementContext, rule, props, singleTableNames, result);
}
// 创建单表路由引擎实例,执行route()方法
SingleRouteEngineFactory.newInstance(singleTableNames, sqlStatementContext.getSqlStatement()).ifPresent(optional -> optional.route(result, rule));
return result;
}
/**
* 装饰路由上下文呢
* @param routeContext 路由上下文
* @param queryContext 查询上下文
* @param database database
* @param rule rule
* @param props configuration properties
* @param connectionContext connection context
*/
@Override
public void decorateRouteContext(final RouteContext routeContext, final QueryContext queryContext, final ShardingSphereDatabase database,
final SingleRule rule, final ConfigurationProperties props, final ConnectionContext connectionContext) {
// SQL 语句上下文呢
SQLStatementContext<?> sqlStatementContext = queryContext.getSqlStatementContext();
// 从单表规则中查找当前sql语句中的表,返回sql语句中的单表
Collection<QualifiedTable> singleTableNames = getSingleTableNames(sqlStatementContext, database, rule, routeContext);
if (singleTableNames.isEmpty()) {
return;
}
validateSameDataSource(sqlStatementContext, rule, props, singleTableNames, routeContext);
// 创建单表路由引擎,执行路由
SingleRouteEngineFactory.newInstance(singleTableNames, sqlStatementContext.getSqlStatement()).ifPresent(optional -> optional.route(routeContext, rule));
}
/**
* 创建单数据源的路由上下文对象
* @param rule
* @param database
* @return
*/
private RouteContext createSingleDataSourceRouteContext(final SingleRule rule, final ShardingSphereDatabase database) {
// 从配置中获取逻辑数据源
String logicDataSource = rule.getDataSourceNames().iterator().next();
// 从数据库信息中获取真实数据源
String actualDataSource = database.getResourceMetaData().getDataSources().keySet().iterator().next();
RouteContext result = new RouteContext();
// 创建路由映射及路由单元
result.getRouteUnits().add(new RouteUnit(new RouteMapper(logicDataSource, actualDataSource), Collections.emptyList()));
return result;
}
/**
* 获取单表名称
* @param sqlStatementContext sql语句上下文
* @param database 数据库
* @param rule 单表规则
* @param routeContext 当前的路由上下文
* @return
*/
private static Collection<QualifiedTable> getSingleTableNames(final SQLStatementContext<?> sqlStatementContext,
final ShardingSphereDatabase database, final SingleRule rule, final RouteContext routeContext) {
// 获取数据库类型
DatabaseType databaseType = sqlStatementContext.getDatabaseType();
// 获取合格表
Collection<QualifiedTable> result = getQualifiedTables(database, databaseType, sqlStatementContext.getTablesContext().getTables());
// 如果是索引操作
if (result.isEmpty() && sqlStatementContext instanceof IndexAvailable) {
result = IndexMetaDataUtil.getTableNames(database, databaseType, ((IndexAvailable) sqlStatementContext).getIndexes());
}
// 如果路由单元为空 && 是建表语句,则返回当前的合格表,否则从单表规则中匹配当前的合格表
return routeContext.getRouteUnits().isEmpty() && sqlStatementContext.getSqlStatement() instanceof CreateTableStatement ? result : rule.getSingleTableNames(result);
}
/**
* 获取满足条件的表。其中QualifiedTable中,schema为表的owner,如果不存在,默认为logic_db
* @param database
* @param databaseType
* @param tableSegments sql语句中的表部分,记录sql语句涉及的表
* @return
*/
private static Collection<QualifiedTable> getQualifiedTables(final ShardingSphereDatabase database, final DatabaseType databaseType, final Collection<SimpleTableSegment> tableSegments) {
Collection<QualifiedTable> result = new LinkedList<>();
// 获取默认的schema名称。默认为databaseName,即logic_db
String schemaName = DatabaseTypeEngine.getDefaultSchemaName(databaseType, database.getName());
// 遍历表部分
for (SimpleTableSegment each : tableSegments) {
// 获取表中定义的schema
String actualSchemaName = each.getOwner().map(optional -> optional.getIdentifier().getValue()).orElse(schemaName);
// 创建合格表
result.add(new QualifiedTable(actualSchemaName, each.getTableName().getIdentifier().getValue()));
}
return result;
}
/**
* 同源校验。sql语句中的所有表是否在同一个数据源中
* @param sqlStatementContext
* @param rule
* @param props
* @param singleTableNames
* @param routeContext
*/
private static void validateSameDataSource(final SQLStatementContext<?> sqlStatementContext, final SingleRule rule,
final ConfigurationProperties props, final Collection<QualifiedTable> singleTableNames, final RouteContext routeContext) {
// 获取属性中配置的SQL联合类型
String sqlFederationType = props.getValue(ConfigurationPropertyKey.SQL_FEDERATION_TYPE);
boolean allTablesInSameDataSource = !"NONE".equals(sqlFederationType)
// 如果配置不等于 NONE,则为查询语句 || 所有单表在同一个数据源中,则返回true;否则返回false
? sqlStatementContext instanceof SelectStatementContext || rule.isSingleTablesInSameDataSource(singleTableNames)
// 如果配置为 NONE(默认值),则所有的表都需要是同一个数据源,包括设置了分片规则要执行的实际表
: rule.isAllTablesInSameDataSource(routeContext, singleTableNames);
Preconditions.checkState(allTablesInSameDataSource, "All tables must be in the same datasource.");
}
@Override
public int getOrder() {
return SingleOrder.ORDER;
}
@Override
public Class<SingleRule> getTypeClass() {
return SingleRule.class;
}
}
在createRouteContext()和decorateRouteContext()方法中,功能类似。核心逻辑如下:
1)在createRouteContext()中,新添加一个判断。如果只配置了一个数据源,那么获取数据源名称,创建一个数据源的映射,然后创建一个路由单元,添加到新创建的RouteContext,结束方法;
2)从SQL语句中获取表部分,每个表创建一个QualifiedTable对象,其中schema默认为logic_db;
3)进行同一个数据源的有效性判断,如果校验不通过,抛出异常;
此处针对SQL联邦类型进行判断,如果设置了非NONE的值,对于查询语句或所有涉及的单表都在同一个数据源,满足这两个条件的任意一个验证结果都为有效;如果是NONE(默认值),本次SQL操作涉及的所有最终执行的真实表(包括分片后的真实表以及单表),必现在同一个数据源中,否则验证无效,抛出异常;
4)通过SingleRouteEngineFactory.newInstance(),创建一个路由引擎,执行route()方法,添加路由单表的路由信息;
对于存在QualifiedTable对象的操作,返回的是SingleStandardRouteEngine;如果是Schema的增删改操作,返回SingleDatabaseBroadcastRouteEngine;
SingleStandardRouteEngine
对于SQL语句中存在单表的操作,通过SingleStandardRouteEngine执行路由,创建或合并路由上下文对象。
SingleStandardRouteEngine的源码如下:
package org.apache.shardingsphere.single.route.engine;
/**
* 标准单表的路由引擎
*/
@RequiredArgsConstructor
public final class SingleStandardRouteEngine implements SingleRouteEngine {
private final Collection<QualifiedTable> singleTableNames;
private final SQLStatement sqlStatement;
/**
* 单表的路由
* @param routeContext 路由上下文
* @param singleRule 单表规则
*/
public void route(final RouteContext routeContext, final SingleRule singleRule) {
// 如果路由单元为空 || 是查询语句
if (routeContext.getRouteUnits().isEmpty() || sqlStatement instanceof SelectStatement) {
route0(routeContext, singleRule);
} else {
// 重新创建一个路由单元
RouteContext newRouteContext = new RouteContext();
route0(newRouteContext, singleRule);
// 路由单元合并
combineRouteContext(routeContext, newRouteContext);
}
}
/**
* 合并路由上下文
* @param routeContext
* @param newRouteContext
*/
private void combineRouteContext(final RouteContext routeContext, final RouteContext newRouteContext) {
// 获取路由上下文的路由单元。key为数据源的逻辑名,value为路由单元
Map<String, RouteUnit> dataSourceRouteUnits = getDataSourceRouteUnits(newRouteContext);
// 移除不在newRouteContext中的路由单元
routeContext.getRouteUnits().removeIf(each -> !dataSourceRouteUnits.containsKey(each.getDataSourceMapper().getLogicName()));
// 遍历,进行表映射的合并
for (Entry<String, RouteUnit> entry : dataSourceRouteUnits.entrySet()) {
routeContext.putRouteUnit(entry.getValue().getDataSourceMapper(), entry.getValue().getTableMappers());
}
}
/**
* 获取路由上下文的路由单元。key为数据源的逻辑名,value为路由单元
* @param newRouteContext
* @return
*/
private Map<String, RouteUnit> getDataSourceRouteUnits(final RouteContext newRouteContext) {
return newRouteContext.getRouteUnits().stream().collect(Collectors.toMap(each -> each.getDataSourceMapper().getLogicName(), Function.identity()));
}
/**
* 路由
* @param routeContext 空的路由上下文
* @param rule 单表规则
*/
private void route0(final RouteContext routeContext, final SingleRule rule) {
// 如果是建表语句
if (sqlStatement instanceof CreateTableStatement) {
// 获取第一个表
QualifiedTable table = singleTableNames.iterator().next();
// 从单表规则中获取匹配的schema和表的DataNode
Optional<DataNode> dataNodeOptional = rule.findSingleTableDataNode(table.getSchemaName(), table.getTableName());
// 没找到
if (!dataNodeOptional.isPresent()) {
// 指定新数据源名称。如果没有默认的数据源,从可用的数据源中随机获取一个数据源,否则为默认数据源
String dataSourceName = rule.assignNewDataSourceName();
// 创建路由映射及路由单元
routeContext.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName, dataSourceName), Collections.singleton(new RouteMapper(table.getTableName(), table.getTableName()))));
} else if (CreateTableStatementHandler.ifNotExists((CreateTableStatement) sqlStatement)) {
// 从查找到的DataNode中获取数据源。如Postgre、OpenGauss没有数据源,只有schema
String dataSourceName = dataNodeOptional.map(DataNode::getDataSourceName).orElse(null);
// 创建路由映射及路由单元
routeContext.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName, dataSourceName), Collections.singleton(new RouteMapper(table.getTableName(), table.getTableName()))));
} else {
throw new TableExistsException(table.getTableName());
}
} else if (sqlStatement instanceof AlterTableStatement || sqlStatement instanceof DropTableStatement || rule.isAllTablesInSameDataSource(routeContext, singleTableNames)) {
fillRouteContext(rule, routeContext, rule.getSingleTableNames(singleTableNames));
}
}
/**
* 填充路由上下文
* @param singleRule 单表规则
* @param routeContext 路由上下文
* @param logicTables SQL中涉及的表
*/
private void fillRouteContext(final SingleRule singleRule, final RouteContext routeContext, final Collection<QualifiedTable> logicTables) {
// 遍历QualifiedTable
for (QualifiedTable each : logicTables) {
String tableName = each.getTableName();
// 通过schema查找
Optional<DataNode> dataNode = singleRule.findSingleTableDataNode(each.getSchemaName(), tableName);
// 如果没找到,抛异常
if (!dataNode.isPresent()) {
throw new SingleTableNotFoundException(tableName);
}
String dataSource = dataNode.get().getDataSourceName();
// put路由单元到路由单元集合中
routeContext.putRouteUnit(new RouteMapper(dataSource, dataSource), Collections.singletonList(new RouteMapper(tableName, tableName)));
}
}
}
在route()方法中,执行如下:
1)如果路由单元为空 || 是查询语句,直接执行route0()方法;
在route0()中,执行如下:
1.1)如果是执行的SQL语句是建表语句;
1.1.1)如果表是一个新的表,即当前不存在的,则创建路由映射和路由单元,添加到路由上下文中;
1.1.2)否则判断对应的建表语句是否添加了 is not exists 的标注,如果有,则创建路由映射和路由单元,添加到路由上下文中;
1.1.3)否则抛异常,表明要创建的表已存在;
1.2)否则,如果是修改表 || 删除表 || 当前路由上下文以及单表都在同一个数据源中,则进行路由上下文的填充。通过遍历QualifiedTable,创建创建路由映射和路由单元,添加到路由上下文中;
如果是查询语句,且存在单表,那么SQL语句中涉及的所有表(包括分片后的实际表、单表)都必现在同一个数据源中,否则路由上下文内容没有变化。因为在单条查询语句中,所涉及的真实表必现在同一个数据源中,否则无法查询。单表的逻辑表名和真实表名是一致的,所以无需额外路由;
2)否则重新创建一个路由单元,执行route0()方法,然后进行路由合并;
2.1)此处新创建一个路由单元,然后执行route0(),和 1)中执行route1()的不同之处在于,在1.2)中判断所有表在同一个数据源中,只需要判断单表,因为此时的分片表为空,其他都一样;
2.2)路由上下文合并。将同一个数据源中的表进行合并;
小结
限于篇幅,本篇先分析到这里,以下做一个小结:
1)ShardingSpherePreparedStatement在开始真正执行SQL之前,会先通过KernelProcessor的generateExecutionContext()创建执行上下文ExecutionContext对象;
2)在创建ExecutionContext对象前,先通过SQLRouteEngine的route()方法进行路由,创建路由上下文RouteContext对象;
3)在SQLRouteEngine的route()方法中,先获取一个路由执行器,执行路由执行器的route()方法,创建路由上下文对象;
路由执行器分为 AllSQLRouteExecutor(全路由执行器,即在所有的配置的数据源中执行该SQL语句)和 PartialSQLRouteExecutor(部分路由执行器)。只有对于MySQL数据库的显示表或表状态的SQL语句才使用全路由执行器;
4)在 PartialSQLRouteExecutor 中通过route()进行路由时,执行如下:
4.1)执行findDataSourceByHint(),判断是否通过HintManager或SQL中的注释指定了数据源。如果有,则创建路由映射、路由单元,添加到新创建的RouteContext路由上下文中,返回路由上下文对象;
4.2)如果没有通过Hint指定数据源,则遍历设置的路由器,执行路由器创建路由上下文或装饰路由上下文;
可配置的路由器包括:分片路由器(ShardingSQLRouter)、单表路由器(SingleSQLRouter)、读写分离路由器(ReadwriteSplittingSQLRouter)、数据库发现路由器(DatabaseDiscoverySQLRouter)、影子库路由器(ShadowSQLRouter)。并按以上顺序执行;
4.3)分片路由器ShardingSQLRouter是根据设置的分片键,解析SQL语句中的Insert的values部分或SQL语句中的Where部分,最早执行分片算法中的doSharding(),获取路由的数据源和表。创建路由映射、路由单元,添加到新创建的RouteContext路由上下文中;
详见:【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理-CSDN博客
4.4)单表(没有设置分片的表)路由器SingleSQLRouter是系统默认添加的路由器,解析SQL语句中的表,查找其中的单表,创建路由映射、路由单元,添加到RouteContext路由上下文中;
a)解析SQL语句中的表,每个表创建一个QualifiedTable对象,其中schema默认为logic_db;
b)同源有效性判断。如果校验不通过,抛出异常。针对SQL联邦类型进行判断,如果设置了非NONE的值,对于查询语句或所有涉及的单表都在同一个数据源,满足这两个条件的任意一个验证结果都为有效;如果是NONE(默认值),本次SQL操作涉及的所有最终执行的真实表(包括分片后的真实表以及单表);
c)通过SingleStandardRouteEngine的route()方法进行最终路由,对单表创建路由映射、路由单元,添加或合并到RouteContext路由上下文中。对于查询语句,此处会进行全表的同源再次判断。(如果是Schema的增删改操作,在SingleDatabaseBroadcastRouteEngine中进行路由);
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。