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对象,返回该对象;
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。