解決 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補丁檔案: