记一笔(mybatis) Could not find a parent resultmap with id "xx"

springboot运行项目无问题,但junit跑测试提示以下问题

文中 com.springcloud.mxl.sys.base.mapper.BaseMapper.StdResultMap为 xml resultmap继承的(extends)父parent resultmap

 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) Could not find a parent resultmap with id

junit 运行时,无法读取到xml
记一笔(mybatis) Could not find a parent resultmap with id

调试mybatis代码最终无果, 最后莫名补全路径,junit竟然好了
记一笔(mybatis) Could not find a parent resultmap with id 最终解决方案(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的代码

你可能感兴趣的:(java基础)