sharding-jdbc 执行流程源码分析-sql 提取

上一篇分析了 sharding-jdbc sql 解析过程,从本篇分析 sql 提取过程,解析成功之后将语法树封装成为 SQLAST ,然后依靠 SQLAST 和提取规则的配置,进行 sql 提取。

1、首先看一下提取部分的初始化

当 ParseRuleRegistry 类被加载的时候

   static {
        ...略
        instance = new ParseRuleRegistry();
    }
    private ParseRuleRegistry() {
        initParseRuleDefinition();
    }

创建 ParseRuleRegistry 实例和调用 initParseRuleDefinition 方法,此方法的目的是为了加载默认的提取规则配置文件,和根据数据库类型加载对应目录的提取规则配置文件

1、加载 ETA-INF/parsing-rule-definition/extractor-rule-definition.xml 配置文件
2、获取SQLParserEntry 所有实现类循环所有的数据库类型,根据数据库类型加载对应的配置文件例如mysql
META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml

    /**
     * load sql 解析规范 例如sql-statement-rule-definition.xml
     */
    private void initParseRuleDefinition() {
        //利用JAXB加载META-INF/parsing-rule-definition/extractor-rule-definition.xml配置文件
        ExtractorRuleDefinitionEntity generalExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile());
        //利用JAXB加载下META-INF/parsing-rule-definition/filler-rule-definition.xml配置文件
        FillerRuleDefinitionEntity generalFillerRuleEntity = fillerRuleLoader.load(RuleDefinitionFileConstant.getFillerRuleDefinitionFile());
        //根据数据库类型加载对应的配置文件
        for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {
            String databaseTypeName = each.getDatabaseTypeName();
            //META-INF/parsing-rule-definition/sharding.mysql/filler-rule-definition.xml
            //databaseType:rules<segment,filler>
            fillerRuleDefinitions.put(databaseTypeName, createFillerRuleDefinition(generalFillerRuleEntity, databaseTypeName));
            //META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
            //META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
            //databaseType:rules<xxxContext,SQLStatementRule>
            sqlStatementRuleDefinitions.put(databaseTypeName, createSQLStatementRuleDefinition(generalExtractorRuleEntity, databaseTypeName));
        }
    }
1.1、根据数据库类型加载对应的配置文件例如mysql META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
private SQLStatementRuleDefinition createSQLStatementRuleDefinition(final ExtractorRuleDefinitionEntity generalExtractorRuleEntity, final String databaseTypeName) {
        //加载 META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
        ExtractorRuleDefinitionEntity databaseDialectExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile(databaseTypeName));
        ExtractorRuleDefinition extractorRuleDefinition = new ExtractorRuleDefinition(generalExtractorRuleEntity, databaseDialectExtractorRuleEntity);
        //加载 META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
        return new SQLStatementRuleDefinition(statementRuleLoader.load(RuleDefinitionFileConstant.getSQLStatementRuleDefinitionFile(databaseTypeName)), extractorRuleDefinition);
    }

    //ExtractorRuleDefinitionEntityLoader
    @Override
    @SneakyThrows
    public ExtractorRuleDefinitionEntity load(final String extractorRuleDefinitionFile) {
        InputStream inputStream = ExtractorRuleDefinitionEntityLoader.class.getClassLoader().getResourceAsStream(extractorRuleDefinitionFile);
        return null == inputStream
                ? new ExtractorRuleDefinitionEntity() : (ExtractorRuleDefinitionEntity) JAXBContext.newInstance(ExtractorRuleDefinitionEntity.class).createUnmarshaller().unmarshal(inputStream);
    }

@XmlRootElement(name = "extractor-rule-definition")
@Getter
public final class ExtractorRuleDefinitionEntity implements RuleDefinitionEntity {
    @XmlElement(name = "extractor-rule")
    private Collection<ExtractorRuleEntity> rules = new LinkedList<>();
}

这里只列举出一个 ExtractorRuleDefinitionEntityLoader,其他Loader 类都是根据 JAXBContext 加载解析 xml。
ExtractorRuleDefinitionEntityLoader 解析结果封装成ExtractorRuleDefinitionEntity ,然后创建 SQLStatementRuleDefinition

1、根据 extractor-rule-definition.xml 封装成 ExtractorRuleDefinitionEntity 包含 Collection<ExtractorRuleEntity> rules
根据 sql-statement-rule-definition.xml 封装成 SQLStatementRuleDefinitionEntity
2、根据 sql-statement-rule-definition.xml 配置的 extractor-rule-refs 对应到 ExtractorRuleEntity

    //SQLStatementRuleDefinition.java
    @SneakyThrows
    public SQLStatementRuleDefinition(final SQLStatementRuleDefinitionEntity entity, final ExtractorRuleDefinition extractorRuleDefinition) {
        for (SQLStatementRuleEntity each : entity.getRules()) {
            //获取 xml 配置 class名称 sql-statement-class 创建处理类 SQLStatementRule
            rules.put(getContextClassName(each.getContext()), new SQLStatementRule(each, extractorRuleDefinition));
        }
    }
    //SQLStatementRuleDefinition.java
    private String getContextClassName(final String context) {
        return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, context + "Context");
    }
    //SQLStatementRule.java
    public SQLStatementRule(final SQLStatementRuleEntity entity, final ExtractorRuleDefinition extractorRuleDefinition) {
        contextName = entity.getContext();
        //反射创建 sql-statement-class
        sqlStatementClass = (Class<? extends SQLStatement>) Class.forName(entity.getSqlStatementClass());
        extractors = getExtractors(entity.getExtractorRuleRefs(), extractorRuleDefinition);
    }
    //SQLStatementRule.java
    //通过 sql-statement-rule-definition.xml 中配置  extractor-rule-refs  获取 extractorRuleDefinition 对应 extractor-rule-definition.xml 
    private Collection<SQLSegmentExtractor> getExtractors(final String extractorRuleRefs, final ExtractorRuleDefinition extractorRuleDefinition) {
        if (null == extractorRuleRefs) {
            return Collections.emptyList();
        }
        Collection<SQLSegmentExtractor> result = new LinkedList<>();
        for (String each : Splitter.on(',').trimResults().splitToList(extractorRuleRefs)) {
            // sql 每段的提取的规则
            result.add(extractorRuleDefinition.getExtractor(each));
        }
        return result;
    }

初始化阶段类的关系图


image.png
2、sql 提取

入口是在 SQLParseKernel 类中

1、用 antlr4 执行解析生成语法树
2、根据语法树提取 sql 分段
3、 将sql 分段 SQLSegment 分类后封装成 SQLStatement

   public SQLStatement parse() {
       //获取数据解析工具,并且执行解析生成语法树
       SQLAST ast = parserEngine.parse();
       //根据语法树提取可以提取的部分
       Collection<SQLSegment> sqlSegments = extractorEngine.extract(ast);
       Map<ParserRuleContext, Integer> parameterMarkerIndexes = ast.getParameterMarkerIndexes();
       //将 SQLSegment 分类后封装成 SQLStatement
       return fillerEngine.fill(sqlSegments, parameterMarkerIndexes.size(), ast.getSqlStatementRule());
   }

ParserEngine 的 parse 方法,调用 parseRuleRegistry 方法根据数据库类型,和 antlr g4 中配置的规则名称+Context(parseTree.getClass().getSimpleName()),提取 SQLStatementRule 然后封装成 SQLAST 返回

public SQLAST parse() {
        ...略
        SQLStatementRule rule = parseRuleRegistry.getSQLStatementRule(databaseTypeName, parseTree.getClass().getSimpleName());
        ...略
        return new SQLAST((ParserRuleContext) parseTree, getParameterMarkerIndexes((ParserRuleContext) parseTree), rule);
    }

然后 SQLParseKernel 继续调用了SQLSegmentsExtractorEngine 的 extract 方法

循环 ast 中所有的 Extractors,调用 extract 方法,获取 sql 分段。

   public Collection<SQLSegment> extract(final SQLAST ast) {
        Collection<SQLSegment> result = new LinkedList<>();
        //循环 sql-statement-rule-definition.xml 文件配置的 extractor-rule-refs 中的没一项
        for (SQLSegmentExtractor each : ast.getSqlStatementRule().getExtractors()) {
            if (each instanceof OptionalSQLSegmentExtractor) {
                Optional<? extends SQLSegment> sqlSegment = ((OptionalSQLSegmentExtractor) each).extract(ast.getParserRuleContext(), ast.getParameterMarkerIndexes());
                if (sqlSegment.isPresent()) {
                    result.add(sqlSegment.get());
                }
            } else if (each instanceof CollectionSQLSegmentExtractor) {
                result.addAll(((CollectionSQLSegmentExtractor) each).extract(ast.getParserRuleContext(), ast.getParameterMarkerIndexes()));
            }
        }
        return result;
    }

这里以 WhereExtractor 为例

1、遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点
2、提取第一个 where 内部子查询 where 部分
3、提取参数部分 where 语句中的 ? 号部分

public final class WhereExtractor implements OptionalSQLSegmentExtractor {
    private final PredicateExtractor predicateExtractor = new PredicateExtractor();
    @Override
    public Optional<WhereSegment> extract(final ParserRuleContext ancestorNode, final Map<ParserRuleContext, Integer> parameterMarkerIndexes) {
        //遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点
        Optional<ParserRuleContext> whereNode = ExtractorUtils.findFirstChildNode(ancestorNode, RuleName.WHERE_CLAUSE);
        if (!whereNode.isPresent()) {
            return Optional.absent();
        }
        //创建 WhereSegment
        WhereSegment result = new WhereSegment(whereNode.get().getStart().getStartIndex(), whereNode.get().getStop().getStopIndex(), parameterMarkerIndexes.size());
        //提取第一个 where 内部子查询 where 部分
        Optional<OrPredicateSegment> orPredicateSegment = predicateExtractor.extract(whereNode.get(), parameterMarkerIndexes);
        if (orPredicateSegment.isPresent()) {
            result.getAndPredicates().addAll(orPredicateSegment.get().getAndPredicates());
        }
        //提取参数部分 sql 语句中的 ? 号部分
        Collection<ParserRuleContext> parameterMarkerNodes = ExtractorUtils.getAllDescendantNodes(whereNode.get(), RuleName.PARAMETER_MARKER);
        if (!parameterMarkerNodes.isEmpty()) {
            result.setParameterStartIndex(parameterMarkerIndexes.get(parameterMarkerNodes.iterator().next()));
        }
        return Optional.of(result);
    }
}
2.1、遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点

这里涉及到了一个遍历树的算法

1、用一个队列保存,即将被判断是否匹配的树节点集合
2、如果不匹配那么如果节点包括子节点,那么将子节点添加到队列尾部,为下次匹配做准备。

    public static Optional<ParserRuleContext> findFirstChildNode(final ParserRuleContext node, final RuleName ruleName) {
        //保存解析过程中即将被匹配的树节点
        Queue<ParserRuleContext> parserRuleContexts = new LinkedList<>();
        //将树的根节点入队列,代表下一个即将要被解析到的节点为根节点
        parserRuleContexts.add(node);
        ParserRuleContext parserRuleContext;
        //从队列头部 poll 出来一个节点,poll 弹出节点后,队列中将不存在此数据了
        while (null != (parserRuleContext = parserRuleContexts.poll())) {
            //如果匹配那么退出  ruleName.getName().equals(node.getClass().getSimpleName()); 即只查找一个
            if (isMatchedNode(parserRuleContext, ruleName)) {
                return Optional.of(parserRuleContext);
            }
            //如果不匹配那么将该节点的所有子节点都添加到队列尾部,即子节点也要参与 匹配 isMatchedNode
            for (int i = 0; i < parserRuleContext.getChildCount(); i++) {
                if (parserRuleContext.getChild(i) instanceof ParserRuleContext) {
                    parserRuleContexts.add((ParserRuleContext) parserRuleContext.getChild(i));
                }
            }
        }
        return Optional.absent();
    }

此处为什么不用传统的递归解决遍历树呢? 因为递归同样是一种栈的保存方式,只不过是对程序员不可见罢了,递归保存的方法调用栈内容多例如:程序基数器,操作数栈,返回地址等等。所以这里用parserRuleContexts 只保存解析过程中即将被匹配的树节点即可。

2.2 将sql 分段 SQLSegment 封装成 SQLStatement

1、遍历所有分段,通过 parseRuleRegistry 按照数据库类型和分段类型,查找到对应的填充规则,填充规则在 filler-rule-definition.xml 中配置。
2、如果填充规则不为空,那么调用 fill 方法填充 SQLStatement

    SQLStatementFillerEngine.java
    @SneakyThrows
    public SQLStatement fill(final Collection<SQLSegment> sqlSegments, final int parameterMarkerCount, final SQLStatementRule rule) {
        SQLStatement result = rule.getSqlStatementClass().newInstance();
        Preconditions.checkArgument(result instanceof AbstractSQLStatement, "%s must extends AbstractSQLStatement", result.getClass().getName());
        ((AbstractSQLStatement) result).setParametersCount(parameterMarkerCount);
        result.getAllSQLSegments().addAll(sqlSegments);
        for (SQLSegment each : sqlSegments) {
            Optional<SQLSegmentFiller> filler = parseRuleRegistry.findSQLSegmentFiller(databaseTypeName, each.getClass());
            if (filler.isPresent()) {
                filler.get().fill(each, result);
            }
        }
        return result;
    }

这里以 TableFiller 为例子:

public final class TableFiller implements SQLSegmentFiller<TableSegment> {
    @Override
    public void fill(final TableSegment sqlSegment, final SQLStatement sqlStatement) {
        //填充表名分段 
        if (sqlStatement instanceof TableSegmentAvailable) {
            ((TableSegmentAvailable) sqlStatement).setTable(sqlSegment);
        } else if (sqlStatement instanceof TableSegmentsAvailable) {
            ((TableSegmentsAvailable) sqlStatement).getTables().add(sqlSegment);
        }
    }
}

到此处sql 提取过程就讲完了,在付一张图,解析过程涉及到的类关系图。


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353