重写路由_ShardingJDBC源码阅读(六)重写

本文深入剖析了ShardingJDBC的SQL重写过程,包括SQL重写入口、注册装饰器、SQLRewriteEntry的运作以及SQLRouteRewriteEngine的重写和参数组装。详细讲解了参数重写如主键和分页参数的处理,以及SQLToken的生成和使用,揭示了ShardingJDBC如何实现动态SQL。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

e141a5de53c509cfb74b104b9efd959e.png

前言

6229d61212c74591c240e5800d672562.png

本章分析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);
  }
}
280b36fdb2c19c2aec6b260ab0aa89b9.png

二、注册装饰器

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<>();
}

暴露两个公共方法:

  1. registerDecorator方法:注册SQLRewriteContextDecorator,这个在BasePrepareEngine#executeRewrite的第一步执行了。
private final Map decorators = new LinkedHashMap<>();public void registerDecorator(final BaseRule rule, final SQLRewriteContextDecorator decorator) {
  decorators.put(rule, decorator);
}
  1. 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装饰。

这里重点看ShardingSQLRewriteContextDecoratordecorate方法。

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_ordert_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方法拼接参数列表。

4314a7733b8321b625f3074109d3484e.png

7783ce29525a919e77f837e32060847f.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值