MyBatis之映射文件解析_二级缓存及cache与cache-ref的解析

二级缓存的开启

首先,在mybatis的配置文件中,要将settings的子标签setting的属性cacheEnabled的值设为true,默认即为true,即默认开启二级缓存。

其次在映射文件中,要定义cache标签或cache-ref,这说明这个命名空间开启二级缓存管理。mybatis中每个命名空间(每个映射文件)对应着一个Cache对象,并使用这个Cache对象进行二级缓存管理。如下图所示。

<mapper namespace="xxx.XxxMapper" >
    <!-- 启动当前命名空间的二级缓存 -->
    <cache></cache>
</mapper>    

Cache接口及其实现类:

Cache是一个接口,用来管理二级缓存,mybatis提供了5个常用的实现类PerpetualCache(默认实现),FifoCache,LruCache,SoftCache,WeakCache。

public interface Cache {
  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  default ReadWriteLock getReadWriteLock() {
    return null;
  }
}

PerpetualCache就是用一个没有长度限制的Map存放缓存数据。

FifoCache默认限定可缓存1024个对象,如果超出这个长度,则按FIFO原则清空缓存中的数据

cache标签的配置

<cache type="Cache类型" eviction="FIFO" flushInterval="60000" size="512" readOnly="true" >
    <property name="" value=""></property>
</cache>

其中type定义了一个类型,必须是一个实现了Cache接口的类,默认类型为PERPETUAL, 其他属性在解析cache都会处理,子标签property定义的name-value会作为Properties传给Cache对象

Congiuration的无参构造方法中,注册了上述实现类型的别名。也可自定义Cache的实现类,用来管理二级缓存。

public Configuration() {
  // 注册了Cache的五个内部实现类的别名	
  typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  typeAliasRegistry.registerAlias("LRU", LruCache.class);
  typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
}

Configuration中二级缓存的数据结构

​ 在Configuration中用Map对象存储命名空间对应的Cache对象。mybatis启动时会创建好这些对象。

​ 使用两个Map对象表示二级缓存的配置:

Map<String, Cache> caches = new StrictMap<>("Caches collection"),其中键是命名空间字符串,值是这个命名空间所使用的Cache对象

Collection incompleteCacheRefs = new LinkedList<>();mybatis启动时,如果所引用的命名空间对应的Cache对象还没有找到,则将对应的CacheRefResolver对象加到这个集合中

映射文件中cache标签的解析

解析由XMLMapperBuilder中的方法cacheElement实现。它的主要功能就是获得cache各属性值,这里读取的属性有: type,eviction,flushInterval,size,readOnly,blocking,及子元素property中名-值对,这些属性都有默认值,如type的默认值是别名PERPETUAL表示的类型,eviction的默认值是别名LRU表示的值,所以只要有cache标签就会创建Cache对象,没有cache标签(context为null)则不会创建Cache,意味着当前namespace不使用二级缓存。

private void cacheElement(XNode context) {
  if (context != null) {
    // 默认类型PERPETUAL  
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    // 默认为LRU类型  
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    Long flushInterval = context.getLongAttribute("flushInterval");
    Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    // 读取property子标签  
    Properties props = context.getChildrenAsProperties();
    // 使用帮助类构建Cache对象  
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

Cache对象的构建

MapperBuilderAssistant是一个帮助类,其useNewCache完成创建当前命名空间的二级缓存对象及设置,它首先创建CacheBuilder对象,将获得到属性值调用CacheBuilder中的对应方法,最后调用build方法构造出一个Cache对象,并将这个对象加到配置对象的属性caches

Cache对象构造逻辑,CacheBuilder的build方法,负责构造Cache对象,其构造逻辑为:

​ 默认的实现类是PerpetualCache,装饰类是LruCache

​ Cache实现类应该有一个带字符串的构造方法,使用这个构造方法创建Cache对象

​ 使用反射调用Cache对象的set方法设置property子标签中设置的值,name的值即是Cache对象的属性名

​ 如果Cache是PerpetualCache,则使用装饰者模式,先应用decorators中的Cache装饰对象,默认为LruCache,而PerpetualCache对象是其引用的代理,

​ Cache装饰类都有一个Cache的构造方法,如public LruCache(Cache delegate),使用这个方法创建Cache的装饰类对象作为Cache对象

​ 再调用方法setStandardDecorators进行标准设置:

​ 若Cache对象有size属性,设置这个属性值

​ 若clearInterval不空,加ScheduledCache

​ 若有readWrite,加SerializedCache

​ 加LoggingCache

​ 加SynchronizedCache

​ 若blocking为true,加BlockingCache

​ 如果Cache是不PerpetualCache,说明使用的是定制的Cache对象,则不使用上述的装饰者模式

​ 若Cache是LoggingCache,则加LoggingCache

​ 从上可以看到,如cache标签不作任何设置,其返回的是:

SynchronizedCache对象,它的代理是LoggingCache对象

LoggingCache对象的代理是LruCache对象

LruCache对象的代理是PerpetualCache对象

//MapperBuilderAssistant中的方法
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 使用CacheBuilder构建Cache对象
    // Cache的id是当前映射文件的命名空间
    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();
    // 将当前创建的Cache对象保存到Configuration对象中
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }



cache-ref标签及配置: 说明当前命名空间使用另一个命名空间Cache对象作为当前命名空间的Cache对象。它的配置如下:

在Configuration中用Map<String, String> cacheRefMap = new HashMap<>()表示,其中键是当前的命名空间字符串,值是另一个命名空间字符串,表示当前命名空间使用的用于管理二级缓存的Cache对象,就是后一个命名空间所使用的Cache对象

cache-ref标签解析的主方法为private void cacheRefElement(XNode context)运行时context即对应着标签cache-ref

​ 以当前命名空间为键,cache-ref的属性namespace的值为值加到Configuration的集合cacheRefMap中,

​ 然后创建CacheRefResolver对象

​ 接着如果命名空间xxx中的Cache已存在,则当前命名空间的Cache就使用这个Cache对象,存在对应的MapperBuilderAssistant对象中,否则会抛出异常,并将CacheRefResolver对象加到Configuration对象的incompleteCacheRefs中,以便解析完所有的命名空间后再作处理,这个在方法incompleteCacheRefs完成,处理逻辑与上一样,都是使用MapperBuilderAssistant对象中的方法public Cache useCacheRef(String namespace),只是现在有异常,还找不到所引用的Cache对象,即不做任何处理了。

// cache-ref解析代码,CacheRefResolver对象存储了另一个命名空间字符串,通过这个字符串可以找到它的Cache对象
private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
posted @ 2022-05-12 13:22  beckwu  阅读(1571)  评论(0)    收藏  举报