前言
上文ShardingSphere-JDBC分片路由引擎中介绍了分片流程中的路由引擎,最终获取了路由结果;本文要介绍的改写引擎需要使用路由结果来对SQL进行改写,改写成可以被正确的分库分表能够执行的SQL;这里面涉及对各种SQL改写的情况众多,接下来本文会进行一一分析。
改写装饰器
重写引擎同样使用了装饰器模式,提供了接口类SQLRewriteContextDecorator
,实现类包括:
- ShardingSQLRewriteContextDecorator:分片SQL改写装饰器;
- ShadowSQLRewriteContextDecorator:影子库SQL改写装饰器;
- EncryptSQLRewriteContextDecorator:数据加密SQL改写装饰器;
默认加载ShardingSQLRewriteContextDecorator
和EncryptSQLRewriteContextDecorator
,使用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())