美文网首页
MyBatis(三) xml文件解析流程 动态SQL解析

MyBatis(三) xml文件解析流程 动态SQL解析

作者: 5d44bc28b93d | 来源:发表于2017-05-23 10:57 被阅读518次

    1.MyBatis将整个系统串联起来的就是Configure对象这个需要牢记。在前面MyBatis xml文件解析流程(二) Mapper解析中介绍了Mapper的解析入口,以及Mapper文件中各个节点的解析方法。里面有几个类需要总结下

    BaseBuilder.png

    <p>

    1.BaseBuilder:作为其他Builder类的基类
    2.XMLConfigBuilder:在Configuration解析时介绍过,主要用来解析config配置文件下的Configuration节点,内部会使用XMLMapperBuilder用于解析xml文件
    3.XMLMapperBuilder:在Mapper解析中也介绍过。主要用来解析Mapper文件的,里面用,内部会使用XMLStatementBuilder来处理节点
    4.XMLStatementBuilder:解析select|insert|update|delete节点,内部会使用XMLScriptBuilder解析xml节点
    5.XMLScriptBuilder:解析sql中各个其他的节点并把解析结果保存到SqlNode中。
    6.MapperBuilderAssistant:这是mapper解析中的关键,他作为mapper解析的助理类,负责将解析出来的结果保存下来,并且通过configuration.addMappedStatement方法保存到conguration对象中,最终在调用dao是根据传入的参数动态生成sql。所以说configuration是MyBatis调用的调配中心。


    2.动态sql解析流程分析
    2.1 从XMLMapperBuilder类中configurationElement方法是mapper文件解析开始。

    private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
              throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          sqlElement(context.evalNodes("/mapper/sql"));
    //sql insert|select|update|delete节点解析入口
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }
    

    2.2调用buildStatementFromContext方法,将配置文件中的insert|select|update|delete保存到list集合中,并且通过buildStatementFromContext方法进行循环解析所有节点

     private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
          buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
      }
    
      private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
    //创建XMLStatementBuilder 实例
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
    //进行解析
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    

    2.3此处XMLStatementBuilder(configuration, builderAssistant, context,requiredDatabaseId)引出来XMLStatementBuilder , MapperBuilderAssistant类,上面介绍过这两个类的作用,**XMLStatementBuilder **的parseStatementNode方法负责即解析sql节点。

     public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
    
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes,
        // in case if IncompleteElementException (issue #291)
        List<XNode> selectKeyNodes = context.evalNodes("selectKey");
        if (configuration.getDatabaseId() != null) {
          parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
        }
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        }
    //MapperBuilderAssistant将解析信息保存并且与configuration关联起来,在后面调用mapper接口时会根据这里保存的信息,以及用户传入的参数来进行动态生成sql后面会说到。
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    

    2.4 其中 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    很重要,我们看下这个SqlSouse是如何生成的。

    sqlsource.png

    langDriver我们看其子类XMLLanguageDriver。在XMLLanguageDriver使用XMLScriptBuilder并传入了Configuration ,以及XNode 节点parameterType

    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script);
        return builder.parseScriptNode();
      }
    

    2.5XMLScriptBuilder的parseScriptNode方法

    public SqlSource parseScriptNode() {
    //进行解析节点并返回sqlnode集合
        List<SqlNode> contents = parseDynamicTags(context);
    //MixedSqlNode 负责对所有不同的SqlNode进行遍历并且调用他们的apply方法此时并没有调用只是将其保存到sqlSource 中,等待将来用户调用时执行MixedSqlNode apply方法
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    //返回的是动态DynamicSqlSource
        SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        return sqlSource;
      }
    

    2.6 SqlNode介绍

    SqlNode.png

    如上图所示所有的“sqlNode”都是实现自SqlNode接口并实现它的apply方法
    <p>

    1.TrimSqlNode:insert|update|select|delete,mapper配置下的trim节点对象
    2.IfSqlNode:insert|update|select|delete,mapper配置下的if节点对象
    3.ChooseSqlNode:insert|update|select|delete,mapper配置下的Choose节点对象
    4.ForEachSqlNode:insert|update|select|delete,mapper配置下的ForEach节点对象
    5.WhereSqlNode,SetSqlNode集成自trim节点,其实这两个完全可以用trim的形式写出来。等等。

    2.7XMLScriptBuilder parseDynamicTags方法

    private List<SqlNode> parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          String nodeName = child.getNode().getNodeName();
    //如果节点类型是CDATA类型或者是TEXT_NODE则创建TextNode
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
              || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            contents.add(new TextSqlNode(data));
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE && !"selectKey".equals(nodeName)) { // issue #628
    //如果是ELEMENT_NODE 类型则根据nodeName,根据nodename获取不同的NodeHandler
            NodeHandler handler = nodeHandlers.get(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);
          }
        }
        return contents;
      }
    

    获取不同的NodeHandler

    private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
        private static final long serialVersionUID = 7123056019193266281L;
    
        {
          put("trim", new TrimHandler());
          put("where", new WhereHandler());
          put("set", new SetHandler());
          put("foreach", new ForEachHandler());
          put("if", new IfHandler());
          put("choose", new ChooseHandler());
          put("when", new IfHandler());
          put("otherwise", new OtherwiseHandler());
          put("bind", new BindHandler());
        }
      };
    

    以上就完成了sql的解析操作
    2.8 介绍MixedSqlNode ,其中apply用于遍历所有sqlnode节点

      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    

    相关文章

      网友评论

          本文标题:MyBatis(三) xml文件解析流程 动态SQL解析

          本文链接:https://www.haomeiwen.com/subject/okvsxxtx.html