Ibatis源码学习(三):配置文件的解析

ibatis文件的文件的解析从主文件sql-map-config.xml开始。见类:com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser

public SqlMapConfigParser() {
    parser.setValidation(true);
    parser.setEntityResolver(new SqlMapClasspathEntityResolver());

    addSqlMapConfigNodelets();             //"/sqlMapConfig/end()"
    addGlobalPropNodelets();                 //"/sqlMapConfig/properties"
    addSettingsNodelets();                     //"/sqlMapConfig/settings"
    addTypeAliasNodelets();                   //"/sqlMapConfig/typeAlias"
    addTypeHandlerNodelets();              //"/sqlMapConfig/typeHandler"
    addTransactionManagerNodelets();  //"/sqlMapConfig/transactionManager/property" ...
    addSqlMapNodelets();                       //"/sqlMapConfig/sqlMap"
    addResultObjectFactoryNodelets();   //"/sqlMapConfig/resultObjectFactory" ...

  }

先将上诉的节点默认添加进来,然后根据具体的设置在利用回调来初始化各个节点的具体属性值,以addSettingsNodelets()为例:

private void addSettingsNodelets() {
    parser.addNodelet("/sqlMapConfig/settings", new Nodelet() {
      public void process(Node node) throws Exception { //该方法做会调用
        Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
        SqlMapConfiguration config = state.getConfig();

        String classInfoCacheEnabledAttr = attributes.getProperty("classInfoCacheEnabled");
        boolean classInfoCacheEnabled = (classInfoCacheEnabledAttr == null || "true".equals(classInfoCacheEnabledAttr));
        config.setClassInfoCacheEnabled(classInfoCacheEnabled);

        String lazyLoadingEnabledAttr = attributes.getProperty("lazyLoadingEnabled");
        boolean lazyLoadingEnabled = (lazyLoadingEnabledAttr == null || "true".equals(lazyLoadingEnabledAttr));
        config.setLazyLoadingEnabled(lazyLoadingEnabled);

        String statementCachingEnabledAttr = attributes.getProperty("statementCachingEnabled");
        boolean statementCachingEnabled = (statementCachingEnabledAttr == null || "true".equals(statementCachingEnabledAttr));
        config.setStatementCachingEnabled(statementCachingEnabled);

        String cacheModelsEnabledAttr = attributes.getProperty("cacheModelsEnabled");
        boolean cacheModelsEnabled = (cacheModelsEnabledAttr == null || "true".equals(cacheModelsEnabledAttr));
        config.setCacheModelsEnabled(cacheModelsEnabled);

        String enhancementEnabledAttr = attributes.getProperty("enhancementEnabled");
        boolean enhancementEnabled = (enhancementEnabledAttr == null || "true".equals(enhancementEnabledAttr));
        config.setEnhancementEnabled(enhancementEnabled);

        String useColumnLabelAttr = attributes.getProperty("useColumnLabel");
        boolean useColumnLabel = (useColumnLabelAttr == null || "true".equals(useColumnLabelAttr));
        config.setUseColumnLabel(useColumnLabel);

        String forceMultipleResultSetSupportAttr = attributes.getProperty("forceMultipleResultSetSupport");
        boolean forceMultipleResultSetSupport = "true".equals(forceMultipleResultSetSupportAttr);
        config.setForceMultipleResultSetSupport(forceMultipleResultSetSupport);

        String defaultTimeoutAttr = attributes.getProperty("defaultStatementTimeout");
        Integer defaultTimeout = defaultTimeoutAttr == null ? null : Integer.valueOf(defaultTimeoutAttr);
        config.setDefaultStatementTimeout(defaultTimeout);

        String useStatementNamespacesAttr = attributes.getProperty("useStatementNamespaces");
        boolean useStatementNamespaces = "true".equals(useStatementNamespacesAttr);
        state.setUseStatementNamespaces(useStatementNamespaces);
      }
    });
  }

 parser.addNodelet(xpath,nodelet)添加一个节点,见类:com.ibatis.common.xml.NodeletParser

public void addNodelet(String xpath, Nodelet nodelet) {
    letMap.put(xpath, nodelet);
  }

在这个类中,解析节点的时候,

public void parse(Reader reader) throws NodeletException {
    try {
      Document doc = createDocument(reader);
      parse(doc.getLastChild());
    } catch (Exception e) {
      throw new NodeletException("Error parsing XML.  Cause: " + e, e);
    }
  }

  public void parse(InputStream inputStream) throws NodeletException {
    try {
      Document doc = createDocument(inputStream);
      parse(doc.getLastChild());
    } catch (Exception e) {
      throw new NodeletException("Error parsing XML.  Cause: " + e, e);
    }
  }
  
  /**
   * Begins parsing from the provided Node.
   */
  public void parse(Node node) {
    Path path = new Path();
    processNodelet(node, "/");
    process(node, path);
  }

  /**
   * A recursive method that walkes the DOM tree, registers XPaths and
   * calls Nodelets registered under those XPaths.
   */
  private void process(Node node, Path path) {
    if (node instanceof Element) {
      // Element
      String elementName = node.getNodeName();
      path.add(elementName);
      processNodelet(node, path.toString());
      processNodelet(node, new StringBuffer("//").append(elementName).toString());

      // Attribute
      NamedNodeMap attributes = node.getAttributes();
      int n = attributes.getLength();
      for (int i = 0; i < n; i++) {
        Node att = attributes.item(i);
        String attrName = att.getNodeName();
        path.add("@" + attrName);
        processNodelet(att, path.toString());
        processNodelet(node, new StringBuffer("//@").append(attrName).toString());
        path.remove();
      }

      // Children
      NodeList children = node.getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
        process(children.item(i), path);
      }
      path.add("end()");
      processNodelet(node, path.toString());
      path.remove();
      path.remove();
    } else if (node instanceof Text) {
      // Text
      path.add("text()");
      processNodelet(node, path.toString());
      processNodelet(node, "//text()");
      path.remove();
    }
  }

  private void processNodelet(Node node, String pathString) {
    Nodelet nodelet = (Nodelet) letMap.get(pathString);
    if (nodelet != null) { 
      try {
        nodelet.process(node); //这个方法进行回调,设置初始值
      } catch (Exception e) {
        throw new RuntimeException("Error parsing XPath '" + pathString + "'.  Cause: " + e, e);
      }
    }
  }
 

在com.ibatis.sqlmap.engine.builder.xml.SqlMapParser里面解析到statement时:

protected void addStatementNodelets() {
    parser.addNodelet("/sqlMap/statement", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new MappedStatement());
      }
    });
    parser.addNodelet("/sqlMap/insert", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new InsertStatement());
      }
    });
    parser.addNodelet("/sqlMap/update", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new UpdateStatement());
      }
    });
    parser.addNodelet("/sqlMap/delete", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new DeleteStatement());
      }
    });
    parser.addNodelet("/sqlMap/select", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new SelectStatement());
      }
    });
    parser.addNodelet("/sqlMap/procedure", new Nodelet() {
      public void process(Node node) throws Exception {
        statementParser.parseGeneralStatement(node, new ProcedureStatement());
      }
    });
  }
 实际上调用了com.ibatis.sqlmap.engine.builder.xml.SqlStatementParser的parseGeneralStatement方法:
public void parseGeneralStatement(Node node, MappedStatement statement) {

    // ...

    MappedStatementConfig statementConf = state.getConfig().newMappedStatementConfig(id, statement,
        new XMLSqlSource(state, node), parameterMapName, parameterClass, resultMapName, additionalResultMapNames,
        resultClass, additionalResultClasses, resultSetType, fetchSizeInt, allowRemappingBool, timeoutInt, cacheModelName,
        xmlResultName);

    findAndParseSelectKey(node, statementConf);
  }
 此时又是调用了com.ibatis.sqlmap.engine.config.SqlMapConfiguration的newMappedStatementConfig方法:
public MappedStatementConfig newMappedStatementConfig(String id, MappedStatement statement, SqlSource processor,
                                                        String parameterMapName, Class parameterClass,
                                                        String resultMapName, String[] additionalResultMapNames,
                                                        Class resultClass, Class[] additionalResultClasses, 
                                                        String resultSetType, Integer fetchSize,
                                                        boolean allowRemapping, Integer timeout, String cacheModelName,
                                                        String xmlResultName) {
    return new MappedStatementConfig(this, id, statement, processor, parameterMapName, parameterClass, resultMapName,
        additionalResultMapNames, resultClass, additionalResultClasses, cacheModelName, resultSetType, fetchSize,
        allowRemapping, timeout, defaultStatementTimeout, xmlResultName);
  }
 返回一个com.ibatis.sqlmap.engine.config.MappedStatementConfig的实例
MappedStatementConfig(SqlMapConfiguration config, String id, MappedStatement statement, SqlSource processor,
                        String parameterMapName, Class parameterClass, String resultMapName,
                        String[] additionalResultMapNames, Class resultClass, Class[] additionalResultClasses,
                        String cacheModelName, String resultSetType, Integer fetchSize, boolean allowRemapping,
                        Integer timeout, Integer defaultStatementTimeout, String xmlResultName) {
    this.errorContext = config.getErrorContext();
    this.client = config.getClient();
    SqlMapExecutorDelegate delegate = client.getDelegate();
    this.typeHandlerFactory = config.getTypeHandlerFactory();
    errorContext.setActivity("parsing a mapped statement");
    errorContext.setObjectId(id + " statement");
    errorContext.setMoreInfo("Check the result map name.");
    if (resultMapName != null) {
      statement.setResultMap(client.getDelegate().getResultMap(resultMapName));
      if (additionalResultMapNames != null) {
        for (int i = 0; i < additionalResultMapNames.length; i++) {
          statement.addResultMap(client.getDelegate().getResultMap(additionalResultMapNames[i]));
        }
      }
    }
    errorContext.setMoreInfo("Check the parameter map name.");
    if (parameterMapName != null) {
      statement.setParameterMap(client.getDelegate().getParameterMap(parameterMapName));
    }
    statement.setId(id);
    statement.setResource(errorContext.getResource());
    if (resultSetType != null) {
      if ("FORWARD_ONLY".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_FORWARD_ONLY));
      } else if ("SCROLL_INSENSITIVE".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_INSENSITIVE));
      } else if ("SCROLL_SENSITIVE".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_SENSITIVE));
      }
    }
    if (fetchSize != null) {
      statement.setFetchSize(fetchSize);
    }

    // set parameter class either from attribute or from map (make sure to match)
    ParameterMap parameterMap = statement.getParameterMap();
    if (parameterMap == null) {
      statement.setParameterClass(parameterClass);
    } else {
      statement.setParameterClass(parameterMap.getParameterClass());
    }

    // process SQL statement, including inline parameter maps
    errorContext.setMoreInfo("Check the SQL statement.");
    Sql sql = processor.getSql();
    setSqlForStatement(statement, sql);

    // set up either null result map or automatic result mapping
    ResultMap resultMap = (ResultMap) statement.getResultMap();
    if (resultMap == null && resultClass == null) {
      statement.setResultMap(null);
    } else if (resultMap == null) {
      resultMap = buildAutoResultMap(allowRemapping, statement, resultClass, xmlResultName);
      statement.setResultMap(resultMap);
      if (additionalResultClasses != null) {
        for (int i = 0; i < additionalResultClasses.length; i++) {
          statement.addResultMap(buildAutoResultMap(allowRemapping, statement, additionalResultClasses[i], xmlResultName));
        }
      }

    }
    statement.setTimeout(defaultStatementTimeout);
    if (timeout != null) {
      try {
        statement.setTimeout(timeout);
      } catch (NumberFormatException e) {
        throw new SqlMapException("Specified timeout value for statement " + statement.getId() + " is not a valid integer");
      }
    }
    errorContext.setMoreInfo(null);
    errorContext.setObjectId(null);
    statement.setSqlMapClient(client);
    if (cacheModelName != null && cacheModelName.length() > 0 && client.getDelegate().isCacheModelsEnabled()) {//这个判断很关键
      CacheModel cacheModel = client.getDelegate().getCacheModel(cacheModelName);
      mappedStatement = new CachingStatement(statement, cacheModel);
    } else {
      mappedStatement = statement;
    }
    rootStatement = statement;
    delegate.addMappedStatement(mappedStatement);
  }

 上诉的if (cacheModelName != null && cacheModelName.length() > 0 && client.getDelegate().isCacheModelsEnabled())判断,直接决定了是否启用缓存,假设前面没有设置setting这个属性,该值是为空的,不存在默认值,故此时是不会启用缓存的。

总结:

当解析实际的配置文件sql-map-config.xml时,

如果设置了<setting>这个属性,则回调addSettingsNodelets里面的addNodelet里面的Nodelet的process方法。

设置里面的属性值,假如此时没有设置<setting>属性(注意,是没有设置,不是设置为空,因为设置为空,同样的调用回调,设置了默认值),则不再调用此回调函数,则此时将没有默认值,如果此时想启动缓存等,都是无效的。

   故,此时应注意,如果想利用默认值,setting是必须设置的,哪怕设置为空。但,如果设置了setting后,文件的先后顺序是有要求的,如果A文件利用到b文件的命名空间的某个引用id,则此时必须使得B文件在A文件之前装载,否则将出现文件A引用文件B中的id时出现不存在的异常。

相关推荐