Mybatis源码解析之Mapper映射器

Mapper是XML解析的最后子节点,也是最重要的一节。我们先了解下Mapper.xml文件结构:

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

解析mappers节点代码:

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

从源码可以看到mapper配置有四种方案,下面是官方文档提供的例子:

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

这里我们使用resource讲解,可以看到mappers子节点mepper配置 resourse,url,mepperClass每次只能配置一种,package子节点是通过包扫描器进行扫描。虽然是通过不同的方式进行解析,但最终的处理类都是XMLMapperBuilder 解析器的parse方法:

          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }

看下这个方法:

  public void parse() {
    //判断configuration对象是否已经加载对应的mapper.xml文件,避免重复加载
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper.xml文件
      configurationElement(parser.evalNode("/mapper"));
      //加载mapper.xml文件,其实添加到Set集合
      configuration.addLoadedResource(resource);
      //绑定namespace与mapper.xml关系
      bindMapperForNamespace();
    }
    //这三个方法没看懂,后面看懂了再补上
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
configurationElement方法解析了mapper节点下的所有子节点,
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      //可见命名空间是必须配置的
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置当前namespace 
      builderAssistant.setCurrentNamespace(namespace);
      //解析缓存引用
      cacheRefElement(context.evalNode("cache-ref"));
      //解析缓存
      cacheElement(context.evalNode("cache"));
      //处理参数映射配置
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //处理结果映射配置
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //处理sql片段
      sqlElement(context.evalNodes("/mapper/sql"));
      //使用XMLStatementBuilder的对象解析mapper的<select>、<insert>、<update>、<delete>节点,
      //mybaits会使用MappedStatement.Builder类build一个MappedStatement对象,
      //所以mybaits中一个sql对应一个MappedStatement
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

 

posted @ 2017-07-06 19:02  ToFlyer  阅读(180)  评论(0)    收藏  举报