MyBatis之映射文件解析_概述

配置文件中映射文件的配置

mybatis配置文件中用子标签mappers定义要用到的映射文件,它有两个子标签mapperpackage,mapper子标签定义一个具体的XML映射文件,而在纯注解的方式下,使用package定义一个包名,这个包下的所有类都作为映射接口类,类中用注解定义了SQL语句。

<mappers>        
    <!-- 定义所需的映射文件 -->
    <!-- mapper可以有resource,url及class三个属性 -->
    <mapper resource="mappers/OrderMapper.xml" />
    <mapper resource="mappers/CustomerMapper.xml"/>
    <!-- 纯注解的方法,这是包名,将这个包下的每个类都作为映射接口类 -->
    <package name="com.kclm.owep.mapper"></package>
</mappers>

映射文件解析入口

mybatis在启动时,即会解析配置文件中的映射文件,当解析到配置文件的子标签mappers时,调用XMLConfigBuilder中的方法mapperElement开始处理配置文件中的mappers元素。

private void mapperElement(XNode parent)方法功能:

​ 若子元素是mapper,则获取到mapper子标签的属性resource、url及class,这三个属性只能定义一个,并且必须定义一个

​ resource的值是一个本地的XML映射文件,url也类似,而class的属性值则是一个映射接口的类名,对应着使用纯注解的映射方式。

​ 若是resource或url,则获取到对应的资源(一般是XML配置文件对应的InputStream),创建XMLMapperBuilder对象,在XML配置下,这个对象负责解析映射文件中的配置,其方法是public void parse()

​ 属性class的值应该是映射接口的类名,此时调用Configuration对象有的addMapper方法,将这个类名加到MapperRegistry注册器中,Configuration对象持有MapperRegistry,具体加到映射注册器由MapperRegistry的方法public void addMapper(Class type)实现。

private void mapperElement(XNode parent)方法源代码:

// 解析配置文件中的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");
        // resource,url及mapperClass必须有一个为空,并且不能同时不空  
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          // 获得映射文件输入流  
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 创建XMLMapperBuilder对象  
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          // 使用parse方法解析映射文件  
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          // 这里创建了XMLMapperBuilder对象,由它负责解析映射文件中的元素  
          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.");
        }
      }
    }
  }
}

XMLMapperBuilder类

XMLMapperBuilder用来解析Mybatis映射文件,与XMLConfigBuilder类似,它也是BaseBuilder的子类,负责映射文件的解析,节点的解析仍然使用XPathParser对象,同时使用MapperBuilderAssistant辅助类,用于缓存、sql参数、查询返回的结果集处理 。

XMLMapperBuilder主要持有Configuration,XPathParser及MapperBuilderAssistant等对象,它的主要功能就是解析映射文件,并将解析结果装配到Configuration对象,其入口方法为public void parse()

// 继承BaseBuilder
public class XMLMapperBuilder extends BaseBuilder {
  // 使用XPathParser解析XML文档节点
  private final XPathParser parser;
  // 辅助类,用于缓存、sql参数、查询返回的结果集处理  
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

}

XMLMapperBuilder类的parse方法

在XMLConfigBuilder解析到mappers子标签,如果要解析XML映射文件,则会创建XMLMapperBuilder对象,并调用parse()方法解析映射文件。

parse主要功能:

​ 先判断要解析的资源(resource)是否已经解析,在Configuration中有一个资源已解析的集合loadedResources,以此判断资源是否已加载,若没有加载,则调用方法configurationElement开始解析映射文件的mapper根标签,标识这个资源已加载,并调用方法bindMapperForNamespace处理命名空间,如果一个映射文件的namespace是一个映射接口类,则要注册这个映射接口类。

​ 无论资源是否已加载,都要处理没有解决的ResultMaps、CacheRefs及Statements

public void parse() {
  // 这个资源是否已解析  
  if (!configuration.isResourceLoaded(resource)) {
    // 解析映射文件中根节点mapper的主方法configurationElement  
    configurationElement(parser.evalNode("/mapper"));
    //   
    configuration.addLoadedResource(resource);
    // 该方法处理mapper标签的namespace属性,若namespace属性定义的是一个映射接口类,在这里处理这个接口类  
    bindMapperForNamespace();
  }
  // 其他处理,处理没有解决的ResultMaps、CacheRefs及Statements
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

XMLMapperBuilder类的bindMapperForNamespace方法

这个方法用来处理mapper标签的namespace属性,若这个属性是一个映射接口,则将这个类型保存到Configuration对象中。并注册到MapperRegistry中。

private void bindMapperForNamespace() {
  // 获得当前命名空间字符串  
  String namespace = builderAssistant.getCurrentNamespace();
  // 若不空  
  if (namespace != null) {
    // 检查是否是一个类,不是类,则不作处理  
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      //ignore, bound type is not required
    }
    // 若是一个类型,即是一个映射接口  
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
		// 将这个类型保存到Configuration对象中          
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }

XMLMapperBuilder类的configurationElement方法

private void configurationElement(XNode context)方法比较简单,就是调用相应的方法依次处理映射文件根标签的mapper元素的属性namespace及各个子标签:

cache-ref – 对其他命名空间缓存配置的引用。

cache – 对给定命名空间的缓存配置

resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。

parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。

sql – 可被其他语句引用的可重用语句块。

insert – 映射插入语句

update – 映射更新语句

delete – 映射删除语句

select – 映射查询语句

上面四种SQL语句标签的处理方法是一样的。

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");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

后面的博文会依次介绍每个子标签的解析。

posted @ 2022-05-11 11:14  beckwu  阅读(237)  评论(0)    收藏  举报