mybatis源码解析--mapper解析之cache

mybatis源码解析–mapper解析之cache

cache用法

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。

默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,需要我们在自己的maper文件中添加

<cache type="" eviction="FIFO" flushInterval="60000" size="512" readOnly="" blocking="">
      <property name="" value=""/>
</cache>
#type 
表示自定义的缓存的类,没有的情况下会存在一个默认的缓存类PerpetualCache.class。
自定义的缓存类需要有一个以String为参数的构造函数
#eviction 
表示缓存的策略(只支持默认的缓存类,自定义的缓存类不支持)
    LRU – 最近最少使用的:移除最长时间不被使用的对象。
    FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
#flushInterval 
(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
#size 
(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。
默认值是 1024
#readOnly 
(只读)属性可以被设置为 truefalse。
只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。
这会慢一些,但是安全,因此默认是 false
#blocking 
是否采用阻塞的方式(加锁)

同时还需要我们在对应的SQL上面调整缓存的使用

<!--根据ID查询-->
    <select id="selectById"
         resultType="com.liubin.study.mybatis.object.dataobject.SupplierLabelDO"
            useCache="true"
            flushCache="false">
        <include refid="selectFields"/>
        <where>
            label.id = #{id}
        </where>
    </select>
#useCache 是否使用缓存。
从缓存中读取,如果存在,那么直接返回。不存在,则查询数据库并更新缓存。
#flushCache 是否更新缓存
强制更新缓存

实现自定义的缓存

对于自定义的缓存,我们需要实现Cache接口,可以看一下Cache接口的内容

public interface Cache {

  /**
   * 返回缓存的唯一标识
   */
  String getId();

  /**
   * 设置缓存
   */
  void putObject(Object key, Object value);

  /**
   * 换取缓存
   */
  Object getObject(Object key);

  /**
   * As of 3.3.0 this method is only called during a rollback 
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that 
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null 
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be 
   * available instead of hitting the database.
   *
   * 
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * 清空缓存
   */  
  void clear();

  /**
   * 可选的,获取缓存的数量
   */
  int getSize();

  /** 
   * 可选的,获取缓存的读写锁
   */
  ReadWriteLock getReadWriteLock();

}

我们自行实现一个用mapper存储的缓存

/**
 * 自定义实行的mybatis的缓存<br/>
 * 
 * @Type
 * @Desc
 * @date 2018/8/2
 * @Version
 */
public class SimpleCache implements Cache {
    private final Logger LOGGER = LoggerFactory.getLogger(SimpleCache.class);
    private String id;
    private Map<Object, Object> cache = new HashMap<Object, Object>();
    public SimpleCache(String id) {
        this.id = id;
    }
    public String getId() {
        return this.id;
    }
    public void putObject(Object key, Object value) {
        LOGGER.info("putObject:{},{}",key,value);
        cache.put(key,value);
    }
    public Object getObject(Object key) {
        Object value = cache.get(key);
        LOGGER.info("getObject:{},value:{}",key,value);
        return value;
    }
    public Object removeObject(Object key) {
        LOGGER.info("removeObject:{}",key);
        return null;
    }
    public void clear() {
        LOGGER.info("clear");
    }
    public int getSize() {
        LOGGER.info("getSize");
        return 0;
    }
    public ReadWriteLock getReadWriteLock() {
        LOGGER.info("getReadWriteLock");
        return null;
    }
}

在Mapper.xml中使用自定义的缓存

<!--
        自行实现的缓存缓存的配置
        -->
    <cache type="com.liubin.study.mybatis.cache.SimpleCache" eviction="FIFO" flushInterval="60000" size="512">
    </cache>
    <!--使用默认的缓存
    <cache eviction="FIFO" flushInterval="60000" size="512">
    </cache>-->

    <!--根据ID查询-->
    <select id="selectById"
            resultType="com.liubin.study.mybatis.object.dataobject.SupplierLabelDO"
            useCache="true">
        <include refid="selectFields"/>
        <where>
            label.id = #{id}
        </where>
    </select>

测试的代码如下

SqlSessionFactory factory = buildFactory();
        SqlSession session = factory.openSession();
        try{
            ISupplierLabelDAO supplierLabelDAO = session.getMapper(ISupplierLabelDAO.class);
            SupplierLabelDO labelDO = supplierLabelDAO.selectById(1L);
            System.out.println(labelDO);

            labelDO = supplierLabelDAO.selectById(1L);
            System.out.println(labelDO);
        }catch (Exception e){
            LOGGER.error("执行查询SQL失败",e);
        }finally {
            session.close();
        }

执行的结果是

[INFO]-[com.liubin.study.mybatis.cache.SimpleCache] getObject:-534486525:156909814:com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById:0:2147483647:SELECT
        label.id,
        label.supplier_type,
        label.supplier_id,
        label.label_type,
        label.label_id,
        label.label_name,
        label.gmt_created,
        label.gmt_modified
        FROM supplier_label label

         WHERE label.id = ?:1:dev,value:null
[DEBUG]-[com.liubin.study.mybatis.object.ISupplierLabelDAO] Cache Hit Ratio [com.liubin.study.mybatis.object.ISupplierLabelDAO]: 0.0
[DEBUG]-[org.apache.ibatis.transaction.jdbc.JdbcTransaction] Opening JDBC Connection
[DEBUG]-[org.apache.ibatis.datasource.pooled.PooledDataSource] Created connection 394721749.
[DEBUG]-[org.apache.ibatis.transaction.jdbc.JdbcTransaction] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1786f9d5]
[DEBUG]-[com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById] ==>  Preparing: SELECT label.id, label.supplier_type, label.supplier_id, label.label_type, label.label_id, label.label_name, label.gmt_created, label.gmt_modified FROM supplier_label label WHERE label.id = ? 
[DEBUG]-[com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById] ==> Parameters: 1(Long)
[DEBUG]-[com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById] <==      Total: 1
com.liubin.study.mybatis.object.dataobject.SupplierLabelDO@15b3e5b[id=1,supplierType=12,supplierId=1,labelType=1,labelId=1,labelName=1,gmtCreated=Thu Aug 02 22:04:35 CST 2018,gmtModified=Thu Aug 02 22:04:38 CST 2018]
[INFO]-[com.liubin.study.mybatis.cache.SimpleCache] getObject:-534486525:156909814:com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById:0:2147483647:SELECT
        label.id,
        label.supplier_type,
        label.supplier_id,
        label.label_type,
        label.label_id,
        label.label_name,
        label.gmt_created,
        label.gmt_modified
        FROM supplier_label label

         WHERE label.id = ?:1:dev,value:null
[DEBUG]-[com.liubin.study.mybatis.object.ISupplierLabelDAO] Cache Hit Ratio [com.liubin.study.mybatis.object.ISupplierLabelDAO]: 0.0
com.liubin.study.mybatis.object.dataobject.SupplierLabelDO@15b3e5b[id=1,supplierType=12,supplierId=1,labelType=1,labelId=1,labelName=1,gmtCreated=Thu Aug 02 22:04:35 CST 2018,gmtModified=Thu Aug 02 22:04:38 CST 2018]
[INFO]-[com.liubin.study.mybatis.cache.SimpleCache] putObject:-534486525:156909814:com.liubin.study.mybatis.object.ISupplierLabelDAO.selectById:0:2147483647:SELECT
        label.id,
        label.supplier_type,
        label.supplier_id,
        label.label_type,
        label.label_id,
        label.label_name,
        label.gmt_created,
        label.gmt_modified
        FROM supplier_label label

         WHERE label.id = ?:1:dev,[com.liubin.study.mybatis.object.dataobject.SupplierLabelDO@15b3e5b[id=1,supplierType=12,supplierId=1,labelType=1,labelId=1,labelName=1,gmtCreated=Thu Aug 02 22:04:35 CST 2018,gmtModified=Thu Aug 02 22:04:38 CST 2018]]
[DEBUG]-[org.apache.ibatis.transaction.jdbc.JdbcTransaction] Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1786f9d5]

从执行结果可以看到,在第一次的时候,因为缓存中不存在,去查询了数据库。第二次因为缓存中存在,则没有查询数据库。

cache 源码的解析

之前我们已经知道了对于mapper.xml中所有元素的解析都由XMLMapperBuilder来进行。现在我们深入了解一下对于缓存的解析。

private void cacheElement(XNode context) throws Exception {
    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");
      // 不存在,默认为false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 不存在,默认为false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 对于参数的解析
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

从上面我们看到,是以硬编码的形式去解析cache标签下面的所有的元素的。对于缓存的构建,则委托给了builderAssistant的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.addCache(cache);
    currentCache = cache;
    return cache;
  }

这里可以看到,对于缓存的构建交由CacheBuilder来进行构建的。我们继续深入。

public Cache build() {
    // 设置默认的实现类
    setDefaultImplementations();
    // 实例化缓存
    Cache cache = newBaseCacheInstance(implementation, id);
    // 设置缓存的参数
    setCacheProperties(cache);
    // 自定义的缓存实现类和mybatis提供的缓存实现类的构建
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

从上面我们可以看出,缓存是这里实例化并传入参数的。同时如果缓存的实现类是PerpetualCache,那么会构建一个缓存的装饰器链出来。链尾由PerpetualCache来实现。
如果是自定义的其他的缓存类,则会再外部再包装一个LoggingCache(用来记录缓存的命中概率)的实现。

缓存-设置缓存的默认实现类

private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

如果默认实现类为空,那么实现类采用PerpetualCache(使用map实现的缓存),装饰器为空,那么默认使用LruCache(最新使用的缓存-策略实现)。

缓存-实例化缓存实现类

/**
 * 实例化缓存
 */
private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
    Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(id);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
    }
  }
  /**
   * 获取基础的缓存构造函数
   */
  private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
    try {
      return cacheClass.getConstructor(String.class);
    } catch (Exception e) {
      throw new CacheException("Invalid base cache implementation (" + cacheClass + ").  " +
          "Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);
    }
  }

从这里可以看出,是获取到一个缓存类的一个带有String.class的构造参数,然后进行构建缓存信息。

缓存–设置缓存的参数

private void setCacheProperties(Cache cache) {
    if (properties != null) {
      // 根据setter方法获取需要传入的参数信息(只支持基础的包装类)
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          Class<?> type = metaCache.getSetterType(name);
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    // 如果实现了 InitializingObject 接口,会调用InitializingObject  的initialize()方法
    if (InitializingObject.class.isAssignableFrom(cache.getClass())){
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '" +
            cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }

缓存 – 设置缓存的策略

// 实例化缓存的策略类
private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
    Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(base);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
    }
  }

 //获取缓存策略类中的包含Cache.class的构造函数
  private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {
    try {
      return cacheClass.getConstructor(Cache.class);
    } catch (Exception e) {
      throw new CacheException("Invalid cache decorator (" + cacheClass + ").  " +
          "Cache decorators must have a constructor that takes a Cache instance as a parameter.  Cause: " + e, e);
    }
  }

从这里我们看到,对于缓存的策略,我们也可以自己单独的去实现,只需要在中注册对应的缓存策略,同时在的eviction中使用自己注册的策略即可

设置标准的缓存策略

try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      // 如果清空时间段不为空,那么采用ScheduledCache来包装一层
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      // 如果读写标志为true,那么采用SerializedCache来包装一层
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      // 默认添加命中率日志的包装
      cache = new LoggingCache(cache);
      // 默认添加锁机制的包装
      cache = new SynchronizedCache(cache);
      // 如果阻塞获取为true,那么采用阻塞策略包装一层
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }

从这段代码,我们可以知道,如果使用默认的缓存。那么缓存会实现一些默认的策略。会打印对应的命中率、保证线程安全性。

猜你喜欢

转载自blog.csdn.net/liu20111590/article/details/81329130