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

 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) {// 创建 SQLRewriteContextSQLRewriteContext 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")@Overridepublic 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());}}@Overridepublic int getOrder() {return ShardingOrder.ORDER;}@Overridepublic 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,重新拼接sqlsql.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);// 翻译后,重新创建SQLRewriteUnitSQLRewriteUnit 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.rhkb.cn/news/2106.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务主流框架和基础设施介绍

概述 微服务架构的落地需要解决服务治理问题&#xff0c;而服务治理依赖良好的底层方案。当前&#xff0c;微服务的底层方案总的来说可以分为两 种&#xff1a;微服务SDK &#xff08;微服务框架&#xff09;和服务网格。 微服务框架运行原理&#xff1a; 应用程序通过接入 SD…

微信小程序集成Vant Weapp移动端开发的框架

什么是Vant Weapp Vant 是一个轻量、可靠的移动端组件库&#xff0c;于 2017 年开源。 目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本&#xff0c;并由社区团队维护 React 版本和支付宝小程序版本。 官网地睛&#xff1a;介绍 - Vant Weapp (vant-ui.gith…

(STM32笔记)十二、DMA的基础知识与用法 第二部分

我用的是正点的STM32F103来进行学习&#xff0c;板子和教程是野火的指南者。 之后的这个系列笔记开头未标明的话&#xff0c;用的也是这个板子和教程。 DMA的基础知识与用法 二、DMA传输设置1、数据来源与数据去向外设到存储器存储器到外设存储器到存储器 2、每次传输大小3、传…

C语言 - 可变参数函数 va_list、va_start、va_arg、va_end

目录 一、_INTSIZEOF宏分析 二、可变参数函数介绍 1、va_list 2、va_start 3、va_arg 4、va_end 三、使用介绍 示例1&#xff1a; 示例2&#xff1a; 一、_INTSIZEOF宏分析 #define _INTSIZEOF(n) ((sizeof(n)sizeof(int)-1)&~(sizeof(int) - 1) ) 功能&#x…

【Rust自学】12.2. 读取文件

12.2.0. 写在正文之前 第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print)&#xff0c;是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。 这个项目分为这么几步&#xff1a; 接收命令行参数读…

记一次OpenEuler Linux磁盘分区表损坏的数据恢复

问题复现 原本有一台GIS地图服务器存放大量数据&#xff0c;突然有一天磁盘满了&#xff0c;于是运维人员照常进行磁盘扩容。但由于误操作&#xff0c;导致使用fdisk的时候把分区表损坏了&#xff0c;表现如下&#xff1a; 这里可以看到启动时能看到xvda被分为了xvda1和xvda2…

二手车交易系统的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统二手车交易信息管理难度大&#xff0c;容错率低&#xf…

【大模型系列篇】数字人音唇同步模型——腾讯开源MuseTalk

之前有一期我们体验了阿里开源的半身数字人项目EchoMimicV2&#xff0c;感兴趣的小伙伴可跳转至《AI半身数字人开箱体验——开源项目EchoMimicV2》&#xff0c;今天带大家来体验腾讯开源的数字人音唇同步模型MuseTalk。 MuseTalk 是一个实时高品质音频驱动的唇形同步模型&#…

如何禁用 PySpark 在运行时打印信息

我已经开始使用 PySpark。PySpark 的版本是3.5.4&#xff0c;它是通过 进行安装的pip。 这是我的代码&#xff1a; from pyspark.sql import SparkSession pyspark SparkSession.builder.master("local[8]").appName("test").getOrCreate() df pyspark…

HTML拖拽功能(纯html5+JS实现)

1、HTML拖拽--单元行拖动 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><…

GLM: General Language Model Pretraining with Autoregressive Blank Infilling论文解读

论文地址&#xff1a;https://arxiv.org/abs/2103.10360 参考&#xff1a;https://zhuanlan.zhihu.com/p/532851481 GLM混合了自注意力和masked注意力&#xff0c;而且使用了2D位置编码。第一维的含义是在PartA中的位置&#xff0c;如5 5 5。第二维的含义是在Span内部的位置&a…

华为数通HCIE备考经验分享

在分享我的考试心得前我先介绍一下我自己&#xff0c;我叫郑同学&#xff0c;22岁&#xff0c;就读于深圳信息职业技术学院移动通信技术专业&#xff0c;在2024年的9月&#xff0c;我成功获得了HCIE-Datacom证书。 考证契机 我的备考之旅始于去年2023年的华为ICT大赛。在这场…

Web开发(二)CSS3基础与进阶

Web开发&#xff08;二&#xff09;CSS3基础与进阶 写在前面 参考黑马程序员前端Web教程做的笔记&#xff0c;主要是想后面自己搭建网页玩。 这部分是前端HTML5CSS3移动web视频教程的CSS3基础与进阶部分&#xff0c;包括CSS3的选择器、文字控制属性、背景属性、显示模式等CS…

使用PWM生成模式驱动BLDC三相无刷直流电机

引言 在 TI 的无刷直流 (BLDC) DRV8x 产品系列使用的栅极驱动器应用中&#xff0c;通常使用一些控制模式来切换MOSFET 开关的输出栅极。这些控制模式包括&#xff1a;1x、3x、6x 和独立脉宽调制 (PWM) 模式。   不过&#xff0c;DRV8x 产品系列&#xff08;例如 DRV8311&…

mac 安装docker

1、下载docker 进入 /Applications/Docker.app/Contents/MacOS/Docker Desktop.app/Contents/Resources目录 把app.asar 文件备份 将下载的中文包复制进去。修改成一样的名字 [汉化包下载地址](https://github.com/asxez/DockerDesktop-CN)

jupyter notebook练手项目:线性回归——学习时间与成绩的关系

线性回归——学习时间与学习成绩的关系 第1步&#xff1a;导入工具库 pandas——数据分析库&#xff0c;提供了数据结构&#xff08;如DataFrame和Series&#xff09;和数据操作方法&#xff0c;方便对数据集进行读取、清洗、转换等操作。 matplotlib——绘图库&#xff0c;p…

Vue3使用vue-count-to数字滚动模块报错解决方案

小伙伴们是不是遇到了vue3项目使用vue-count-to出现报错的问题 报错如下&#xff1a; TypeError: Cannot read properties of undefined (reading _c) 这个错误信息具体是说没读取到_c的属性 具体不清楚是什么原因&#xff0c;排查还得去看源码&#xff0c;所以我们来解决&a…

「实战应用」如何为DHTMLX JavaScript 甘特图添加进度线

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 今天&#xff0c;您将学习如何使用进度线补充JavaScript 甘特图&#xff0c;以便于监控项目进度。 DHTMLX Gantt 最新试用版下载 …

系统架构设计师-第1章-计算机系统知识要点

【本章学习建议】 根据考试大纲&#xff0c;本章主要考查系统架构设计师单选题&#xff0c;预计考1分左右。第二版教材2.2节增加了本块内容&#xff0c;但较为简略&#xff0c;需要课程补充&#xff0c;属于非重点内容。 1.1 计算机硬件组成 计算机的基本硬件系统由运算器、控…

【C】初阶数据结构3 -- 单链表

之前在顺序表那一篇文章中&#xff0c;提到顺序表具有的缺点&#xff0c;比如头插&#xff0c;头删时间复杂度为O(n)&#xff0c;realloc增容有消耗等。而在链表中&#xff0c;这些问题将得到解决。所以在这一篇文章里&#xff0c;我们将会讲解链表的定义与性质&#xff0c;以及…