mybatis源码总结--MapperBuilder(2)

首先在此声明,本人是通过学习湖畔微风《深入浅出mybatis》基础之上对代码进行总结。

《深入浅出mybatis》 http://blog.csdn.net/hupanfeng/article/details/9068003

 项目中的sql语句、一级缓存等都会配置在Mapper文件中,在开发中要对其经常进行配置,所以对解析Mapper文件做一下详细的总结。

1、XMLConfigBuilder类中的mapperElement方法是解析Mapper文件的入口,在解析之前会检查子节点类型并获取子节点属性,加载配置文件获取字节流。然后创建XmlMapperBuilder对象,字节流作为参数传进去。

 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.");
          }
        }
      }
    }
  }

1.1 在创建XMLMapperBuilder对象,其中一个参数为configuration.getSqlFragments(),返回Configuration类中的成员数性

 protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
StrictMap是Configuration类的一个内部类用来存储每个mapper元素下的sql标签,它继承了HashMap并重写了put和get方法

  @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key))
        throw new IllegalArgumentException(name + " already contains value for " + key);
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }
通过代码可以看出,重写put方法重要是为了,当key值有重复时,抛出异常。

public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }
get方法,获取不到值时,抛出异常。

1.2 XMLMapper的构造方法,其中MapperBuilderAssistant用于缓存、sql参数、查询返回的结果集处理。

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

2、XMLmapper中的parse方法

 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
2.1 configurationElement 方法解析mapper标签下的子节点。mybatis的一级缓存是默认开启的,可以通过追查buildStatemnetFromContext方法中调用XMLStatementBuilder类的parseStatementNode方法去验证而且想关闭一级缓存光配置useCache=false是没有用的,还必须配置flushCache=true才行。其它的方法有兴趣的朋友可以自己研究,这里就不做详细介绍了。

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      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 RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
2.2 configuration.addLoadedResource(resource)添加到一个HashSet中表示此rescue的mapper文件已经被解析过了。

2.3 bindMapperForNameSpace方法,用于获取mapper的代理工厂对象,判断是否knowMappers中是否存在其代理工厂对象,knowMappers在上一节中有介绍。如果不存在,则创建其代理工程对象并保存在knowMappers中。

  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)) {
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }
总结:(1)通过上一节和这节的总结,可以发现mybatis解析xml的套路。先获取配置文件的根节点,解析其子节点的类名称为(根节点名+MapperBulider)这样写的程序很有层次感。

         (2)内部类并重写hashmap的get、put方法,对于内部类的学习http://blog.csdn.net/ilibaba/article/details/3866537。

         (3)想要详细了解对Mapper文件的解析请学习XMLMapperBuilder类中的configurationElement方法。





郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。