摘要: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;
}
}
}
如图所示:
原代码块:
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补丁文件:
http://download.csdn.net/detail/waixin/6480381