1. 程式人生 > >解決 mybatis 載入xml配置檔案bug

解決 mybatis 載入xml配置檔案bug

摘要:mybatis

【問題描述】

                   mybatis在載入配置檔案時,有可能會拋“"Could not find SQL statement to include with refid"異常資訊。

           在windows下偶爾某個開發人員會遇到錯誤,在linux下幾乎肯定會拋異常,但在aix環境下目前還未遇到此問題。

【問題分析】

                對於一些開源框架,我的原則是儘量不去修改它的原始碼,主要是為了方便今後升級減少影響。從mybatis 官方網(http://code.google.com/p/mybatis/)查詢針對此問題的補丁,沒找到針對此問題的修改補丁。於是對mybatis3.0.2原始碼進行除錯,分析發現此問題是由於當mybatis在載入某個 配置檔案時,當存在refid 引用到 其它配置檔案中的ID時,且被引用的那個配置檔案還未載入時,就丟擲了 “ Could not find SQL statement to include with refid" 異常錯誤:

Caused By: org.apache.ibatis.builder.BuilderException: Could not find SQL statement to include with refid 'SAD02.SAD02_COL'
        at org.apache.ibatis.builder.xml.XMLStatementBuilder$IncludeNodeHandler.handleNode(XMLStatementBuilder.java:160)
        at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseDynamicTags(XMLStatementBuilder.java:87)
        at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:45)
        at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:192)
        at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:57)
        Truncated. see log file for complete stacktrace


  原始碼位置:

        此問題應該是 mybatis 3.0.2的一個bug,而且在目前最新的版本也同樣存在此問題。

【解決方案】

          針對此問題,目前我的解決辦法是在載入配置檔案時,記錄那些載入過程中出錯的檔案,等所有檔案載入之後,之前因refid引用的不存的ID在後邊架載,然後再重新載入之前加載出錯的檔案。

          這樣就解決了大多數refid id不存在的問題,但對於存在級聯引用或迴圈引用的配置則繼續報錯,不建議這樣引用。

          1修改Mybatis src/org/apache/ibatis/builder/xml/XMLMapperBuilder.java

                           增加屬性:                         

private boolean ignoreAlreadyMapped = false;

                           增加構造方法:             

  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,boolean ignoreAlreadyMapped) {
      super(configuration);
      this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
      this.parser = new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver());
      this.sqlFragments = sqlFragments;
      this.resource = resource;
      this.ignoreAlreadyMapped = ignoreAlreadyMapped;
    }

                            修改buildStatementFromContext方法:

 private void buildStatementFromContext(List<XNode> list) {
    for (XNode context : list) {
      try {
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, this);
          statementParser.parseStatementNode(context);
      } catch (IllegalArgumentException e) {
          if (ignoreAlreadyMapped && e != null && e.getLocalizedMessage().indexOf("already contains value for") != -1) {

          } else {
              throw e;
          }
      }
    }

如圖所示:

                  

   2. 修改mybatis-spring-1.0.1 包中 org.springframework.orm.ibatis3.SqlSessionFactoryBean類 buildSqlSessionFactory()方法           原始碼塊:     
configuration.setEnvironment(environment);

        if (!ObjectUtils.isEmpty(mapperLocations)) {
            Map<String, XNode> sqlFragments = new HashMap<String, XNode>();

            for (Resource mapperLocation : mapperLocations) {
                try {
                    Reader reader = new InputStreamReader(mapperLocation.getInputStream());
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(reader, configuration, mapperLocation.getURI().toString(), sqlFragments);
                    xmlMapperBuilder.parse();
                }
                catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                }

                logger.debug("Parsed mapper file: '" + mapperLocation + "'");
            }
        }
        else {
            logger.debug("Property 'mapperLocations' was not specified, only iBatis mapper files specified in the config xml were loaded");
        }

                   修改後:

configuration.setEnvironment(environment);
     List<Resource> reParse = new ArrayList<Resource>();  //Modified by herong on August 9, 2013 
if (!ObjectUtils.isEmpty(mapperLocations)) {
	Map<String, XNode> sqlFragments = new HashMap<String, XNode>();

	for (Resource mapperLocation : mapperLocations) {
		try {
			//System.out.println("Parsed mapper file: '" + mapperLocation + "'");
			logger.debug("Parsed mapper file: '" + mapperLocation + "'");  //Modified by herong on August 9, 2013 
			Reader reader = new InputStreamReader(mapperLocation.getInputStream());
			XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(reader, configuration, mapperLocation.getURI().toString(), sqlFragments);
			xmlMapperBuilder.parse();
		} catch (Exception e) {
			configuration.removeLoadedResource(mapperLocation.getURI().toString());
			reParse.add(mapperLocation);   //Modified by herong on August 9, 2013 
		}
	}
	//Modified by herong on August 9, 2013 
	//--------begin-------------
	for (Resource mapperLocation : reParse) {
		try {
			logger.debug("Parsed mapper file: '" + mapperLocation + "'");
			Reader reader = new InputStreamReader(mapperLocation.getInputStream());
			XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(reader, configuration, mapperLocation.getURI().toString(), sqlFragments,true);
			xmlMapperBuilder.parse();
		} catch (Exception e) {
			if (e != null && e.getLocalizedMessage().indexOf("Mapped Statements collection already") == -1){
				throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
			} 
		} 
	}
	 //--------end-------------

【附】

 mybatis補丁檔案: