ShardingSphere源码解析之改写引擎(一)

在介绍路由引擎的最后一篇文章《ShardingSphere源码解析之路由引擎(七)》中,我们在BaseShardingEngine的shard方法中看到了ShardingSphere中另一个重要的概念,即SQL改写(Rewrite)。SQL改写在分库分表框架中通常位于路由之后,也是整个SQL执行流程中的重要的一个环节,因为开发人员是面向逻辑库与逻辑表书写的SQL,并不能够直接在真实的数据库中执行,SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。

让我们来看一下BaseShardingEngine中用于执行改写逻辑的rewriteAndConvert方法:

       private Collection<RouteUnit> rewriteAndConvert(final String sql, final List<Object> parameters, final SQLRouteResult sqlRouteResult) {

        //构建SQLRewriteContext

    SQLRewriteContext sqlRewriteContext = new SQLRewriteContext(metaData.getRelationMetas(), sqlRouteResult.getSqlStatementContext(), sql, parameters);

    //构建ShardingSQLRewriteContextDecoratorSQLRewriteContext进行装饰

    new ShardingSQLRewriteContextDecorator(shardingRule, sqlRouteResult).decorate(sqlRewriteContext);

    //判断是否是数据脱敏列

    boolean isQueryWithCipherColumn = shardingProperties.<Boolean>getValue(ShardingPropertiesConstant.QUERY_WITH_CIPHER_COLUMN);

        //构建EncryptSQLRewriteContextDecoratorSQLRewriteContext进行装饰

    new EncryptSQLRewriteContextDecorator(shardingRule.getEncryptRule(), isQueryWithCipherColumn).decorate(sqlRewriteContext);

      //生成SQLTokens

    sqlRewriteContext.generateSQLTokens();   

        Collection<RouteUnit> result = new LinkedHashSet<>();

        for (RoutingUnit each : sqlRouteResult.getRoutingResult().getRoutingUnits()) {

        //构建ShardingSQLRewriteEngine

            ShardingSQLRewriteEngine sqlRewriteEngine = new ShardingSQLRewriteEngine(shardingRule, sqlRouteResult.getShardingConditions(), each);

            //执行改写

            SQLRewriteResult sqlRewriteResult = sqlRewriteEngine.rewrite(sqlRewriteContext);

            //保存改写结果

            result.add(new RouteUnit(each.getDataSourceName(), new SQLUnit(sqlRewriteResult.getSql(), sqlRewriteResult.getParameters())));

        }

        return result;

    }

这段代码虽然内容不多,但确完整描述了实现SQL改写的整体流程。这里面涉及到的核心类也很多,值得我们花几篇文章对其进行详细展开。在此之前,我们还是给出相关核心类的整体结构,如下图所示:

针对上图,我们发现在改写引擎中,SQLRewriteContext是一个非常重要的类,SQLRewriteEngine、SQLRewriteContextDecorator等核心接口都与它有依赖关系。从命名上讲,SQLRewriteContext是一个上下文对象,可以想象肯定保存着与SQL改写相关的很多数据信息,让我们来看一下它的变量定义,如下所示:   

    private final RelationMetas relationMetas;   

    private final SQLStatementContext sqlStatementContext;   

    private final String sql;   

    private final List<Object> parameters;   

    private final List<SQLToken> sqlTokens = new LinkedList<>();   

    private final ParameterBuilder parameterBuilder;   

    @Getter(AccessLevel.NONE)

    private final SQLTokenGenerators sqlTokenGenerators = new SQLTokenGenerators();

在这里,我们看到了前面已经介绍过的SQLStatementContext,也看到了新的SQLToken和SQLTokenGenerators。随着内容的演进,这些对象都会逐一进行介绍。这里我们先明确SQLRewriteContext中保存着用于SQL改写的相关信息,而且这些信息的构建过程会根据不同的应用场景而有所不同。

我们先来看一下这里的SQLToken对象,该对象在改写引擎中重要性很高,SQLRewriteEngine正是基于SQLToken实现了SQL改写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实际上是一个抽象类,在ShardingSphere中,存在了一大批SQLToken的子类,其类层结构如下所示:

这些SQLToken多数跟SQL改写相关(包名中包含rewrite),而有些在改写的基础上还与后面要讲到的数据脱敏功能相关(包名中还包含着encrypt),数据脱敏也是ShardingSphere提供的一项非常实用的功能,我们后面会有专题进行介绍。同时,这里的部分SQLToken位于shardingsphere-rewrite-engine工程中,而有些则位于sharding-core-rewrite工程中,这点也需要注意。

结合SQL改写的常见场景,上图中的部分SQLToken的含义我们可以从字面意思上直接进行理解。

例如,对于INSERT语句而言,如果使用数据库自增主键,是无需写入主键字段的。但数据库的自增主键是无法满足分布式场景下的主键唯一性,因此ShardingSphere提供了分布式自增主键的生成策略,并且可以通过补列,让使用方无需改动现有代码,即可将分布式自增主键透明的替换数据库现有的自增主键。关于ShardingSphere中的分布式主键的介绍可以回顾《ShardingSphere源码解析之分布式主键》中的内容。举例说明,假设表t_order的主键是order_id,原始的SQL为:

INSERT INTO t_order (`field1`, `field2`) VALUES (10, 1);

可以看到,上述SQL中并未包含自增主键,需要数据库自行填充。ShardingSphere配置自增主键后,SQL将改写为:

INSERT INTO t_order (`field1`, `field2`, order_id) VALUES (10, 1, xxxxx);

改写后的SQL将在INSERT语句的最后部分增加主键列名称以及自动生成的自增主键值。上述SQL中的xxxxx表示自动生成的自增主键值。

从命名上看,GeneratedKeyInsertColumnToken对应上述的自动主键填充的场景,这实际上属于常见的一种SQL改写策略,即补列。GeneratedKeyInsertColumnToken的实现如下所示:

public final class GeneratedKeyInsertColumnToken extends SQLToken implements Attachable {   

    private final String column;   

    public GeneratedKeyInsertColumnToken(final int startIndex, final String column) {

        super(startIndex);

        this.column = column;

    }   

    @Override

    public String toString() {

        return String.format(", %s", column);

    }

}

可以看到这里多了一个column变量用于指定主键的所在列。我们再来跟踪GeneratedKeyInsertColumnToken的构造函数调用情况,发现在GeneratedKeyInsertColumnTokenGenerator中创建了GeneratedKeyInsertColumnToken。顾名思义,GeneratedKeyInsertColumnTokenGenerator是一种TokenGenerator,专门负责生成具体的Token。TokenGenerator接口定义如下:

public interface SQLTokenGenerator {   

    //判断是否要生成SQLToken

    boolean isGenerateSQLToken(SQLStatementContext sqlStatementContext);

}

该接口还有两个子接口,分别是负责生成单个SQLToken的OptionalSQLTokenGenerator和负责生成批量SQLToken的CollectionSQLTokenGenerator,如下所示:

public interface OptionalSQLTokenGenerator extends SQLTokenGenerator {   

    //生成单个SQLToken

    SQLToken generateSQLToken(SQLStatementContext sqlStatementContext);

}

public interface CollectionSQLTokenGenerator extends SQLTokenGenerator {

    //生成批量SQLToken

    Collection<? extends SQLToken> generateSQLTokens(SQLStatementContext sqlStatementContext);

}

在ShardingSphere,和SQLToken一样,TokenGenerator的类层结构也比较复杂,类层结构如下所示:

对于GeneratedKeyInsertColumnTokenGenerator而言,它还有一个抽象的基类,即上图中的BaseGeneratedKeyTokenGenerator,其实现如下所示:

public abstract class BaseGeneratedKeyTokenGenerator implements OptionalSQLTokenGenerator, SQLRouteResultAware {

    private SQLRouteResult sqlRouteResult;   

    @Override

    public final boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {

        return sqlStatementContext instanceof InsertSQLStatementContext && sqlRouteResult.getGeneratedKey().isPresent()

                && sqlRouteResult.getGeneratedKey().get().isGenerated() && isGenerateSQLToken((InsertStatement) sqlStatementContext.getSqlStatement());

    }   

    protected abstract boolean isGenerateSQLToken(InsertStatement insertStatement);   

    @Override

    public final SQLToken generateSQLToken(final SQLStatementContext sqlStatementContext) {

        Preconditions.checkState(sqlRouteResult.getGeneratedKey().isPresent());

        return generateSQLToken(sqlStatementContext, sqlRouteResult.getGeneratedKey().get());

    }   

    protected abstract SQLToken generateSQLToken(SQLStatementContext sqlStatementContext, GeneratedKey generatedKey);

}

这个抽象类留下了两个模板方法isGenerateSQLToken和generateSQLToken交由子类进行实现,在GeneratedKeyInsertColumnTokenGenerator中,这两个方法的实现如下所示:

public final class GeneratedKeyInsertColumnTokenGenerator extends BaseGeneratedKeyTokenGenerator {   

    @Override

    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {

        Optional<InsertColumnsSegment> sqlSegment = insertStatement.findSQLSegment(InsertColumnsSegment.class);

        return sqlSegment.isPresent() && !sqlSegment.get().getColumns().isEmpty();

    }   

    @Override

    protected GeneratedKeyInsertColumnToken generateSQLToken(final SQLStatementContext sqlStatementContext, final GeneratedKey generatedKey) {

        Optional<InsertColumnsSegment> sqlSegment = sqlStatementContext.getSqlStatement().findSQLSegment(InsertColumnsSegment.class);

        Preconditions.checkState(sqlSegment.isPresent());

        return new GeneratedKeyInsertColumnToken(sqlSegment.get().getStopIndex(), generatedKey.getColumnName());

    }

}

我们看到在上述generateSQLToken方法中,通过利用在SQL解析引擎中获取的InsertColumnsSegment以及从用于生成分布式主键的GeneratedKey中获取对应的主键列,我们就可以构建一个GeneratedKeyInsertColumnToken。

关于SQLToken的基本概念以及它的其中一个实现类GeneratedKeyInsertColumnToken就介绍到这里,关于其他SQLToken实现类我们不会全部展开,部分会在后面的内容中进行穿插介绍。

作为总结,从今天起,我们正式进入到ShardingSphere中关于改写引擎部分的讲解,本文先从整体结构上给出了改写引擎部分的核心类,然后重点分析了改写引擎的上下文对象SQLRewriteContext中与SQLToken相关的内容。

更多内容可以关注我的公众号:程序员向架构师转型。

发布了104 篇原创文章 · 获赞 10 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104709420
今日推荐