mybatis源码学习------mapper配置的解析

XMLMapperBuilder

介绍

XMLMapperBuilder类继承自BaseBuilder类,该类也使用了建造者设计模式。XMLMapperBuilder类的主要作用是解析xxxMapper.xml配置文件中用户的配置,如下图所示。

在这里插入图片描述

mybatis-3-mapper.dtd文件中对于mapper节点的定义如下:

在这里插入图片描述

XMLMapperBuilder类的核心功能就是解析<mapper></mapper>标签中配置的内容。

字段

XMLMapperBuilder类中定义的字段如下

//xpath解析器,用于解析用于配置的xml
private final XPathParser parser;
//Mapper建造者助手,一个小工具
private final MapperBuilderAssistant builderAssistant;
//sql片段集合,key为sql片段的namespace+id,value为对应的XNode节点
private final Map<String, XNode> sqlFragments;
//xml配置的资源路径
private final String resource;

构造函数

除了两个已经过时的方法外,所有的构造函数都通过重载调用了最后一个构造函数

@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
  this(reader, configuration, resource, sqlFragments);
  this.builderAssistant.setCurrentNamespace(namespace);
}

@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
    configuration, resource, sqlFragments);
}

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
  this(inputStream, configuration, resource, sqlFragments);
  //给builderAssistant设置命名空间
  this.builderAssistant.setCurrentNamespace(namespace);
}

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
    configuration, resource, sqlFragments);
}
//保存全局的配置,并创建一个BuilderAssistant实例,便于后续使用
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  super(configuration);
  //构造一个builderAssistant实例,方便后面解析
  this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
  this.parser = parser;
  this.sqlFragments = sqlFragments;
  this.resource = resource;
}

核心方法

XMLMapperBuilder类的核心方法为parse()方法,通过调用该方法,触发对mapper.xml文件中内容的解析。

parse()方法的主要逻辑是解析mapper节点中的配置,并做一些后续的收尾工作。parse()方法的定义如下:

/**
 * XMLMapperBuilder类的核心方法,是解析mapper.xml文件配置的入口
 */
public void parse() {
  //防止重复加载
  if (!configuration.isResourceLoaded(resource)) {
    //解析mapper配置
    configurationElement(parser.evalNode("/mapper"));
    //将读取过的配置文件的地址保存在configuration对象的loadedResources字段中,
    //该字段用于防止资源被重复加载
    configuration.addLoadedResource(resource);
    //为读取的mapper配置信息绑定命名空间
    bindMapperForNamespace();
  }
  //解析待定的<resultMap>节点
  parsePendingResultMaps();
  //解析待定的<cache-ref>节点
  parsePendingCacheRefs();
  //解析待定的sql片段节点
  parsePendingStatements();
}

解析mapper配置

该方法的作用是根据dtd文件中的定义,逐一解析<mapper></mapper>下的所有配置,方法实现如下:

private void configurationElement(XNode context) {
  try {
    //获取配置文件中配置的名称空间
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //设置BuilderAssistant实例当前的命名空间为mapper.xml文件中配置的命名空间
    builderAssistant.setCurrentNamespace(namespace);
    //解析<cache-ref/>配置
    cacheRefElement(context.evalNode("cache-ref"));
    //解析<cache/>配置
    cacheElement(context.evalNode("cache"));
    //解析<parameterMap/>配置
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析<resultMap/>配置
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析<sql/>配置
    sqlElement(context.evalNodes("/mapper/sql"));
    //解析<select/>、<insert/>、<update/>和<delete/>配置
    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);
  }
}
解析缓存引用配置

对于< cache-ref />的作用,可参考官网文档的介绍:

对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。如果想要在多个命名空间中共享相同的缓存配置和实例,则可以使用 cache-ref 元素来引用另一个缓存

cacheRefElement方法的主要逻辑就是构建一个缓存引用解析器对象CacheRefResolver,并通过CacheRefResolver对象来解析用户配置的缓存

private void cacheRefElement(XNode context) {
  if (context != null) {
    //在全局配置对象的cacheRefMap字段中维护缓存引用的关系,
    //cacheRefMap中的映射关系为,当前命名空间--->配置中声明的名称空间
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    //构造CacheRefResolver对象
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      //解析缓存引用,实际的解析工作是委派给builderAssistant对象来完成
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      //如果在解析的过程中抛出了IncompleteElementException异常,则将当前的配置解析器对象添加到configuration对象的待完成缓存引用的列表中
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
CacheRefResolver类

根据resolveCacheRef()方法的定义可以发现,mybatis对于缓存引用的解析是委派给了MapperBuilderAssistant类的实例去完成的。

public class CacheRefResolver {
  //构造小助手
  private final MapperBuilderAssistant assistant;
  //引用的命名空间
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

MapperBuilderAssistant#useCacheRef方法的定义如下:

/**
 *
 * @param namespace 配置文件中引用的名称空间
 * @return
 */
public Cache useCacheRef(String namespace) {
  if (namespace == null) {
    throw new BuilderException("cache-ref element requires a namespace attribute.");
  }
  try {
    unresolvedCacheRef = true;
    //在全局配置对象中查找该 namespace 对应的缓存对象
    Cache cache = configuration.getCache(namespace);
    //如果在configuration对象中没有找到对应的缓存实例,则抛出异常
    //并将当前的解析任务放进incompleteCacheRefs集合中,后续会调用	  
    //XmlMapperBuilder#parsePendingCacheRefs()方法对其进行再次解析
    if (cache == null) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
    }
    currentCache = cache;
    unresolvedCacheRef = false;
    return cache;
  } catch (IllegalArgumentException e) {
    throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  }
}
解析缓存配置

二级缓存默认配置时的行为

mybatis二级缓存默认配置的作用如下,他的作用域是全局的:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

cacheElement的代码如下,其中:

  • type配置的是缓存的类型,默认为PERPETUAL,也就是使用HashMap作为缓存的容器。
  • eviction配置的是缓存的清除策略,可用的清除策略有LRU、FIFO、SOFT和WEAK,其中LRU为默认策略。
  • flushInterval获取缓存的清除间隔,可以配置为任意正整数,单位为毫秒,默认为0。
  • size配置的是最大缓存的引用个数,可以是任意正整数,默认为缓存1024个引用。
  • readOnly属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
  • blocking表示当在缓存中获取不到数据时,是否会阻塞后续的请求,默认为false。
//解析<cache/>配置,最后委派给MapperBuilderAssistant的实例去完成缓存对象的创建任务
private void cacheElement(XNode context) {
  if (context != null) {
    //获取type属性的配置,如果未配置,则默认值为PERPETUAL
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    //获取eviction属性的配置,如果未配置,则默认值为LRU
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    //获取eviction属性的配置
    Long flushInterval = context.getLongAttribute("flushInterval");
    //获取size属性的配置
    Integer size = context.getIntAttribute("size");
    //获取readOnly属性的配置
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    //获取blocking属性的配置
    boolean blocking = context.getBooleanAttribute("blocking", false);
    //获取<cache/>标签中配置的property属性
    Properties props = context.getChildrenAsProperties();
    //根据用户配置创建一个缓存对象,并将其添加到configuration的caches属性中
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

MapperBuilderAssistant#useNewCache方法定义如下:

该方法会创建一个缓存建造者对象(建造者设计模式),并通过其创建一个缓存对象

public Cache useNewCache(Class<? extends Cache> typeClass,
    Class<? extends Cache> evictionClass,
    Long flushInterval,
    Integer size,
    boolean readWrite,
    boolean blocking,
    Properties props) {
  Cache cache = new CacheBuilder(currentNamespace)
      .implementation(valueOrDefault(typeClass, PerpetualCache.class))
      .addDecorator(valueOrDefault(evictionClass, LruCache.class))
      .clearInterval(flushInterval)
      .size(size)
      .readWrite(readWrite)
      .blocking(blocking)
      .properties(props)
      .build();
  //将缓存对象保存在Configuration配置对象中
  configuration.addCache(cache);
  currentCache = cache;
  return cache;
}

绑定mapper接口

解析mapper.xml的工作完成后需要为每一个配置文件都绑定一个Mapper接口,并注册到MapperRegistry中,配置文件都绑定Mapper接口的工作由XMLMapperBuilder#bindMapperForNamespace方法完成,其定义如下:

  /**
   * 根据namespace绑定mapper.xml与mapper接口的关系
   */
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        //解析namespace所对应的类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !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
        //追加namespace前缀并添加到configuration对象loadedResources属性中
        configuration.addLoadedResource("namespace:" + namespace);
        //调用mapperRegistry的方法注册mapper接口
        configuration.addMapper(boundType);
      }
    }
  }

处理Pending集合

mybatis在处理xml配置文件时,是按照从上到下顺序进行解析的,但是在解析一个节点的时候,该节点可能引用后面定义的还未解析的节点,遇到这种情况时,mybatis会抛出对应的IncompleteExementException异常。根据抛出异常的节点不同,mybatis会创建不同的解析对象,并添加到confiuration对象中暂存,便于后续再次处理。
parsePendingResultMaps、parsePendingCacheRefs和parsePendingStatements方法便是用于处理这种情况

parsePendingResultMaps
  private void parsePendingResultMaps() {
    //获取resultMap对应的集合
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {//同步,防止重复解析
      //迭代器迭代其中的元素,逐一进行解析
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

parsePendingCacheRefs和parsePendingStatements方法的逻辑与parsePendingResultMaps类似,不做分析

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值