乐趣区

关于java:ShardingSphereJDBC分片改写引擎

前言

上文 ShardingSphere-JDBC 分片路由引擎中介绍了分片流程中的路由引擎,最终获取了路由后果;本文要介绍的改写引擎须要应用路由后果来对 SQL 进行改写,改写成能够被正确的分库分表可能执行的 SQL;这外面波及对各种 SQL 改写的状况泛滥,接下来本文会进行一一剖析。

改写装璜器

重写引擎同样应用了装璜器模式,提供了接口类SQLRewriteContextDecorator,实现类包含:

  • ShardingSQLRewriteContextDecorator:分片 SQL 改写装璜器;
  • ShadowSQLRewriteContextDecorator:影子库 SQL 改写装璜器;
  • EncryptSQLRewriteContextDecorator:数据加密 SQL 改写装璜器;

默认加载 ShardingSQLRewriteContextDecoratorEncryptSQLRewriteContextDecorator,应用 java.util.ServiceLoader 来加载重写装璜器,须要在 META-INF/services/ 中指定具体的实现类:

org.apache.shardingsphere.sharding.rewrite.context.ShardingSQLRewriteContextDecorator
org.apache.shardingsphere.encrypt.rewrite.context.EncryptSQLRewriteContextDecorator

装璜器能够叠加,所以提供了优先级性能OrderAware,同时每个装璜器都有对应的规定,大抵如下所示:

装璜器 -SQLRewriteContextDecorator 规定 -BaseRule 优先级 -Order
ShardingSQLRewriteContextDecorator ShardingRule 0
EncryptSQLRewriteContextDecorator EncryptRule 20
ShadowSQLRewriteContextDecorator ShadowRule 30

只有在配置了相干 BaseRule,对应的SQLRewriteContextDecorator 能力失效,最常见的是ShardingSQLRewriteContextDecorator,上面重点介绍此装璜器;

改写引擎

不同的 SQL 语句,须要解决的改写都不一样,改写引擎的整体构造划分如下图所示(来自官网):

执行改写引擎之前须要做一些筹备工作,整个改写流程大抵分为以下几步:

  • 依据不同的改写装璜器结构不同的 SQLTokenGenerator 列表;
  • 依据 SQLTokenGenerator 生成对应的SQLToken
  • 改写引擎依据 SQLToken 执行改写操作;

结构 SQLTokenGenerator

不同的装璜器须要结构不同的 SQLTokenGenerator 列表,以最常见的 ShardingSQLRewriteContextDecorator 为例,会筹备如下 13 种SQLTokenGenerator

    private Collection<SQLTokenGenerator> buildSQLTokenGenerators() {Collection<SQLTokenGenerator> 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;
    }

以上实现类的公共接口类为SQLTokenGenerator,提供了是否失效的公共办法:

public interface SQLTokenGenerator {boolean isGenerateSQLToken(SQLStatementContext sqlStatementContext);
}

各种不同的 SQLTokenGenerator 并不是每次都能失效的,须要依据不同 SQL 语句进行判断,SQL 语句在解析引擎中曾经被解析为 SQLStatementContext,这样能够通过SQLStatementContext 参数进行判断;

TableTokenGenerator

TableToken生成器,次要用于对表名的改写;

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return true;}

能够发现这里对是否生成 SQLToken 没有任何条件,间接返回 true;然而在生成 TableToken 的时候是会查看是否存在表信息以及是否配置相干表TableRule

DistinctProjectionPrefixTokenGenerator

DistinctProjectionPrefixToken生成器,次要对聚合函数和去重的解决:

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof SelectStatementContext && !((SelectStatementContext) sqlStatementContext).getProjectionsContext().getAggregationDistinctProjections().isEmpty();}

首先必须是 select 语句,同时蕴含:聚合函数和 Distinct 去重,比方上面的 SQL:

select sum(distinct user_id) from t_order where order_id = 101

改写之后的 SQL 如下所示:

Actual SQL: ds0 ::: select DISTINCT user_id AS AGGREGATION_DISTINCT_DERIVED_0 from t_order1 where order_id = 101
Actual SQL: ds1 ::: select DISTINCT user_id AS AGGREGATION_DISTINCT_DERIVED_0 from t_order1 where order_id = 101

ProjectionsTokenGenerator

ProjectionsToken生成器,聚合函数须要做派生解决,比方 AVG 函数

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof SelectStatementContext && !getDerivedProjectionTexts((SelectStatementContext) sqlStatementContext).isEmpty();}
    
    private Collection<String> getDerivedProjectionTexts(final SelectStatementContext selectStatementContext) {Collection<String> result = new LinkedList<>();
        for (Projection each : selectStatementContext.getProjectionsContext().getProjections()) {if (each instanceof AggregationProjection && !((AggregationProjection) each).getDerivedAggregationProjections().isEmpty()) {result.addAll(((AggregationProjection) each).getDerivedAggregationProjections().stream().map(this::getDerivedProjectionText).collect(Collectors.toList()));
            } else if (each instanceof DerivedProjection) {result.add(getDerivedProjectionText(each));
            }
        }
        return result;
    }

首先必须是 select 语句,其次能够是:

  • 聚合函数,同时须要派生新的函数,比方 avg 函数;
  • 派生关键字,比方 order by,group by 等;

比方 avg 函数应用:

select avg(user_id) from t_order where order_id = 101

改写的 SQL 如下所示:

Actual SQL: ds0 ::: select avg(user_id) , COUNT(user_id) AS AVG_DERIVED_COUNT_0 , SUM(user_id) AS AVG_DERIVED_SUM_0 from t_order1 where order_id = 101
Actual SQL: ds1 ::: select avg(user_id) , COUNT(user_id) AS AVG_DERIVED_COUNT_0 , SUM(user_id) AS AVG_DERIVED_SUM_0 from t_order1 where order_id = 101

比方 order by 应用:

select user_id from t_order order by order_id

改写的 SQL 如下所示:

Actual SQL: ds0 ::: select user_id , order_id AS ORDER_BY_DERIVED_0 from t_order0 order by order_id
Actual SQL: ds0 ::: select user_id , order_id AS ORDER_BY_DERIVED_0 from t_order1 order by order_id
Actual SQL: ds1 ::: select user_id , order_id AS ORDER_BY_DERIVED_0 from t_order0 order by order_id
Actual SQL: ds1 ::: select user_id , order_id AS ORDER_BY_DERIVED_0 from t_order1 order by order_id

须要 order by 中指定的排序字段在 select 前面没有,这时候会派生一个;

OrderByTokenGenerator

OrderByToken生成器,主动生成 order by,失效条件:

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof SelectStatementContext && ((SelectStatementContext) sqlStatementContext).getOrderByContext().isGenerated();
    }

首先必须是 select 语句,其次有主动生成的 order by 内容;

比方如下 SQL:

select distinct user_id from t_order

改写的 SQL 如下所示,改写主动增加了ORDER BY

Actual SQL: ds0 ::: select distinct user_id from t_order0 ORDER BY user_id ASC 
Actual SQL: ds0 ::: select distinct user_id from t_order1 ORDER BY user_id ASC 
Actual SQL: ds1 ::: select distinct user_id from t_order0 ORDER BY user_id ASC 
Actual SQL: ds1 ::: select distinct user_id from t_order1 ORDER BY user_id ASC 

AggregationDistinctTokenGenerator

AggregationDistinctToken生成器,相似DistinctProjectionPrefixTokenGenerator

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof SelectStatementContext;}

是否生成 SQLToken 没做查看,然而在生成 SQLToken 的时候会查看是否有聚合函数和 Distinct 去重;

IndexTokenGenerator

IndexToken生成器,次要用在应用索引的中央,对索引名称重命名,用在 sql server,PostgreSQL 中,Mysql 并没有应用;

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof IndexAvailable && !((IndexAvailable) sqlStatementContext).getIndexes().isEmpty();
    }

OffsetTokenGenerator

OffsetToken生成器,次要作用于分页,对应 limitoffset关键字

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {
        return sqlStatementContext instanceof SelectStatementContext
                && ((SelectStatementContext) sqlStatementContext).getPaginationContext().getOffsetSegment().isPresent()
                && ((SelectStatementContext) sqlStatementContext).getPaginationContext().getOffsetSegment().get() instanceof NumberLiteralPaginationValueSegment;}

如下通过 limit 实现分页查问:

SELECT * FROM t_order LIMIT 1,2

改写的 SQL 如下所示,分页参数改写成了 0,3

Actual SQL: ds0 ::: SELECT * FROM t_order0 LIMIT 0,3
Actual SQL: ds0 ::: SELECT * FROM t_order1 LIMIT 0,3
Actual SQL: ds1 ::: SELECT * FROM t_order0 LIMIT 0,3
Actual SQL: ds1 ::: SELECT * FROM t_order1 LIMIT 0,3

RowCountTokenGenerator

RowCountToken生成器,同样作用于分页,对应 limitcount关键字

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {
        return sqlStatementContext instanceof SelectStatementContext
                && ((SelectStatementContext) sqlStatementContext).getPaginationContext().getRowCountSegment().isPresent()
                && ((SelectStatementContext) sqlStatementContext).getPaginationContext().getRowCountSegment().get() instanceof NumberLiteralPaginationValueSegment;}

实例和下面的 OffsetTokenGenerator 统一;

GeneratedKeyForUseDefaultInsertColumnsTokenGenerator

UseDefaultInsertColumnsToken生成器,insert 的 sql 中并未蕴含表的列名称

    public final boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof InsertStatementContext && ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().isPresent()
                && ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().get().isGenerated() && isGenerateSQLToken(((InsertStatementContext) sqlStatementContext).getSqlStatement());
    }
    
    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {return insertStatement.useDefaultColumns();
    }

应用以上 TokenGenerator 须要多个条件包含:

  • 必须是 insert 语句;
  • 配置了KeyGeneratorConfiguration,如下所示:

    orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id"));
  • 主键是主动生成的也就是用户没有被动生成主键;
  • 应用默认的字段,insert 的 sql 中并未蕴含表的列名称;

上面看一条 insert sql 的实例:

insert into t_order values (1,1)

改写的 SQL 如下所示:

Actual SQL: ds1 ::: insert into t_order1(user_id, order_id, id) values (1, 1, 600986707608731648)

GeneratedKeyInsertColumnTokenGenerator

GeneratedKeyInsertColumnToken生成器,insert 的 sql 中蕴含表的列名称,其余根本同上

    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {Optional<InsertColumnsSegment> sqlSegment = insertStatement.getInsertColumns();
        return sqlSegment.isPresent() && !sqlSegment.get().getColumns().isEmpty();
    }

上面看一条 insert sql 的实例:

insert into t_order (user_id,order_id) values (1,1)

改写的 SQL 如下所示:

Actual SQL: ds1 ::: insert into t_order1 (user_id,order_id, id) values (1, 1, 600988204400640000)

GeneratedKeyAssignmentTokenGenerator

GeneratedKeyAssignmentToken生成器,次要用于 insert...set 操作,同上无需指定主键

    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {return insertStatement.getSetAssignment().isPresent();}

上面看一条 insert set 的实例:

insert into t_order set user_id = 111,order_id=111

改写的 SQL 如下所示:

Actual SQL: ds1 ::: insert into t_order1 set user_id = 111,order_id=111, id = 600999588391813120

ShardingInsertValuesTokenGenerator

InsertValuesToken生成器,有插入值做分片解决

    public boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {return sqlStatementContext instanceof InsertStatementContext && !(((InsertStatementContext) sqlStatementContext).getSqlStatement()).getValues().isEmpty();
    }

上面看一条 insert sql 实例:

insert into t_order values (95,1,1),(96,2,2)

改写的 SQL 如下所示:

Actual SQL: ds1 ::: insert into t_order1 values (95, 1, 1)
Actual SQL: ds0 ::: insert into t_order0 values (96, 2, 2)

GeneratedKeyInsertValuesTokenGenerator

InsertValuesToken生成器,只有有插入值,就会存在此生成器,前提不能指定主键,应用主动生成

    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {return !insertStatement.getValues().isEmpty();}

上面看一条 insert sql 实例:

insert into t_order values (1,1),(2,2)

改写的 SQL 如下所示:

Actual SQL: ds1 ::: insert into t_order1(user_id, order_id, id) values (1, 1, 601005570564030465)
Actual SQL: ds0 ::: insert into t_order0(user_id, order_id, id) values (2, 2, 601005570564030464)

生成 SQLToken

以上别离介绍了常见的几种 TokenGenerator,以及在哪种条件下能够生成SQLToken,本节重点看一下是如何生成SQLToken 的,别离提供了两个接口类:

  • CollectionSQLTokenGenerator:对应的 TokenGenerator 会生成 SQLToken 列表;实现类包含:AggregationDistinctTokenGeneratorTableTokenGeneratorIndexTokenGenerator
  • OptionalSQLTokenGenerator:对应的 TokenGenerator 会生成惟一的 SQLToken;实现类包含:DistinctProjectionPrefixTokenGeneratorProjectionsTokenGeneratorOrderByTokenGeneratorOffsetTokenGeneratorRowCountTokenGeneratorGeneratedKeyForUseDefaultInsertColumnsTokenGeneratorGeneratedKeyInsertColumnTokenGeneratorGeneratedKeyAssignmentTokenGeneratorShardingInsertValuesTokenGeneratorGeneratedKeyInsertValuesTokenGenerator 等;
public interface CollectionSQLTokenGenerator<T extends SQLStatementContext> extends SQLTokenGenerator {Collection<? extends SQLToken> generateSQLTokens(T sqlStatementContext);
}

public interface OptionalSQLTokenGenerator<T extends SQLStatementContext> extends SQLTokenGenerator {SQLToken generateSQLToken(T sqlStatementContext);
}

上面重点剖析一下每种生成器是如何生成对应的 SQLToken 的;

TableTokenGenerator

首先从解析引擎中获取的 SQLStatementContext 中获取所有表信息SimpleTableSegment,而后查看以后表是否配置了TableRule,如果都能满足那么会创立一个TableToken

public final class TableToken extends SQLToken implements Substitutable, RouteUnitAware {
    @Getter
    private final int stopIndex;// 完结地位,开始地位在父类 SQLToken 中
    private final IdentifierValue identifier; // 表标识符信息 
    private final SQLStatementContext sqlStatementContext;//SQLStatement 上下文
    private final ShardingRule shardingRule;// 定义的分片规定
}

如果 SQL 中存在多个表信息,这里会生成 TableToken 列表;因为须要做表名称替换解决,所以须要如上定义的相干参数,不便后续做重写解决;

DistinctProjectionPrefixTokenGenerator

去重解决改写后的 SQL 须要在指定地位增加 DISTINCT 关键字:

public final class DistinctProjectionPrefixToken extends SQLToken implements Attachable {public DistinctProjectionPrefixToken(final int startIndex) {super(startIndex);
    }
}

这里只须要提供一个开始地位即可,也就是开始插入 DISTINCT 关键字的地位;

ProjectionsTokenGenerator

聚合函数和派生关键字生成器,能够查看枚举类DerivedColumn

public enum DerivedColumn {AVG_COUNT_ALIAS("AVG_DERIVED_COUNT_"), 
    AVG_SUM_ALIAS("AVG_DERIVED_SUM_"), 
    ORDER_BY_ALIAS("ORDER_BY_DERIVED_"), 
    GROUP_BY_ALIAS("GROUP_BY_DERIVED_"),
    AGGREGATION_DISTINCT_DERIVED("AGGREGATION_DISTINCT_DERIVED_");
}

比方下面介绍的实例中 avg 会派生成:

  • COUNT(user_id) AS AVG_DERIVED_COUNT_0;
  • SUM(user_id) AS AVG_DERIVED_SUM_0;

最初所有派生保留到一个文本中,封装到 ProjectionsToken 中:

public final class ProjectionsToken extends SQLToken implements Attachable {
    private final Collection<String> projections;// 派生列表
    public ProjectionsToken(final int startIndex, final Collection<String> projections) {super(startIndex);
        this.projections = projections;
    }
}

OrderByTokenGenerator

重点是生成 OrderByContext 类,具体能够查看 OrderByContextEngine,其中会判断isDistinctRow;最终会把所有须要生成order by 的字段和排序形式保留到 OrderByToken 中:

public final class OrderByToken extends SQLToken implements Attachable {private final List<String> columnLabels = new LinkedList<>();  //order by 字段
    private final List<OrderDirection> orderDirections = new LinkedList<>();// 排序形式
    public OrderByToken(final int startIndex) {super(startIndex);
    }
}

AggregationDistinctTokenGenerator

条件和 DistinctProjectionPrefixTokenGenerator 统一,前者次要通过 DistinctProjectionPrefixToken 生成 DISTINCT 关键字;而此生成器是给相干字段增加派生的别名,如下面实例中的AGGREGATION_DISTINCT_DERIVED_0

select DISTINCT user_id AS AGGREGATION_DISTINCT_DERIVED_0 from t_order1 where order_id = 101
public final class AggregationDistinctToken extends SQLToken implements Substitutable {
    private final String columnName;// 字段名称
    private final String derivedAlias;// 别名
}

IndexTokenGenerator

如果 SQL 是一个 IndexAvailable,并且蕴含索引信息,则会生成一个IndexToken,其中信息和TableToken 统一;常见的 IndexAvailable 蕴含:AlterIndexStatementContextCreateIndexStatementContextCreateTableStatementContextDropIndexStatementContext

OffsetTokenGenerator

次要是对 limit 关键字中的 offset 值进行重置解决,解决的信息蕴含在 OffsetToken 中:

public final class OffsetToken extends SQLToken implements Substitutable {
    @Getter
    private final int stopIndex;
    private final long revisedOffset; // 订正过的 offset
}

RowCountTokenGenerator

次要是对 limit 关键字中的 count 值进行重置解决,解决的信息蕴含在 RowCountToken 中:

public final class RowCountToken extends SQLToken implements Substitutable {
    @Getter
    private final int stopIndex;
    private final long revisedRowCount; // 订正过的 rowcout
}

GeneratedKeyForUseDefaultInsertColumnsTokenGenerator

因为配置了 KeyGeneratorConfiguration,insert 语句中会主动生成组件,解析的时候会在InsertStatementContext 中生成 GeneratedKeyContext,外面蕴含了主键字段,以及主键值;此生成器是在 insert 中没有指定字段的状况,所有字段会被保留到UseDefaultInsertColumnsToken 中,字段列表是有序的,须要将生成的 id 挪动到列表的开端;

public final class UseDefaultInsertColumnsToken extends SQLToken implements Attachable {private final List<String> columns;// 字段列表:user_id,order_id,id}

GeneratedKeyInsertColumnTokenGenerator

此生成器是在 insert 中指定了字段的状况,须要改写的中央是增加主键字段名称即可,保留到 GeneratedKeyInsertColumnToken 中:

public final class GeneratedKeyInsertColumnToken extends SQLToken implements Attachable {private final String column;// 主键字段:id}

GeneratedKeyAssignmentTokenGenerator

此生成器是在 insert set 中应用,须要增加主键、值,然而因为能够由用户指定 parameter,所以这里会依据是否配置了 parameter 来生成不同的 Token:

  • LiteralGeneratedKeyAssignmentToken:没有 parameter 的状况,提供主键名称和主键值:

    public final class LiteralGeneratedKeyAssignmentToken extends GeneratedKeyAssignmentToken {
        private final Object value;// 主键值
        public LiteralGeneratedKeyAssignmentToken(final int startIndex, final String columnName, final Object value) {super(startIndex, columnName);// 开始地位和主键名称
            this.value = value;
        }
  • ParameterMarkerGeneratedKeyAssignmentToken:指定了 parameter,只须要提供主键名称即可,值从 parameter 中获取:

    public final class ParameterMarkerGeneratedKeyAssignmentToken extends GeneratedKeyAssignmentToken {public ParameterMarkerGeneratedKeyAssignmentToken(final int startIndex, final String columnName) {super(startIndex, columnName);// 开始地位和主键名称
        }
    }

ShardingInsertValuesTokenGenerator

插入的值能够是一条也能够是多条,每条数据都会和 DataNode 绑定,也就是属于哪个库属于哪个表,这里数据和 DataNode 的绑定被包装到了 ShardingInsertValue 中:

public final class ShardingInsertValue {
    private final Collection<DataNode> dataNodes;// 数据节点信息
    private final List<ExpressionSegment> values;// 数据信息
}

最初所有数据包装到 ShardingInsertValuesToken 中;

GeneratedKeyInsertValuesTokenGenerator

此生成器会对后面 ShardingInsertValuesTokenGenerator 生成的 ShardingInsertValue 进行再加工解决,次要针对没有指定主键的状况,对其中的 values 减少一个 ExpressionSegment 保留主键信息;

执行改写

通过以上两步曾经筹备好了所有SQLToken,上面就能够执行改写操作了,提供了改写引擎SQLRouteRewriteEngine,两个重要的参数别离是:

  • SQLRewriteContext:SQL 改写上下文,生成的 SQLToken 都存在上下文中;
  • RouteResult:路由引擎产生的后果;

有了以上两个外围参数就能够执行改写操作了:

    public Map<RouteUnit, SQLRewriteResult> rewrite(final SQLRewriteContext sqlRewriteContext, final RouteResult routeResult) {Map<RouteUnit, SQLRewriteResult> result = new LinkedHashMap<>(routeResult.getRouteUnits().size(), 1);
        for (RouteUnit each : routeResult.getRouteUnits()) {result.put(each, new SQLRewriteResult(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(), getParameters(sqlRewriteContext.getParameterBuilder(), routeResult, each)));
        }
        return result;
    }

遍历每个路由单元 RouteUnit,每个路由单元都对应一条 SQL 语句;依据路由单元和SQLToken 列表生成改写这条 SQL;能够发现这里执行了 RouteSQLBuilder 中的 toSQL 办法:

    public final String toSQL() {if (context.getSqlTokens().isEmpty()) {return context.getSql();
        }
        Collections.sort(context.getSqlTokens());
        StringBuilder result = new StringBuilder();
        result.append(context.getSql().substring(0, context.getSqlTokens().get(0).getStartIndex()));
        for (SQLToken each : context.getSqlTokens()) {result.append(getSQLTokenText(each));
            result.append(getConjunctionText(each));
        }
        return result.toString();}
    
    protected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {return ((RouteUnitAware) sqlToken).toString(routeUnit);
        }
        return sqlToken.toString();}
    
    private String getConjunctionText(final SQLToken sqlToken) {return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken));
    }

首先对 SQLToken 列表进行排序,排序依据 startIndex 从小到大排序;而后从截取从 0 地位到第一个 SQLToken 的开始地位,这一部分的 sql 是无需任何改变的;接下来就是遍历 SQLToken,会判断SQLToken 是否为 RouteUnitAware,如果是会做路由替换,比方物理表替换逻辑表;最初在截取两个SQLToken 的两头局部,遍历所有SQLToken,返回从新拼接的 SQL;

以一条查问 SQL 为例:

select user_id,order_id from t_order where order_id = 101

第一次截取从 0 地位到第一个 SQLToken 的开始地位,后果为:

select user_id,order_id from 

而后遍历SQLToken,以后只有一个TableToken,并且是一个RouteUnitAware,表格会被替换解决:

t_order->t_order1

截取最初残余的局部:

 where order_id = 101

而后把每个局部拼接到一起,组成能被数据库执行的 SQL:

select user_id,order_id from t_order1 where order_id = 101

上面简略介绍一下每种 SQLToken 是如何执行改写操作的;

TableToken

TableToken是一个RouteUnitAware,所以在重写的时候传入了路由单元RouteUnit,须要依据路由单元来决定逻辑表应该改写为哪个物理表;

DistinctProjectionPrefixToken

次要对聚合函数和去重的解决,这里只是把以后Token,改写成一个DISTINCT,这里其实只是做了局部解决,聚合函数并没有体现,所以整个流程中还有归并这流程,很多聚合函数都须要在归并流程中解决;

ProjectionsToken

聚合函数须要做派生解决,只须要把派生的字符,拼接起来即可,比方:AVG 函数派生的AVG_DERIVED_COUNTAVG_DERIVED_SUM,同样 AVG 聚合函数同样须要到归并流程中解决;

OrderByToken

对外面保留的 order by 字段和对应的排序形式,进行遍历解决,后果相似如下:

order by column1 desc,column2 desc...

AggregationDistinctToken

次要对聚合函数和去重的解决,DistinctProjectionPrefixToken拼接DISTINCT 关键字,此 Token 拼接别名;组合起来如下所示:

select DISTINCT user_id AS AGGREGATION_DISTINCT_DERIVED_0 

IndexToken

IndexToken是一个RouteUnitAware,依据路由单元改写索引名称;

OffsetToken

通过订正过的 offset,改写 limit 中的 offset;

RowCountToken

通过订正过的 RowCount,改写 limit 中的 count;

GeneratedKeyInsertColumnToken

这个改写只是针对字段名称,并不是值,值是须要做路由解决的,即在现有的字段列表中增加主键名称;

(user_id)->(user_id,id)

UseDefaultInsertColumnsToken

此种模式是在插入数据的时候没有指定任何字段,所有这里会生成所有字段名称,同时会增加主键名称

()->(user_id,id)

GeneratedKeyAssignmentToken

上大节中介绍了此 Token 蕴含两个子类,别离对应 insert set 有参数和无参数的状况;这里的改写是蕴含名称和值的

set user_id = 11,id = 1234567

InsertValuesToken

这里区别以上 GeneratedKeyInsertColumnTokenUseDefaultInsertColumnsToken对字段的解决,此 Token 是对值的解决,值必定要路由解决,所有此 Token 也是RouteUnitAware

总结

本文重点介绍了须要改写 SQL 的 13 种状况,其实就是找出所有须要被改写的 SQL,而后记录到不同的 Token 中,同时还会记录以后 Token 在原 SQL 中对应的 startIndex 和 stopIndex,这样能够做改写解决,替换到原理地位的 SQL;通过遍历整个 Token 列表最终达到改写 SQL 的所有部位;当然整个改写有些中央是扭转了原来 SQL 的含意比方聚合函数,所以 ShardingSphere-JDBC 还提供了专门的归并引擎,用来保障 SQL 的完整性。

参考

https://shardingsphere.apache…

感激关注

能够关注微信公众号「回滚吧代码」,第一工夫浏览,文章继续更新;专一 Java 源码、架构、算法和面试。

退出移动版