【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由

Sharding-JDBC系列

1、Sharding-JDBC分库分表的基本使用

2、Sharding-JDBC分库分表之SpringBoot分片策略

3、Sharding-JDBC分库分表之SpringBoot主从配置

4、SpringBoot集成Sharding-JDBC-5.3.0分库分表

5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表

6、【源码】Sharding-JDBC源码分析之JDBC

7、【源码】Sharding-JDBC源码分析之SPI机制

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)方法,创建执行上下文。代码如下:

java 复制代码
/**
 * 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的源码如下:

java 复制代码
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的源码如下:

java 复制代码
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的源码如下:

java 复制代码
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的源码如下:

java 复制代码
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的源码如下:

java 复制代码
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中进行路由);

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

相关推荐
ezreal_pan4 天前
ShardingSphere-Proxy 连接实战:从 Golang 原生 SQL 到 GORM 的应用
golang·shardingsphere·分库分表
vivo互联网技术18 天前
OceanBase 的探索与实践
mysql·oceanbase·分布式数据库·分库分表·tidb迁移
JingAi_jia9172 个月前
【源码】Sharding-JDBC源码分析之Sql解析的原理
分库分表·sharding-jdbc·1024程序员节·sharding jdbc·antlr·springboot分库分表·sql解析原理·mysqlstatement
Dylanioucn2 个月前
【分布式微服务云原生】深入探究:多分片键下的分库分表策略
数据库·分布式·微服务·云原生·分库分表
阿维的博客日记3 个月前
图文并茂解释水平分表,垂直分表,水平分库,垂直分库
数据库·分库分表
JingAi_jia9174 个月前
【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理
database·分库分表·sharding-jdbc·sharding jdbc·springboot分库分表·shardingjdbc源码
wuweijie@apache.org4 个月前
IntelliJ IDEA 集成 ShardingSphere-JDBC 访问分库分表
ide·intellij-idea·shardingsphere·分库分表
JingAi_jia9174 个月前
【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)
分库分表·sharding-jdbc·sharding jdbc·springboot分库分表·分库分表配置·分库分表配置原理