mybatis原始碼學習------cache-ref和cache的解析

A股慈善家發表於2020-11-02

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;
}

相關文章