org.apache.ibatis.builder.IncompleteElementException:
Could not find a parent resultmap with id 'com.springcloud.mxl.sys.base.mapper.BaseMapper.StdResultMap'
最终解决方案xml扫描路径(classpath: 替换为 classpath*: ) -> classpath*:com/springcloud/**/*Mapper.xml
正常运行时SqlSessionFactoryBean的 resources 可以读取到xml
调试mybatis代码最终无果, 最后莫名补全路径,junit竟然好了
最终解决方案(classpath: 替换为 classpath*: ) -> classpath*:com/springcloud/**/*Mapper.xml
当正常启动项目: classpath: 扫描了classes下的**/*Mapper.xml
当用junit运行时: classpath: 只进行扫描了test-classes下的**/*Mapper.xml
而使用 classpath*: 则扫描classes与test-classes下
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith("classpath*:")) {
return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
} else {
int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(58) + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
}
}
解析为何无xml资源报错 :
SqlSessionFactoryBean 在bean属性装配完成后的afterPropertiesSet中执行buildSqlSessionFactory()方法,包含如下代码片段
// 此处判断 mapperLocations (上面xml文件对象) 是否为空, 不为空由xmlMapperBuilder.parse() 解析
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse(); // 解析xml 文件
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
this.mapperLocations不为空处理步骤(仅讨论resultMap部分) :
XMLMapperBuilder.parse() -> 存在resultMap继承,父resultMap还未加载的,抛出Could not find a parent resultmap with id xx,
被上层XMLMapperBuilder.resultMapElement()方法捕获,
存储到 this.configuration.addIncompleteResultMap()即 configuration.IncompleteResultMap 列表内,属于不完全加载,
每一次parse循环会执行 this.parsePendingResultMaps() 检验IncompleteResultMap的父(extends) resultMap是否被加载进来,如果上一次parse加载了,这次就可以处理IncompleteResultMap进行完全加载.
PS:由于每次都要循环检验,毕竟有消耗,因此为避免这种消耗,是否可以让父resultMap优先加载呢,这样后面就不会存在IncompleteResultMap , 需要把父mapper.xml放目录结构父层级 或者 以AxxMapper.xml开头排同层级之前(注意:这并不能解决后面checkDaoConfig报错问题)
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps(); // 解析待定ResultMaps
this.parsePendingChacheRefs(); // 解析待定ChacheRefs
this.parsePendingStatements(); // 解析待定Statements
}
this.mapperLocations如果为空: DaoSupport 的 afterPropertiesSet 中检验dao的配置 checkDaoConfig()
PS : 本人使用github的mybatis开源框架 tk.mapper 4.0.4版,内部实现了自己的MapperFactoryBean
// 验证mapper是否加载,如果没有重新加载mapper.xml
protected void checkDaoConfig() {
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = this.getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface); // 重加载
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
// tk.mapper 4.0.4版 tk\mybatis\spring\mapper\MapperFactoryBean.class 比官方1.3.0版多如下代码,下图可见
if (configuration.hasMapper(this.mapperInterface) && this.mapperHelper != null && this.mapperHelper.isExtendCommonMapper(this.mapperInterface)) {
this.mapperHelper.processConfiguration(this.getSqlSession().getConfiguration(), this.mapperInterface);
}
}
tk.MapperFactoryBean中this.mapperHelper.processConfiguration()内执行此方法configuration.getMappedStatements(),
进而this.buildAllStatements(),接着加载incompleteResultMaps, 由于只加载了this.mapperInterface,其他未加载,导致找不到父resultMap ,而此处未捕获异常处理,所以直接报错;
checkDaoConfig中的重加载步骤 :
Configuration.addMapper() ->
MapperRegistry.addMapper() ->
MapperAnnotationBuilder.parse() ->
MapperAnnotationBuilder.loadXmlResource() ->
XMLMapperBuilder.parse() //解析xml
回溯到 判断 mapperLocations 解析xml的代码