【源码】Sharding-JDBC源码分析之SQL重写实现原理

news2025/1/16 13:43:17

 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单表路由

21、【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理

22、【源码】Sharding-JDBC源码分析之SQL中读写分离路由ReadwriteSplittingSQLRouter的原理

23、 【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理

24、【源码】Sharding-JDBC源码分析之SQL中影子库ShadowSQLRouter路由的原理

25、【源码】Sharding-JDBC源码分析之SQL重写实现原理

前言

在Sharding Sphere框架中,在数据源中真正执行SQL语句之前,先解析SQL,结合配置的规则,进行重新路由,前面用了5篇介绍了SQL路由的实现原理。路由之后,根据路由映射,对SQL进行重写,如替换SQL真正需要执行的表等。本篇从源码的角度,分析SQL重写的实现原理。

ShardingSpherePreparedStatement回顾

在【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由-CSDN博客中分析在执行SQL语句前,会进行SQL路由。通过配置的路由规则,创建RouteContext对象,在RouteContext路由上下文对象中,包含了SQL真正执行的数据源、逻辑表及真实表的映射。

创建完RouteContext路由上下文对象之后,执行rewrite()进行路由重写。rewrite()代码如下:

package org.apache.shardingsphere.infra.context.kernel;

/**
 * 内核处理器
 */
public final class KernelProcessor {
    
    /**
     * sql重写
     * @param queryContext 查询上下文
     * @param database 数据库信息
     * @param globalRuleMetaData 全局规则源数据
     * @param props 配置是props
     * @param routeContext 路由上下文
     * @param connectionContext 连接上下文
     * @return
     */
    private SQLRewriteResult rewrite(final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingSphereRuleMetaData globalRuleMetaData,
                                     final ConfigurationProperties props, final RouteContext routeContext, final ConnectionContext connectionContext) {
        // 创建SQL重写条目,包含重写装饰器
        SQLRewriteEntry sqlRewriteEntry = new SQLRewriteEntry(database, globalRuleMetaData, props);
        // 重写
        return sqlRewriteEntry.rewrite(queryContext.getSql(), queryContext.getParameters(), queryContext.getSqlStatementContext(), routeContext, connectionContext);
    }
	
}

在rewrite()方法中,创建一个SQLRewriteEntry重写对象,执行SQLRewriteEntry的rewrite()方法。

SQLRewriteEntry

SQLRewriteEntry的源码如下:

package org.apache.shardingsphere.infra.rewrite;

/**
 * SQL 重写
 */
public final class SQLRewriteEntry {

    // 数据库
    private final ShardingSphereDatabase database;

    // 配置的全局规则
    private final ShardingSphereRuleMetaData globalRuleMetaData;

    // 配置的属性
    private final ConfigurationProperties props;

    // 规则中的sql重写装饰器上下文
    @SuppressWarnings("rawtypes")
    private final Map<ShardingSphereRule, SQLRewriteContextDecorator> decorators;
    
    public SQLRewriteEntry(final ShardingSphereDatabase database, final ShardingSphereRuleMetaData globalRuleMetaData, final ConfigurationProperties props) {
        this.database = database;
        this.globalRuleMetaData = globalRuleMetaData;
        this.props = props;
		// 通过SPI,结合配置的规则,获取重写装饰器
        decorators = OrderedSPIRegistry.getRegisteredServices(SQLRewriteContextDecorator.class, database.getRuleMetaData().getRules());
    }
    
    /**
     * 重写
     * @param sql 当前的sql语句
     * @param params sql对应的参数值
     * @param sqlStatementContext sql语句的上下文
     * @param routeContext 解析的路由上下文
     * @param connectionContext 连接上下文
     * @return
     */
    public SQLRewriteResult rewrite(final String sql, final List<Object> params, final SQLStatementContext<?> sqlStatementContext,
                                    final RouteContext routeContext, final ConnectionContext connectionContext) {
        // 创建 SQLRewriteContext
        SQLRewriteContext sqlRewriteContext = createSQLRewriteContext(sql, params, sqlStatementContext, routeContext, connectionContext);
        // 获取SQL转换器规则
        SQLTranslatorRule rule = globalRuleMetaData.getSingleRule(SQLTranslatorRule.class);
        DatabaseType protocolType = database.getProtocolType();
        Map<String, DatabaseType> storageTypes = database.getResourceMetaData().getStorageTypes();
        return routeContext.getRouteUnits().isEmpty()
                // 如果没有路由单元
                ? new GenericSQLRewriteEngine(rule, protocolType, storageTypes).rewrite(sqlRewriteContext)
                // 有路由单元
                : new RouteSQLRewriteEngine(rule, protocolType, storageTypes).rewrite(sqlRewriteContext, routeContext);
    }

    /**
     * 创建Sql重写上下文
     * @param sql
     * @param params
     * @param sqlStatementContext
     * @param routeContext
     * @param connectionContext
     * @return
     */
    private SQLRewriteContext createSQLRewriteContext(final String sql, final List<Object> params, final SQLStatementContext<?> sqlStatementContext,
                                                      final RouteContext routeContext, final ConnectionContext connectionContext) {
        // 创建重写上下文
        SQLRewriteContext result = new SQLRewriteContext(database.getName(), database.getSchemas(), sqlStatementContext, sql, params, connectionContext);
        // 遍历重写装饰器,执行装饰器的decorate()方法
        decorate(decorators, result, routeContext);
        // 遍历sql令牌生成器,创建sql令牌。如分页令牌、自动主键令牌、distinct()令牌等
        result.generateSQLTokens();
        return result;
    }

    /**
     * 遍历重写装饰器,执行装饰器的decorate()方法
     * @param decorators
     * @param sqlRewriteContext
     * @param routeContext
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void decorate(final Map<ShardingSphereRule, SQLRewriteContextDecorator> decorators, final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {
        for (Entry<ShardingSphereRule, SQLRewriteContextDecorator> entry : decorators.entrySet()) {
            entry.getValue().decorate(entry.getKey(), props, sqlRewriteContext, routeContext);
        }
    }
}

3.1 构造方法

构造方法主要执行如下:

1)记录基本信息;

2)通过SPI,结合配置的规则,获取重写装饰器;

系统实现的装饰器包括:

a)ShardingSQLRewriteContextDecorator:分片重写装饰器。配置分片规则时,通过SPI获取;

b)EncryptSQLRewriteContextDecorator:加密重写装饰器,配置加密规则时,通过SPI获取;

3.2 rewrite()重写方法

在KernelProcessor中通过该rewrite()方法,执行SQL重写,创建SQLRewriteResult对象。主要执行如下:

1)创建SQLRewriteContext对象;

1.1)创建SQLRewriteContext对象;

1.2)执行decorate()方法,遍历重写装饰器,执行装饰器的decorate()方法。如设置了分片规则,则执行ShardingSQLRewriteContextDecorator的decorate()进行SQLRewriteContext对象的装饰增强。如添加参数重写器创建器、SQL令牌生成器;

1.3)执行SQLRewriteContext对象的generateSQLTokens(),遍历sql令牌生成器,创建sql令牌。如分页令牌、自动主键令牌、distinct()令牌等;

1.4)返回SQLRewriteContext对象;

2)从全局规则元数据中获取SQL转换器规则对象;

3)创建SQL重写引擎,执行重写引擎的rewrite()方法;

如果路由上下文中的路由单元为空,说明没有路由映射,创建GenericSQLRewriteEngine;否则创建RouteSQLRewriteEngine。然后执行重写引擎的rewrite()方法,返回一个SQLRewriteResult对象;

SQLRewriteContext

SQLRewriteContext的源码如下:

package org.apache.shardingsphere.infra.rewrite.context;

/**
 * SQL 重写上下文。维护sql重写令牌、参数生成器
 */
@Getter
public final class SQLRewriteContext {

    // 数据库名称
    private final String databaseName;

    // schema信息
    private final Map<String, ShardingSphereSchema> schemas;

    // sql语句上下文
    private final SQLStatementContext<?> sqlStatementContext;

    // sql语句
    private final String sql;

    // sql语句的参数值
    private final List<Object> parameters;

    // 参数创建者
    private final ParameterBuilder parameterBuilder;

    // SQL令牌,同SQLTokenGenerator生成,如OrderBySQLToken等
    private final List<SQLToken> sqlTokens = new LinkedList<>();

    // sql 令牌生成器。对于大部分的sql操作,都会添加RemoveTokenGenerator
    @Getter(AccessLevel.NONE)
    private final SQLTokenGenerators sqlTokenGenerators = new SQLTokenGenerators();
    
    private final ConnectionContext connectionContext;
    
    public SQLRewriteContext(final String databaseName, final Map<String, ShardingSphereSchema> schemas,
                             final SQLStatementContext<?> sqlStatementContext, final String sql, final List<Object> params, final ConnectionContext connectionContext) {
        this.databaseName = databaseName;
        this.schemas = schemas;
        this.sqlStatementContext = sqlStatementContext;
        this.sql = sql;
        parameters = params;
        this.connectionContext = connectionContext;
        // 添加RemoveTokenGenerator生成器
        addSQLTokenGenerators(new DefaultTokenGeneratorBuilder(sqlStatementContext).getSQLTokenGenerators());
        // 创建参数创建器
        parameterBuilder = ((sqlStatementContext instanceof InsertStatementContext) && (null == ((InsertStatementContext) sqlStatementContext).getInsertSelectContext()))
                // 如果是插入语句,且没有子查询,创建GroupedParameterBuilder参数生成器
                ? new GroupedParameterBuilder(
                        ((InsertStatementContext) sqlStatementContext).getGroupedParameters(), ((InsertStatementContext) sqlStatementContext).getOnDuplicateKeyUpdateParameters())
                // 否则创建标准的参数生成器
                : new StandardParameterBuilder(params);
    }
    
    /**
     * 添加token生成器
     */
    public void addSQLTokenGenerators(final Collection<SQLTokenGenerator> sqlTokenGenerators) {
        this.sqlTokenGenerators.addAll(sqlTokenGenerators);
    }
    
    /**
     * 生成SQL令牌
     */
    public void generateSQLTokens() {
        sqlTokens.addAll(sqlTokenGenerators.generateSQLTokens(databaseName, schemas, sqlStatementContext, parameters, connectionContext));
    }
}

在SQLRewriteContext对象中,保存了当前执行的SQL的语句上下文对象、参数、重写的令牌等。

ShardingSQLRewriteContextDecorator

ShardingSQLRewriteContextDecorator的源码如下:

package org.apache.shardingsphere.sharding.rewrite.context;

/**
 * 用于分片的SQL重写上下文装饰器
 */
@Setter
public final class ShardingSQLRewriteContextDecorator implements SQLRewriteContextDecorator<ShardingRule> {

    /**
     * 装饰
     * @param shardingRule 分片规则
     * @param props 配置的属性
     * @param sqlRewriteContext sql重写上下文
     * @param routeContext 路由上下文
     */
    @SuppressWarnings("rawtypes")
    @Override
    public void decorate(final ShardingRule shardingRule, final ConfigurationProperties props, final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {
        // 如果有参数值
        if (!sqlRewriteContext.getParameters().isEmpty()) {
            // 获取参数重写器
            Collection<ParameterRewriter> parameterRewriters = new ShardingParameterRewriterBuilder(shardingRule,
                    routeContext, sqlRewriteContext.getSchemas(), sqlRewriteContext.getSqlStatementContext()).getParameterRewriters();
            // 参数重写,执行重写器的rewrite()方法
            rewriteParameters(sqlRewriteContext, parameterRewriters);
        }
        // 添加分片sql令牌生成器
        sqlRewriteContext.addSQLTokenGenerators(new ShardingTokenGenerateBuilder(shardingRule, routeContext, sqlRewriteContext.getSqlStatementContext()).getSQLTokenGenerators());
    }
    
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void rewriteParameters(final SQLRewriteContext sqlRewriteContext, final Collection<ParameterRewriter> parameterRewriters) {
        for (ParameterRewriter each : parameterRewriters) {
            each.rewrite(sqlRewriteContext.getParameterBuilder(), sqlRewriteContext.getSqlStatementContext(), sqlRewriteContext.getParameters());
        }
    }
    
    @Override
    public int getOrder() {
        return ShardingOrder.ORDER;
    }
    
    @Override
    public Class<ShardingRule> getTypeClass() {
        return ShardingRule.class;
    }
}

如果配置了分片规则,则在 SQLRewriteEntry 的构造方法中会创建ShardingSQLRewriteContextDecorator装饰器对象。在SQLRewriteEntry的rewrite()方法中,执行ShardingSQLRewriteContextDecorator的decorate()方法。

decorate()方法执行如下:

1)如果SQL操作语句有参数值,则执行如下:

1.1)创建分片参数重写器创建器ShardingParameterRewriterBuilder,获取参数重写器;

在 ShardingParameterRewriterBuilder 类的getParameterRewriters()方法中,会返回两个参数重写器:

a)ShardingGeneratedKeyInsertValueParameterRewriter:自动生成插入语句中的主键;

b)ShardingPaginationParameterRewriter:自动替换分页查询参数的值;

1.2)执行rewriteParameters()方法,进行参数重写。执行ShardingParameterRewriterBuilder的rewrite()进行参数重写;

a)ShardingGeneratedKeyInsertValueParameterRewriter:根据主键生成器,添加主键值;

b)ShardingPaginationParameterRewriter:自动替换分页查询参数的值。如分页查询第二页10~20的数据,且分片到两张表,那么每张表应该查询的记录是1~20条,因为并无法知道第一页的10条是在哪张表获取的,此时的1和20就是通过该参数重写器进行自动替换的;

2)执行sqlRewriteContext的addSQLTokenGenerators()方法,添加分片SQL令牌生成器ShardingTokenGenerateBuilder的getSQLTokenGenerators()方法返回的令牌生成器;

a)在ShardingTokenGenerateBuilder令牌生成器的getSQLTokenGenerators()方法中,添加17个令牌生成器,如order by、distinct、offset、rowcount等;

b)对应的令牌生成器,用于生成对应令牌。如order by的生成器,生成OrderByToken;

c)在进行SQL重写是,会调用令牌的toString()方法。toString()方法返回对应令牌的SQL语句。如OrderByToken的toString()方法,返回 order by columnLabel orderDirection,即order by 字符串加上对应排序的列及排序方向;

RouteSQLRewriteEngine

如果路由上下文不为空,即有路由数据源映射信息,则创建RouteSQLRewriteEngine对象,并执行RouteSQLRewriteEngine的rewrite()方法,进行SQL重写。

RouteSQLRewriteEngine的源码如下:

package org.apache.shardingsphere.infra.rewrite.engine;

/**
 * 路由的SQL重写引擎
 */
@RequiredArgsConstructor
public final class RouteSQLRewriteEngine {

    // 配置的sql转换规则
    private final SQLTranslatorRule translatorRule;

    // 可通过proxy-frontend-database-protocol-type属性配置,
    // 如果没有配置,为当前配置的数据源中可用的第一个数据源的数据库类型
    private final DatabaseType protocolType;

    // 当前配置的数据源对应的数据库类型
    private final Map<String, DatabaseType> storageTypes;
    
    /**
     * 重写sql和参数
     * @param sqlRewriteContext sql重写上下文
     * @param routeContext 路由上下文
     * @return
     */
    public RouteSQLRewriteResult rewrite(final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {
        // key为路由单元;value为重写后的sql单元
        Map<RouteUnit, SQLRewriteUnit> sqlRewriteUnits = new LinkedHashMap<>(routeContext.getRouteUnits().size(), 1);
        // 聚合路由单元组。按数据源名称分组。遍历
        for (Entry<String, Collection<RouteUnit>> entry : aggregateRouteUnitGroups(routeContext.getRouteUnits()).entrySet()) {
            Collection<RouteUnit> routeUnits = entry.getValue();
            // 判断是否需要聚合重写
            if (isNeedAggregateRewrite(sqlRewriteContext.getSqlStatementContext(), routeUnits)) {
                // 对于需要聚合的sql进行重写,使用union all,一次连接执行多个查询
                sqlRewriteUnits.put(routeUnits.iterator().next(), createSQLRewriteUnit(sqlRewriteContext, routeContext, routeUnits));
            } else {
                // 添加重写单元
                addSQLRewriteUnits(sqlRewriteUnits, sqlRewriteContext, routeContext, routeUnits);
            }
        }
        return new RouteSQLRewriteResult(translate(sqlRewriteContext.getSqlStatementContext().getSqlStatement(), sqlRewriteUnits));
    }

    /**
     * 创建重写单元。对于需要聚合的sql进行重写,使用union all,一次连接执行多个查询
     * @param sqlRewriteContext sql重写上下文
     * @param routeContext 路由上下文
     * @param routeUnits 路由单元
     * @return
     */
    private SQLRewriteUnit createSQLRewriteUnit(final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext, final Collection<RouteUnit> routeUnits) {
        Collection<String> sql = new LinkedList<>();
        List<Object> params = new LinkedList<>();
        // 判断是select语句是否包含$符号
        boolean containsDollarMarker = sqlRewriteContext.getSqlStatementContext() instanceof SelectStatementContext
                && ((SelectStatementContext) (sqlRewriteContext.getSqlStatementContext())).isContainsDollarParameterMarker();
        for (RouteUnit each : routeUnits) {
            // 创建RouteSQLBuilder,重新拼接sql
            sql.add(SQLUtil.trimSemicolon(new RouteSQLBuilder(sqlRewriteContext, each).toSQL()));
            // 如果包含$符号 && 有参数值
            if (containsDollarMarker && !params.isEmpty()) {
                continue;
            }
            // 添加参数
            params.addAll(getParameters(sqlRewriteContext.getParameterBuilder(), routeContext, each));
        }
        return new SQLRewriteUnit(String.join(" UNION ALL ", sql), params);
    }

    /**
     * 添加SQL重写单元。每个路由单元创建一个SQLRewriteUnit,每个SQLRewriteUnit对sql进行重写
     * @param sqlRewriteUnits sql重写单元
     * @param sqlRewriteContext 重写上下文
     * @param routeContext 路由上下文
     * @param routeUnits 路由单元
     */
    private void addSQLRewriteUnits(final Map<RouteUnit, SQLRewriteUnit> sqlRewriteUnits, final SQLRewriteContext sqlRewriteContext,
                                    final RouteContext routeContext, final Collection<RouteUnit> routeUnits) {
        // 遍历路由单元
        for (RouteUnit each : routeUnits) {
            // 每个路由单元创建一个SQLRewriteUnit,每个SQLRewriteUnit对sql进行重写
            sqlRewriteUnits.put(each, new SQLRewriteUnit(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(), getParameters(sqlRewriteContext.getParameterBuilder(), routeContext, each)));
        }
    }

    /**
     * 判断是否需要聚合重写。没有子查询或join查询 && 没有排序和分页 && 没有锁部分,返回true;否则为false
     * @param sqlStatementContext sql语句上下文
     * @param routeUnits 路由单元
     * @return
     */
    private boolean isNeedAggregateRewrite(final SQLStatementContext<?> sqlStatementContext, final Collection<RouteUnit> routeUnits) {
        // 只有查询语句 && 大于一个路由单元,才需要聚合
        if (!(sqlStatementContext instanceof SelectStatementContext) || routeUnits.size() == 1) {
            return false;
        }
        SelectStatementContext statementContext = (SelectStatementContext) sqlStatementContext;
        boolean containsSubqueryJoinQuery = statementContext.isContainsSubquery() || statementContext.isContainsJoinQuery();
        boolean containsOrderByLimitClause = !statementContext.getOrderByContext().getItems().isEmpty() || statementContext.getPaginationContext().isHasPagination();
        boolean containsLockClause = SelectStatementHandler.getLockSegment(statementContext.getSqlStatement()).isPresent();
        // 没有子查询或join查询 && 没有排序和分页 && 没有锁部分
        boolean needAggregateRewrite = !containsSubqueryJoinQuery && !containsOrderByLimitClause && !containsLockClause;
        statementContext.setNeedAggregateRewrite(needAggregateRewrite);
        return needAggregateRewrite;
    }

    /**
     * 聚合路由单元组。按数据源名称分组
     * @param routeUnits 路由单元
     * @return
     */
    private Map<String, Collection<RouteUnit>> aggregateRouteUnitGroups(final Collection<RouteUnit> routeUnits) {
        Map<String, Collection<RouteUnit>> result = new LinkedHashMap<>(routeUnits.size(), 1);
        for (RouteUnit each : routeUnits) {
            String dataSourceName = each.getDataSourceMapper().getActualName();
            result.computeIfAbsent(dataSourceName, unused -> new LinkedList<>()).add(each);
        }
        return result;
    }

    /**
     * 获取参数
     * @param paramBuilder 参数创建器
     * @param routeContext 路由上下文
     * @param routeUnit 路由单元
     * @return
     */
    private List<Object> getParameters(final ParameterBuilder paramBuilder, final RouteContext routeContext, final RouteUnit routeUnit) {
        // 如果是标准参数生成器
        if (paramBuilder instanceof StandardParameterBuilder) {
            // 获取参数值,返回的类型为List<List<Object>>,即每个参数都为List<Object>类型
            return paramBuilder.getParameters();
        }
        return routeContext.getOriginalDataNodes().isEmpty()
                // 如果没有路由信息
                ? ((GroupedParameterBuilder) paramBuilder).getParameters()
                // 如果有路由信息,获取路由参数
                : buildRouteParameters((GroupedParameterBuilder) paramBuilder, routeContext, routeUnit);
    }

    /**
     * 构建路由参数
     * @param paramBuilder 参数创建器
     * @param routeContext 路由上下文
     * @param routeUnit 路由单元
     * @return
     */
    private List<Object> buildRouteParameters(final GroupedParameterBuilder paramBuilder, final RouteContext routeContext, final RouteUnit routeUnit) {
        List<Object> result = new LinkedList<>();
        int count = 0;
        // 遍历原始数据节点
        for (Collection<DataNode> each : routeContext.getOriginalDataNodes()) {
            // 找到当前的路由单元的数据节点
            if (isInSameDataNode(each, routeUnit)) {
                // 获取对应下标的分组参数信息
                result.addAll(paramBuilder.getParameters(count));
            }
            count++;
        }
        // 添加通用参数
        result.addAll(paramBuilder.getGenericParameterBuilder().getParameters());
        return result;
    }
    
    private boolean isInSameDataNode(final Collection<DataNode> dataNodes, final RouteUnit routeUnit) {
        if (dataNodes.isEmpty()) {
            return true;
        }
        for (DataNode each : dataNodes) {
            if (routeUnit.findTableMapper(each.getDataSourceName(), each.getTableName()).isPresent()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 翻译转换
     * @param sqlStatement 查询语句
     * @param sqlRewriteUnits 按路由单元重写后的sql单元
     * @return
     */
    private Map<RouteUnit, SQLRewriteUnit> translate(final SQLStatement sqlStatement, final Map<RouteUnit, SQLRewriteUnit> sqlRewriteUnits) {
        Map<RouteUnit, SQLRewriteUnit> result = new LinkedHashMap<>(sqlRewriteUnits.size(), 1);
        // 遍历SQL重写单元
        for (Entry<RouteUnit, SQLRewriteUnit> entry : sqlRewriteUnits.entrySet()) {
            // 获取对应单元数据源的数据库类型
            DatabaseType storageType = storageTypes.get(entry.getKey().getDataSourceMapper().getActualName());
            // 通过配置的翻译规则,执行sql翻译
            String sql = translatorRule.translate(entry.getValue().getSql(), sqlStatement, protocolType, storageType);
            // 翻译后,重新创建SQLRewriteUnit
            SQLRewriteUnit sqlRewriteUnit = new SQLRewriteUnit(sql, entry.getValue().getParameters());
            result.put(entry.getKey(), sqlRewriteUnit);
        }
        return result;
    }
}

6.1 rewrite()方法

在rewrite()中,执行如下:

1)调用aggregateRouteUnitGroups(),遍历路由单元,获取路由单元中的实际数据源名称,按真实数据源名称对路由单元进行分组,同一个数据源放在同一个集合中;

2)按数据源名称遍历进行遍历;

2.1)获取对应数据源的路由单元集合;

2.2)判断是否需要聚合重写,需要则进行重写;

2.2.1)如果不是查询语句 || 路由单元只有一个,说明不需要聚合,返回false;

2.2.2)(没有子查询 || join查询) && 没有排序和分页 && 没有锁部分,返回true;否则为false;

2.2.3)如果以上返回true,则表明通过一个数据源,有多个路由单元,而此处的多个路由单元数据源映射是一样的,不同的是表映射。则执行createSQLRewriteUnit(),创建一个SQLRewriteUnit对象,以路由单元为key,SQLRewriteUnit对象为value,添加到Map中;

2.3)如果不需要聚合重写,则执行 addSQLRewriteUnits(),添加重写单元;

遍历路由单元,每个路由单元创建一个SQLRewriteUnit,每个SQLRewriteUnit对sql进行重写。

3)执行translate()方法,进行翻译转换;

遍历SQL重写单元,执行 ranslatorRule 配置的翻译规则的translate()方法,进行翻译,获取新的sql字符串,创建新的SQLRewriteUnit,替换原来的SQLRewriteUnit对象;

4)创建一个RouteSQLRewriteResult对象,返回该对象;

6.2 createSQLRewriteUnit()方法

createSQLRewriteUnit()方法执行如下:

1)判断是select语句是否包含$符号,保存到containsDollarMarker变量;

2)遍历路由单元,执行如下:

2.1)创建RouteSQLBuilder对象,执行toSQL()方法,重写SQL语句。在toSQL()方法中,遍历SQLRewriteContext对象中的重写令牌,重写拼接SQL语句。如在表的令牌对象(TableToken)中,结合路由单元和分配规则,获取SQL语句执行的真实表名,并进行替换;

2.2)如果containsDollarMarker为true && 有参数值,跳过;

2.3)获取参数值;

3)使用union all 连接多个sql语句,创建新的SQLRewriteUnit对象;

6.3 addSQLRewriteUnits() 方法

对于不需要聚合重写的路由单元,则直接遍历路由单元,每个路由单元创建一个RouteSQLBuilder对象,执行toSQL()方法,重写SQL语句。重写后创建SQLRewriteUnit对象。

小结

以上为本篇分析的全部内容,以下做一个小结:

ShardingSpherePreparedStatement在执行SQL语句前,会进行SQL路由。通过配置的路由规则,创建RouteContext对象,在RouteContext路由上下文对象中,包含了SQL真正执行的数据源、逻辑表及真实表的映射。

创建完RouteContext路由上下文对象之后,执行rewrite()进行SQL重写。重写的执行如下:

1)创建一个SQLRewriteEntry对象,执行rewrite()方法;

在SQLRewriteEntry对象的构造方法中,通过SPI,结合配置的规则,获取重写装饰器。如分片重写装饰器、加密重写装饰器;

2)在rewrite()方法中,执行SQL重写,创建SQLRewriteResult对象;

2.1)创建SQLRewriteContext对象;

a)在SQLRewriteContext对象中,保存了当前执行的SQL的语句上下文对象、参数、重写的令牌等。重写装饰器对象(如ShardingSQLRewriteContextDecorator)根据SQL的类型(如分页、自动生成主键等)添加对应的重写令牌到SQLRewriteContext对象;

b)重写令牌主要用于信息的替换;

c)在构造方法中,添加RemoveTokenGenerator生成器,用于生成RemoveToken令牌。该令牌主要用于SQL字符串中某些字符串的移除(替换为空)。如移除SQL语句中的owner.table中的owner信息等;

2.1.1)创建SQLRewriteContext对象;

2.1.2)执行decorate()方法,遍历重写装饰器,执行装饰器的decorate()方法。如设置了分片规则,则执行ShardingSQLRewriteContextDecorator的decorate()进行SQLRewriteContext对象的装饰增强。如添加参数重写器创建器、SQL令牌生成器;

2.1.3)执行SQLRewriteContext对象的generateSQLTokens(),遍历sql令牌生成器,创建sql令牌。如分页令牌、自动主键令牌、distinct()令牌等;

2.2)从全局规则元数据中获取SQL转换器规则对象;

2.3)创建SQL重写引擎,执行重写引擎的rewrite()方法;

如果路由上下文中的路由单元为空,说明没有路由映射,创建GenericSQLRewriteEngine;否则创建RouteSQLRewriteEngine。然后执行重写引擎的rewrite()方法,返回一个SQLRewriteResult对象;

2.4)在RouteSQLRewriteEngine重写引擎的rewrite()方法中,执行如下:

2.4.1)遍历路由单元,获取路由单元中的实际数据源名称,按真实数据源名称对路由单元进行分组,同一个数据源放在同一个集合中;

2.4.2)按数据源名称遍历进行遍历;

a)如果同一个数据源的多个路由单元可以聚合重写,则执行SQL重写,重新拼接SQL语句,使用union all对多个SQL语句进行联合查询。生成一个SQLRewriteUnit对象;

b)如果不需要联合查询,则执行SQL重写,重新拼接SQL语句。生成一个SQLRewriteUnit对象;

2.4.3)执行translate()方法,进行翻译转换;

遍历SQL重写单元,执行 ranslatorRule 配置的翻译规则的translate()方法,进行翻译,获取新的sql字符串,创建新的SQLRewriteUnit,替换原来的SQLRewriteUnit对象;

2.4.4)创建一个RouteSQLRewriteResult对象,返回该对象;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2277555.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《无锡布里渊分布式光纤传感技术:照亮能源领域新征程》

在全球能源格局加速变革、能源需求持续攀升的当下&#xff0c;保障能源系统的安全、高效运行成为重中之重。分布式光纤传感技术宛如一颗璀璨的科技新星&#xff0c;正以前所未有的姿态融入能源领域&#xff0c;重塑着能源开采、运输与监测的传统模式。 石油与天然气作为现代工…

win32汇编环境,窗口程序中组合框的应用举例

;运行效果 ;win32汇编环境,窗口程序中组合框的应用举例 ;比如在窗口程序中生成组合框&#xff0c;增加子项&#xff0c;删除某项&#xff0c;取得指定项内容等 ;直接抄进RadAsm可编译运行。重点部分加备注。 ;以下是ASM文件 ;>>>>>>>>>>>>…

PHP智慧小区物业管理小程序

&#x1f31f;智慧小区物业管理小程序&#xff1a;重塑社区生活&#xff0c;开启便捷高效新篇章 &#x1f31f; 智慧小区物业管理小程序是一款基于PHPUniApp精心雕琢的智慧小区物业管理小程序&#xff0c;它犹如一股清新的科技之风&#xff0c;吹进了现代智慧小区的每一个角落…

win10电脑 定时关机

win10电脑 定时关机 https://weibo.com/ttarticle/p/show?id2309405110707766296723 二、使用任务计划程序设置定时关机打开任务计划程序&#xff1a; 按下“Win S”组合键&#xff0c;打开搜索框。 在搜索框中输入“任务计划程序”&#xff0c;然后点击搜索结果中的“任务…

【数模学习笔记】插值算法和拟合算法

声明&#xff1a;以下笔记中的图片以及内容 均整理自“数学建模学习交流”清风老师的课程资料&#xff0c;仅用作学习交流使用 文章目录 插值算法定义三个类型插值举例插值多项式分段插值三角插值 一般插值多项式原理拉格朗日插值法龙格现象分段线性插值 牛顿插值法 Hermite埃尔…

​HPM6700——以太网通信lwip_udpecho_freertos_socket

1. 概述 本示例展示在FreeRTOS系统下的UDP回送通讯 PC 通过以太网发送UDP数据帧至MCU&#xff0c;MCU将接收的数据帧回发至PC 2. 硬件设置 使用USB Type-C线缆连接PC USB端口和PWR DEBUG端口 使用以太网线缆连接PC以太网端口和开发板RGMII或RMII端口 3. 工程配置 以太网端…

低代码独特架构带来的编译难点及多线程解决方案

前言 在当今软件开发领域&#xff0c;低代码平台以其快速构建应用的能力&#xff0c;吸引了众多开发者与企业的目光。然而&#xff0c;低代码平台独特的架构在带来便捷的同时&#xff0c;也给编译过程带来了一系列棘手的难点。 一&#xff0c;低代码编译的难点 &#xff08;1…

FPGA 21 ,深入理解 Verilog 中的基数,以及二进制数与十进制数之间的关系( Verilog中的基数 )

目录 前言 一. 基数基础 1.1 基数介绍 2.1 基数符号 3.1 二进制数 二. 二进制与十进制数 三. 二进制数 3.1 定义寄存器类型变量 3.2 定义线网类型变量 3.3 赋值操作 3.4 解析二进制数为十进制数 四. 代码示例 五. 注意事项 六. 更多操作 前言 在Verilog中&#…

开始使用Panuon开源界面库环境配置并手写VS2019高仿界面

1. Panuon环境配置 1.1. 通过Nuget 安装 Panuon.WPF.UI1.2. xaml引用命名空间1.3. using Panuon.WPF.UI; 2. VS2019 view 2.1. 设置窗体尺寸和title2.2. 添加静态资源 2.2.1. 什么是静态资源 2.3. 主Grid 2.3.1. 盒子模型2.3.2. 嵌套布局 3. 总结 1. Panuon环境配置 1.1. 通…

blender导出镜头动作vmd

1 选中相机 2 在相机属性中找到 MMD摄像机工具 3 在弹窗中给 烘焙动画 打勾&#xff08;图中没忘打了&#xff09;点击确定 等待烘焙完成 4 烘焙结束后选中刚刚新增的物体 5 在mmd tools中点击 动作-导出&#xff08;图中已经导出了&#xff0c;故显示灰色&#xff09;

RabbitMQ确保消息可靠性

消息丢失的可能性 支付服务先扣减余额和更新支付状态&#xff08;这俩是同步调用&#xff09;&#xff0c;然后通过RabbitMq异步调用支付服务更新订单状态。但是有些情况下&#xff0c;可能订单已经支付 &#xff0c;但是更新订单状态却失败了&#xff0c;这就出现了消息丢失。…

重生之我在21世纪学C++—string

一、string 概念 string 字符串是一种更加高级的封装&#xff0c;string 字符串中包含大量的方法&#xff0c;这些方法可以使得字符串的操作变得更加简单。如果 string 使用的好&#xff0c;慢慢你就不想使用字符数组来存放字符串了&#xff0c;因为更简单嘛。 C 将字符串直接…

day10_Structured Steaming

文章目录 Structured Steaming一、结构化流介绍&#xff08;了解&#xff09;1、有界和无界数据2、基本介绍3、使用三大步骤(掌握)4.回顾sparkSQL的词频统计案例 二、结构化流的编程模型&#xff08;掌握&#xff09;1、数据结构2、读取数据源2.1 File Source2.2 Socket Source…

x86_64搭建ARM交叉编译工具链

点击上方"蓝字"关注我们 01、下载 >>> GCC 64位交叉编译下载&#xff1a;https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/ 喜欢那个版本自己找 02、简介 >>> 交叉编译器中“交叉”的意思就是在一个架构…

迅翼SwiftWing | ROS 固定翼开源仿真平台正式发布!

经过前期内测调试&#xff0c;ROS固定翼开源仿真平台今日正式上线&#xff01;现平台除适配PX4ROS环境外&#xff0c;也已实现APROS环境下的单机飞行控制仿真适配。欢迎大家通过文末链接查看项目地址以及具体使用手册。 1 平台简介 ROS固定翼仿真平台旨在实现固定翼无人机决策…

IOS界面传值-OC

1、页面跳转 由 ViewController 页面跳转至 NextViewController 页面 &#xff08;1&#xff09;ViewController ViewController.h #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend ViewController.m #import "ViewController.h" …

用 Python 自动化处理日常任务

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

Linux:地址空间(续)与进程控制

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;地址空间与进程控制》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞&#xff0…

链家房价数据爬虫和机器学习数据可视化预测

完整源码项目包获取→点击文章末尾名片&#xff01;

亿道三防丨三防笔记本是什么意思?和普通笔记本的优势在哪里?

三防笔记本是什么意思&#xff1f;和普通笔记本的优势在哪里&#xff1f; 在现代社会中&#xff0c;笔记本电脑已经成为人们工作和生活中不可或缺的一部分。然而&#xff0c;在一些特殊行业或环境中&#xff0c;普通笔记本电脑由于其脆弱性和对环境条件的敏感性&#xff0c;往…