前言

本章分析ShardingJDBC的核心步骤:重写。
一、SQL重写入口
回到BasePrepareEngine#prepare,经过路由处理后最终得到RouteContext,进入executeRewrite重写流程。
public ExecutionContext prepare(final String sql, final List parameters) {
List clonedParameters = cloneParameters(parameters);// 解析 & 路由
RouteContext routeContext = executeRoute(sql, clonedParameters);
ExecutionContext result = new ExecutionContext(routeContext.getSqlStatementContext());// 重写
Collection executionUnits = executeRewrite(sql, clonedParameters, routeContext);
result.getExecutionUnits().addAll(executionUnits);// 打印SQLif (properties.getValue(ConfigurationPropertyKey.SQL_SHOW)) {
SQLLogger.logSQL(sql, properties.getValue(ConfigurationPropertyKey.SQL_SIMPLE), result.getSqlStatementContext(), result.getExecutionUnits());
}return result;
}
BasePrepareEngine#executeRewrite
重写流程分为三步:
1. 注册SQLRewriteContextDecorator到SQLRewriteEntry。
2. SQLRewriteEntry创建SQLRewriteContext,重写参数列表,创建SQLToken。
3. 执行重写引擎SQLRouteRewriteEngine,重写sql,拼装参数列表。
private Collection executeRewrite(final String sql, final List parameters, final RouteContext routeContext) {
// 注册 BaseRule(ShardingRule)和对应SQL重写处理类 SQLRewriteContextDecorator 到SQLRewriteEntry(rewriter)
registerRewriteDecorator();
// 创建SQLRewriteContext,重写参数列表,创建SQLToken
SQLRewriteContext sqlRewriteContext = rewriter.createSQLRewriteContext(sql, parameters, routeContext.getSqlStatementContext(), routeContext);
if (routeContext.getRouteResult().getRouteUnits().isEmpty()) {
// 路由结果是空 比如走了ShardingIgnoreRoutingEngine(use xxx)
return rewrite(sqlRewriteContext);
} else {
// SQLRouteRewriteEngine 重写引擎执行(通常走这里)
return rewrite(routeContext, sqlRewriteContext);
}
}

二、注册装饰器
BasePrepareEngine#executeRewrite的第一步,就是将SQLRewriteContextDecorator注册到SQLRewriteEntry。这里一步类似于路由流程中BasePrepareEngine#registerRouteDecorator注册RouteDecorator到DataNodeRouter。
private void registerRewriteDecorator() {
for (Class extends SQLRewriteContextDecorator> each : OrderedRegistry.getRegisteredClasses(SQLRewriteContextDecorator.class)) {
SQLRewriteContextDecorator rewriteContextDecorator = each.newInstance();
Class> ruleClass = (Class>) rewriteContextDecorator.getType();
rules.stream().filter(rule -> rule.getClass() == ruleClass || rule.getClass().getSuperclass() == ruleClass).collect(Collectors.toList())
// 放入SQLRewriteEntry的Map
.forEach(rule -> rewriter.registerDecorator(rule, rewriteContextDecorator));
}
}
三、SQLRewriteEntry
SQLRewriteEntry负责创建SQLRewriteContextsql重写上下文,重写参数列表,创建SQLToken。
public final class SQLRewriteEntry {
// 表的元数据信息
private final SchemaMetaData schemaMetaData;
// 配置
private final ConfigurationProperties properties;
// BaseRule - SQLRewriteContextDecorator
private final Map decorators = new LinkedHashMap<>();
}
暴露两个公共方法:
- registerDecorator方法:注册SQLRewriteContextDecorator,这个在BasePrepareEngine#executeRewrite的第一步执行了。
private final Map decorators = new LinkedHashMap<>();public void registerDecorator(final BaseRule rule, final SQLRewriteContextDecorator decorator) {
decorators.put(rule, decorator);
}
- createSQLRewriteContext方法:创建SQLRewriteContext并执行所有SQLRewriteContextDecorator,创建SQLToken,这是BasePrepareEngine#executeRewrite的第二步。
public SQLRewriteContext createSQLRewriteContext(final String sql, final List parameters, final SQLStatementContext sqlStatementContext, final RouteContext routeContext) {
SQLRewriteContext result = new SQLRewriteContext(schemaMetaData, sqlStatementContext, sql, parameters);
// 执行所有SQLRewriteContextDecorator,其中重写参数列表
decorate(decorators, result, routeContext);
// 创建SQLToken
result.generateSQLTokens();
return result;
}
private void decorate(final Map decorators, final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {
for (Entry entry : decorators.entrySet()) {
BaseRule rule = entry.getKey();
SQLRewriteContextDecorator decorator = entry.getValue();if (decorator instanceof RouteContextAware) {
((RouteContextAware) decorator).setRouteContext(routeContext);
}
decorator.decorate(rule, properties, sqlRewriteContext);
}
}
SQLRewriteContextDecorator
SQLRewriteContextDecorator,一般情况下要做两个事情:
- 参数重写,执行ParameterRewriter集合,将重写相关信息保存到SQLRewriteContext#parameterBuilder中
- 创建SQLTokenGenerator集合,保存到SQLRewriteContext#sqlTokenGenerators中
SQLRewriteContextDecorator有三个实现:
- EncryptSQLRewriteContextDecorator负责数据脱敏。
- ShadowSQLRewriteContextDecorator负责影子数据库。
- ShardingSQLRewriteContextDecorator负责标准的SQLRewriteContext装饰。
这里重点看ShardingSQLRewriteContextDecorator
的decorate
方法。
public final class ShardingSQLRewriteContextDecorator implements SQLRewriteContextDecorator<ShardingRule>, RouteContextAware {
private RouteContext routeContext;
@Override
public void decorate(final ShardingRule shardingRule, final ConfigurationProperties properties, final SQLRewriteContext sqlRewriteContext) {
// 1. 通过ShardingParameterRewriterBuilder构造ParameterRewriter集合
ShardingParameterRewriterBuilder rewriterBuilder = new ShardingParameterRewriterBuilder(shardingRule, routeContext);
Collection parameterRewriters = rewriterBuilder.getParameterRewriters(sqlRewriteContext.getSchemaMetaData());// 2. 执行所有ParameterRewriterfor (ParameterRewriter each : parameterRewriters) {if (!sqlRewriteContext.getParameters().isEmpty() && each.isNeedRewrite(sqlRewriteContext.getSqlStatementContext())) {
each.rewrite(sqlRewriteContext.getParameterBuilder(), sqlRewriteContext.getSqlStatementContext(), sqlRewriteContext.getParameters());
}
}// 3. 创建SQLTokenGenerators加入SQLRewriteContext
ShardingTokenGenerateBuilder tokenGenerateBuilder = new ShardingTokenGenerateBuilder(shardingRule, routeContext);
sqlRewriteContext.addSQLTokenGenerators(tokenGenerateBuilder.getSQLTokenGenerators());
}
}
参数重写
首先看一下ShardingParameterRewriterBuilder获取了哪些ParameterRewriter。
public final class ShardingParameterRewriterBuilder implements ParameterRewriterBuilder {
private final ShardingRule shardingRule;
private final RouteContext routeContext;
@Override
public Collection getParameterRewriters(final SchemaMetaData schemaMetaData) {
// 获取所有ParameterRewriter
Collection result = getParameterRewriters();for (ParameterRewriter each : result) {// 执行Aware的setter方法,依赖注入
setUpParameterRewriters(each, schemaMetaData);
}return result;
}private static Collection getParameterRewriters() {
Collection result = new LinkedList<>();
result.add(new ShardingGeneratedKeyInsertValueParameterRewriter());
result.add(new ShardingPaginationParameterRewriter());return result;
}private void setUpParameterRewriters(final ParameterRewriter parameterRewriter, final SchemaMetaData schemaMetaData) {if (parameterRewriter instanceof SchemaMetaDataAware) {
((SchemaMetaDataAware) parameterRewriter).setSchemaMetaData(schemaMetaData);
}if (parameterRewriter instanceof ShardingRuleAware) {
((ShardingRuleAware) parameterRewriter).setShardingRule(shardingRule);
}if (parameterRewriter instanceof RouteContextAware) {
((RouteContextAware) parameterRewriter).setRouteContext(routeContext);
}
}
}
主键参数重写
ShardingGeneratedKeyInsertValueParameterRewriter负责主键参数重写。isNeedRewrite方法判断如果sql上下文中的生成主键上下文中存在生成主键才执行重写。rewrite方法将生成主键放入StandardParameterBuilder的addedIndexAndParameters成员变量中。其实所有ParameterRewriter都是借助ParameterBuilder重新构造了参数列表。
@Setter
public final class ShardingGeneratedKeyInsertValueParameterRewriter implements ParameterRewriter<InsertStatementContext> {
// 只有insert语句,并且生成主键上下文中存在生成主键才执行重写
@Override
public boolean isNeedRewrite(final SQLStatementContext sqlStatementContext) {
return sqlStatementContext instanceof InsertStatementContext
&& ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().isPresent()
&& ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().get().isGenerated();
}
@Override
public void rewrite(final ParameterBuilder parameterBuilder, final InsertStatementContext insertStatementContext, final List parameters) {
Preconditions.checkState(insertStatementContext.getGeneratedKeyContext().isPresent());
((GroupedParameterBuilder) parameterBuilder).setDerivedColumnName(insertStatementContext.getGeneratedKeyContext().get().getColumnName());
Iterator> generatedValues = insertStatementContext.getGeneratedKeyContext().get().getGeneratedValues().descendingIterator();int count = 0;int parametersCount = 0;// 针对每个分组 分别在params中插入主键(考虑批量插入的场景)for (List each : insertStatementContext.getGroupedParameters()) {
parametersCount += insertStatementContext.getInsertValueContexts().get(count).getParametersCount();
Comparable> generatedValue = generatedValues.next();if (!each.isEmpty()) {
StandardParameterBuilder standardParameterBuilder = ((GroupedParameterBuilder) parameterBuilder).getParameterBuilders().get(count);// 参数插入StandardParameterBuilder的Map> addedIndexAndParameters
standardParameterBuilder.addAddedParameters(parametersCount, Lists.newArrayList(generatedValue));
}
count++;
}
}
}
分页参数重写
ShardingPaginationParameterRewriter负责分页参数重写。isNeedRewrite判断只有RouteUnit超过一个的分页查询的分页查询才需要重写。rewrite方法重写offset和rowCount,最终也会放入ParameterBuilder中。重点在于PaginationContext#getRevisedOffset方法和PaginationContext#getRevisedRowCount方法。
@Setter
public final class ShardingPaginationParameterRewriter implements ParameterRewriter<SelectStatementContext>, RouteContextAware {
private RouteContext routeContext;
@Override
public boolean isNeedRewrite(final SQLStatementContext sqlStatementContext) {
return sqlStatementContext instanceof SelectStatementContext
&& ((SelectStatementContext) sqlStatementContext).getPaginationContext().isHasPagination() && !routeContext.getRouteResult().isSingleRouting();
}
@Override
public void rewrite(final ParameterBuilder parameterBuilder, final SelectStatementContext selectStatementContext, final List parameters) {
PaginationContext pagination = selectStatementContext.getPaginationContext();
// 重写offset
pagination.getOffsetParameterIndex().ifPresent(offsetParameterIndex -> rewriteOffset(pagination, offsetParameterIndex, (StandardParameterBuilder) parameterBuilder));
// 重写rowCount
pagination.getRowCountParameterIndex().ifPresent(
rowCountParameterIndex -> rewriteRowCount(pagination, rowCountParameterIndex, (StandardParameterBuilder) parameterBuilder, selectStatementContext));
}
private void rewriteOffset(final PaginationContext pagination, final int offsetParameterIndex, final StandardParameterBuilder parameterBuilder) {
parameterBuilder.addReplacedParameters(offsetParameterIndex, pagination.getRevisedOffset());
}
private void rewriteRowCount(final PaginationContext pagination, final int rowCountParameterIndex, final StandardParameterBuilder parameterBuilder, final SQLStatementContext sqlStatementContext) {
parameterBuilder.addReplacedParameters(rowCountParameterIndex, pagination.getRevisedRowCount((SelectStatementContext) sqlStatementContext));
}
}
PaginationContext#getRevisedOffset方法返回值固定为0,也就是说RouteUnit超过一个的分页查询参数的offset一定会被重写为0。
public long getRevisedOffset() {
return 0L;
}
PaginationContext#getRevisedRowCount方法。isMaxRowCount方法判断满足某种条件时,rowCount会被优先重写为Integer.MAX_VALUE,接着如果是limit x,y语句,返回x+y,其他都会返回原始的rowCount。
public long getRevisedRowCount(final SelectStatementContext shardingStatement) {
if (isMaxRowCount(shardingStatement)) {
return Integer.MAX_VALUE;
}
return rowCountSegment instanceof LimitValueSegment ? actualOffset + actualRowCount : actualRowCount;
}
private boolean isMaxRowCount(final SelectStatementContext shardingStatement) {
return (!shardingStatement.getGroupByContext().getItems().isEmpty() || !shardingStatement.getProjectionsContext().getAggregationProjections().isEmpty())
&& !shardingStatement.isSameGroupByAndOrderByItems();
}
isMaxRowCount的含义是,如果存在分组(group by)或聚合函数(sum、count),且group by与order by项不一致时,需要设置rowCount为Integer.MAX_VALUE。那么重点就是isSameGroupByAndOrderByItems方法。
public boolean isSameGroupByAndOrderByItems() {
return !groupByContext.getItems().isEmpty() && groupByContext.getItems().equals(orderByContext.getItems());
}
isSameGroupByAndOrderByItems方法,当group by项与order by项一致时,返回true,重点看OrderByItem的equals方法(GroupByContext和OrderByContext的items属性都是OrderByItem集合)。
@Getter
@Setter
@EqualsAndHashCode
public final class OrderByItem {
private final OrderByItemSegment segment;
private int index;
public OrderByItem(OrderByItemSegment segment) {
this.segment = segment;
}
@Override
public boolean equals(final Object obj) {
if (null == obj || !(obj instanceof OrderByItem)) {
return false;
}
OrderByItem orderByItem = (OrderByItem) obj;
// 升降序相同 且 index相同
return segment.getOrderDirection() == orderByItem.getSegment().getOrderDirection() && index == orderByItem.getIndex();
}
}
OrderByItem的equals方法表名当OrderDirection升降序相同且index相同时,返回true。
案例1:select * from t_order group by user_id order by user_id desc limit ?, ?
。
?,?会被重写为0, 2147483647。因为没有where条件,导致shardingConditions为空,导致ShardingStandardRoutingEngine会选择所有可用的数据源。RouteUnit超过一个,所以offset一定是0。其次由于group by按照user_id升序,order by按照user_id降序,OrderByItem的equals方法返回false,导致满足isMaxRowCount,所以rowCount为2147483647。(group by默认会先分组后升序排序,常见优化 group by xxx order by null)。
案例2:select * from t_order limit ?, ?
。
?,?会被重写为0,?+?。
案例3:select count(*) from t_order where user_id = 2 and order_id = 7 group by user_id order by user_id desc limit ?, ?
。
如果user_id = 2 and order_id = 7路由到单数据源单表,这里ShardingPaginationParameterRewriter#isNeedRewrite返回false,不会进入分页参数重写逻辑。
创建SQLTokenGenerator集合
回到ShardingSQLRewriteContextDecorator
的decorate方法,最后一个逻辑是创建SQLTokenGenerator集合加入SQLRewriteContext。
public final class ShardingSQLRewriteContextDecorator implements SQLRewriteContextDecorator<ShardingRule>, RouteContextAware {
private RouteContext routeContext;
@Override
public void decorate(final ShardingRule shardingRule, final ConfigurationProperties properties, final SQLRewriteContext sqlRewriteContext) {
// ...
// 3. 创建SQLTokenGenerators加入SQLRewriteContext
ShardingTokenGenerateBuilder tokenGenerateBuilder = new ShardingTokenGenerateBuilder(shardingRule, routeContext);
sqlRewriteContext.addSQLTokenGenerators(tokenGenerateBuilder.getSQLTokenGenerators());
}
}
看一下ShardingTokenGenerateBuilder的getSQLTokenGenerators方法。
public final class ShardingTokenGenerateBuilder implements SQLTokenGeneratorBuilder {
private final ShardingRule shardingRule;
private final RouteContext routeContext;
@Override
public Collection getSQLTokenGenerators() {
Collection result = buildSQLTokenGenerators();for (SQLTokenGenerator each : result) {if (each instanceof ShardingRuleAware) {
((ShardingRuleAware) each).setShardingRule(shardingRule);
}if (each instanceof RouteContextAware) {
((RouteContextAware) each).setRouteContext(routeContext);
}
}return result;
}
private Collection buildSQLTokenGenerators() {
Collection result = new LinkedList<>();
addSQLTokenGenerator(result, new TableTokenGenerator());
addSQLTokenGenerator(result, new DistinctProjectionPrefixTokenGenerator());
addSQLTokenGenerator(result, new ProjectionsTokenGenerator());
addSQLTokenGenerator(result, new OrderByTokenGenerator());
addSQLTokenGenerator(result, new AggregationDistinctTokenGenerator());
addSQLTokenGenerator(result, new IndexTokenGenerator());
addSQLTokenGenerator(result, new OffsetTokenGenerator());
addSQLTokenGenerator(result, new RowCountTokenGenerator());
addSQLTokenGenerator(result, new GeneratedKeyInsertColumnTokenGenerator());
addSQLTokenGenerator(result, new GeneratedKeyForUseDefaultInsertColumnsTokenGenerator());
addSQLTokenGenerator(result, new GeneratedKeyAssignmentTokenGenerator());
addSQLTokenGenerator(result, new ShardingInsertValuesTokenGenerator());
addSQLTokenGenerator(result, new GeneratedKeyInsertValuesTokenGenerator());return result;
}
private void addSQLTokenGenerator(final Collection sqlTokenGenerators, final SQLTokenGenerator toBeAddedSQLTokenGenerator) {if (toBeAddedSQLTokenGenerator instanceof IgnoreForSingleRoute && routeContext.getRouteResult().isSingleRouting()) {return;
}
sqlTokenGenerators.add(toBeAddedSQLTokenGenerator);
}
}
其实ShardingTokenGenerateBuilder就是组装了一堆SQLTokenGenerator集合,并执行Aware方法注入属性。最后将这些SQLTokenGenerator放入了sql重写上下文,在SQLRewriteEntry#createSQLRewriteContext的下一步会执行这些SQLTokenGenerator,创建SQLToken。至此SQLRewriteContextDecorator的工作就结束了。
生成SQLToken
回到SQLRewriteEntry#createSQLRewriteContext方法,最后一步是执行SQLRewriteContext#generateSQLTokens方法,生成SQLToken。
private final SQLTokenGenerators sqlTokenGenerators = new SQLTokenGenerators();
public void generateSQLTokens() {
List sqlTokens = sqlTokenGenerators.generateSQLTokens(sqlStatementContext, parameters, schemaMetaData);this.sqlTokens.addAll(sqlTokens);
}
SQLTokenGenerators
执行的正是SQLRewriteContextDecorator
放入sql重写上下文中的每一个SQLTokenGenerator
。
public final class SQLTokenGenerators {
private final Collection sqlTokenGenerators = new LinkedList<>();
public List generateSQLTokens(final SQLStatementContext sqlStatementContext, final List parameters, final SchemaMetaData schemaMetaData) {
List result = new LinkedList<>();for (SQLTokenGenerator each : sqlTokenGenerators) {
// 执行Aware方法
setUpSQLTokenGenerator(each, parameters, schemaMetaData, result);
// 生成器判断是否需要针对这个sql生成SQLTokenif (!each.isGenerateSQLToken(sqlStatementContext)) {continue;
}
// 可选Token生成器,只要结果集中有了这个SQLToken就不需要加入结果集if (each instanceof OptionalSQLTokenGenerator) {
SQLToken sqlToken = ((OptionalSQLTokenGenerator) each).generateSQLToken(sqlStatementContext);if (!result.contains(sqlToken)) {
result.add(sqlToken);
}
}
// 集合Token生成器,生成批量的SQLTokenelse if (each instanceof CollectionSQLTokenGenerator) {
result.addAll(((CollectionSQLTokenGenerator) each).generateSQLTokens(sqlStatementContext));
}
}return result;
}
}
SQLToken是什么?
@RequiredArgsConstructor
@Getter
public abstract class SQLToken implements Comparable<SQLToken> {
private final int startIndex;
@Override
public final int compareTo(final SQLToken sqlToken) {
return startIndex - sqlToken.getStartIndex();
}
}
SQLToken只封装了一个startIndex属性,并用startIndex实现了Comparable接口。这个startIndex代表一个SQL单词的起始下标。SQLToken就是sql字符串中的需要重写的单词抽象。
比如要将逻辑表重写为实际表,一定要知道逻辑表在sql中的位置,比如开始下标,结束下标,这样才好替换。比如对于select * from t_order
,t_order
是表名,作为SQLToken的实现是TableToken
,它的startIndex是14。具体逻辑见TableTokenGenerator。
比如分组聚合场景,从不同数据源不同表中执行avg平均值计算,需要对结果集做归并操作,那么必须要得到每个sql的sum和count,最终avg = 总sum / 总count,这就必须要添加两个查询字段(sum、count)对应的就是一个SQLToken(ProjectionsToken)。具体逻辑见ProjectionsTokenGenerator。
四、SQLRouteRewriteEngine
BasePrepareEngine#rewrite是重写流程第三步,执行重写引擎,重写sql,拼装参数列表。rewrite方法主要是执行SQLRouteRewriteEngine#rewrite方法,后续就是组装ExecutionUnit。
private Collection rewrite(final RouteContext routeContext, final SQLRewriteContext sqlRewriteContext) {
Collection result = new LinkedHashSet<>();
SQLRouteRewriteEngine rewriteEngine = new SQLRouteRewriteEngine();// SQLRouteRewriteEngine重写sql
Map rewrite = rewriteEngine.rewrite(sqlRewriteContext, routeContext.getRouteResult());for (Entry entry : rewrite.entrySet()) {// SQLRewriteResult -> SQLUnit
SQLUnit sqlUnit = new SQLUnit(entry.getValue().getSql(), entry.getValue().getParameters());// DataSourceName + sqlUnit -> ExecutionUnit
ExecutionUnit executionUnit = new ExecutionUnit(entry.getKey().getDataSourceMapper().getActualName(), sqlUnit);
result.add(executionUnit);
}return result;
}
先来看看SQLRewriteResult是什么。
@RequiredArgsConstructor
@Getter
public final class SQLRewriteResult {
private final String sql;
private final List parameters;
}
SQLRewriteResult是sql重写产物,sql属性就是重写之后带占位符的sql语句,parameters属性就是参数列表。有了SQLRewriteResult,就可以真正执行sql了,只不过sharding-jdbc代码结构分层很清晰,要组装到ExecutionUnit中进入sql执行引擎。SQLRewriteResult两个属性正好与SQLUnit相同,BasePrepareEngine#rewrite后来组装ExecutionUnit也就很简单。
SQLRouteRewriteEngine
下面SQLRouteRewriteEngine的rewrite方法可以看出,sql重写是针对每个RouteUnit进行的。一个RouteUnit对应一个dataSource和n个table,对应的就是一个sql。
public final class SQLRouteRewriteEngine {
public Map rewrite(final SQLRewriteContext sqlRewriteContext, final RouteResult routeResult) {
Map result = new LinkedHashMap<>(routeResult.getRouteUnits().size(), 1);for (RouteUnit each : routeResult.getRouteUnits()) {// 重写sql
String sql = new RouteSQLBuilder(sqlRewriteContext, each).toSQL();// 组装新的params列表
List parameters = getParameters(sqlRewriteContext.getParameterBuilder(), routeResult, each);
result.put(each, new SQLRewriteResult(sql, parameters));
}return result;
}
}
重写SQL
首先通过RouteSQLBuilder父类AbstractSQLBuilder的toSQL方法,重写sql,对于普通的sql来说就是替换了逻辑表名为实际表名。对于select * from t_order where user_id = 1,拼接的顺序如下方代码所示。
public abstract class AbstractSQLBuilder implements SQLBuilder {
private final SQLRewriteContext context;
@Override
public final String toSQL() {
// 如果上下文中,没有需要重写的token,直接返回原始sql
if (context.getSqlTokens().isEmpty()) {
return context.getSql();
}
Collections.sort(context.getSqlTokens());
StringBuilder result = new StringBuilder();
// 1. select * from
result.append(context.getSql().substring(0, context.getSqlTokens().get(0).getStartIndex()));
for (SQLToken each : context.getSqlTokens()) {
// 2. select * from t_order_0
// 拼接生成的SQLToken
result.append(getSQLTokenText(each));
// 3. select * from t_order_0 where user_id = 1
// 拼接原来sql不会被替换的连接词
result.append(getConjunctionText(each));
}
return result.toString();
}
}
getSQLTokenText
方法由子类实现,RouteSQLBuilder
的实现方式是调用SQLToken的toString方法。
public final class RouteSQLBuilder extends AbstractSQLBuilder {
private final RouteUnit routeUnit;
public RouteSQLBuilder(final SQLRewriteContext context, final RouteUnit routeUnit) {
super(context);
this.routeUnit = routeUnit;
}
@Override
protected String getSQLTokenText(final SQLToken sqlToken) {
if (sqlToken instanceof RouteUnitAware) {
return ((RouteUnitAware) sqlToken).toString(routeUnit);
}
return sqlToken.toString();
}
}
组装参数列表
SQLRouteRewriteEngine#getParameters方法调用ParameterBuilder的getParameters方法,将之前暂存在ParameterBuilder中的重写参数组装返回。对于普通的StandardParameterBuilder,直接从中取出重写后的params参数列表;对于复杂的GroupedParameterBuilder,需要循环实际DataNode,拼装参数列表。
public final class SQLRouteRewriteEngine {
private List getParameters(final ParameterBuilder parameterBuilder, final RouteResult routeResult, final RouteUnit routeUnit) {
// 对于普通的StandardParameterBuilder,直接从它的成员变量中获取参数列表
if (parameterBuilder instanceof StandardParameterBuilder
|| routeResult.getOriginalDataNodes().isEmpty()
|| parameterBuilder.getParameters().isEmpty()) {
// 从builder中取出之前重写的params
return parameterBuilder.getParameters();
}
// 对于复杂的GroupedParameterBuilder,需要循环实际DataNode,拼装参数列表
List result = new LinkedList<>();int count = 0;for (Collection each : routeResult.getOriginalDataNodes()) {// 找到与实际DataNode匹配的RouteUnit路由结果if (isInSameDataNode(each, routeUnit)) {// 从GroupedParameterBuilder获取对应下标的参数加入结果集
result.addAll(((GroupedParameterBuilder) parameterBuilder).getParameters(count));
}
count++;
}return result;
}
}
StandardParameterBuilder会将之前暂存的重写参数信息,通过getParameters方法组装返回。
public final class StandardParameterBuilder implements ParameterBuilder {
// 原始参数列表
private final List originalParameters;// index - 需要添加的参数private final Map> addedIndexAndParameters = new TreeMap<>();// index - 需要替换的参数private final Map replacedIndexAndParameters = new LinkedHashMap<>();// 需要移除的indexprivate final List removeIndexAndParameters = new ArrayList<>();@Overridepublic List getParameters() {
List result = new LinkedList<>(originalParameters);// 替换下标对应的值for (Entry entry : replacedIndexAndParameters.entrySet()) {
result.set(entry.getKey(), entry.getValue());
}// 新增下标对应的值for (Entry> entry : ((TreeMap>) addedIndexAndParameters).descendingMap().entrySet()) {if (entry.getKey() > result.size()) {
result.addAll(entry.getValue());
} else {
result.addAll(entry.getKey(), entry.getValue());
}
}// 移除下标对应的值for (int index : removeIndexAndParameters) {
result.remove(index);
}return result;
}
}
总结
- BasePrepareEngine#executeRewrite是SQL重写的主流程入口。
- SQLRewriteEntry#createSQLRewriteContext创建SQL重写上下文,执行所有SQLRewriteContextDecorator重写参数列表放入ParameterBuilder,创建SQLTokenGenerator集合并执行生成SQLToken。
- SQLRouteRewriteEngine#rewrite执行AbstractSQLBuilder#toSQL方法利用SQLToken拼接SQL,执行ParameterBuilder#getParameters方法拼接参数列表。