MyBatis 入门 (MyBatis 缓存应用之二级缓存)

二级缓存

为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化

MyBatis 的二级缓存的种类大致可以分为三种:

  • MyBatis 框架默认的二级缓存实现
  • Ehcache 实现二级缓存
  • Redis 实现二级缓存
    在上一小节中提到过 MyBatis 中是通过 cacheEnabled 属性来设置是否启用二级缓存的,默认该值为 true ,即启用,接下来通过一张图来理解一下 MyBatis 对二级缓存是如何实现一步一步配置的:
    在这里插入图片描述
    从上图可以看出,XML 形式的配置和 注解 形式的配置最后都会通过 MapperBuilderAssistant#useNewCache(…) 完成对二级缓存配置的加载,我们来看下该方法的源码:
MapperBuilderAssistant.class

    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)
        // 具体的二级缓存实现类;如果在配置文件中没有设置二级缓存具体实现,则默认使用 PerpetualCache ;它也是一级缓存的实现方式
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        // 设置 MyBatis 二级缓存的缓存策略
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        // 二级缓存刷新时间间隔
        .clearInterval(flushInterval)
        // 二级缓存的长度
        .size(size)
        // 如果设置为 true 则表示 无论是存入或者获取,均经过序列化处理,可避免人为产生脏数据
        // 为 false 时,则表示缓存中的数据为只读的
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        // 稍后我们再看这个方法
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}

下面来看 build() 方法:

CacheBuilder.class

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())) {
      // 所有的二级缓存实现均经过 LoggingCache 装饰;该装饰器只做一件事:当从二级缓存中获取到数据时,打印缓存命令率
      cache = new LoggingCache(cache);
    }
    return cache;
  }

上面两个代码段为 MyBatis 二级缓存的通用步骤方法,下面依次介绍 MyBatis 常用的三种二级缓存实现:

MyBatis 框架默认的二级缓存实现

通过上面的通用步骤的源码,关于默认的二级缓存实现,我们只需关注 build() 方法中的 if 判断部分:

CacheBuilder.class

public Cache build() {
    ...
    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())) {
      // 所有的二级缓存实现均经过 LoggingCache 装饰;该装饰器只做一件事:当从二级缓存中获取到数据时,打印缓存命令率
      cache = new LoggingCache(cache);
    }
    return cache;
  }

上述 if 判断中,对默认二级缓存实现进行完缓存策略装饰之后,还会对缓存对象进行标准装饰,如下:上述 if 判断中,对默认二级缓存实现进行完缓存策略装饰之后,还会对缓存对象进行标准装饰,如下:

CacheBuilder.class

private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        // 设置缓存的长度
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // 调度缓存装饰器
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // 序列化缓存装饰器
        cache = new SerializedCache(cache);
      }
      // 日志缓存装饰器
      cache = new LoggingCache(cache);
      // 同步缓存装饰器
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

配置二级缓存信息之后,便会在后续的解析对应的 SQL 语句时,与其绑定,大致流程图如下:
在这里插入图片描述
通过上面的一些介绍,现在大致清楚了 MyBatis 默认二级缓存的实现过程,现在通过一段示例代码,来了解一些具体使用:

SysUserMapper.xml

<cache eviction="FIFO" flushInterval="60000" size="5000" readOnly="false"/>

由于 MyBatis 的配置中 cacheEnabled 默认为 true,即使用二级缓存,同时,由于我们在这里设置了 readOnly 为 false,即表示从缓存中获取的内容可读写,所以,我们还需要让实体类实现 Serializable 接口,做完这些后,便可以使用 MyBatis 默认的二级缓存了,下面是我的测试方法:

@Test
public void testMyBatisDefaultL2Cache() throws Exception{
    SqlSession sqlSession = sqlSessionFactory.openSession();
    System.err.println("执行第一遍查询~~~~");
    SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
    mapper.selectById(1006);
    // 关闭 sqlSession 为了验证:二级缓存不是基于 session 的
    sqlSession.close();

    System.err.println("重新启动一个 sqlSession ~~~~");
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SysUserMapper mapper1 = sqlSession1.getMapper(SysUserMapper.class);
    System.err.println("执行第二遍查询~~~~");
    mapper1.selectById(1006);
    sqlSession1.close();

    // 重现创建一个 SqlSessionFactory 对象,为了验证:二级缓存时基于 SqlSessionFactory 的
    Reader resourceAsReader = Resources.getResourceAsReader("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory1 = new SqlSessionFactoryBuilder().build(resourceAsReader);
    resourceAsReader.close();
    SqlSession sqlSession2 = sqlSessionFactory1.openSession();
    SysUserMapper mapper2 = sqlSession2.getMapper(SysUserMapper.class);
    System.err.println("执行第三遍查询~~~~");
    mapper2.selectById(1006);
}

执行此测试方法,控制台打印如下:

执行第一遍查询~~~~
// 第一次查询,未命中缓存 0.0 
DEBUG [main] - Cache Hit Ratio [com.mybatis.simple.mapper.SysUserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1006(Integer)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1006, 孙悟空, 123456, SWK@xiyouji.com, <<BLOB>>, <<BLOB>>, 2020-06-15 07:46:06
DEBUG [main] - <==      Total: 1
重新启动一个 sqlSession ~~~~
执行第二遍查询~~~~
// 第二次查询,命中缓存 0.5
DEBUG [main] - Cache Hit Ratio [com.mybatis.simple.mapper.SysUserMapper]: 0.5
执行第三遍查询~~~~
// 第三次查询,由于 SqlSessionFactory 对象为新创建的,故此,二级缓存未命中;结论:MyBatis 默认二级缓存生命周期基于 SqlSessionFactory 而存在
DEBUG [main] - Cache Hit Ratio [com.mybatis.simple.mapper.SysUserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1006(Integer)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1006, 孙悟空, 123456, SWK@xiyouji.com, <<BLOB>>, <<BLOB>>, 2020-06-15 07:46:06
DEBUG [main] - <==      Total: 1

Process finished with exit code 0

我们看到,在执行第一遍查询时,首先查询二级缓存,缓存未命中,打印值为:0.0,回去查询数据库,查询完后将结果存入分别存入一级、二级缓存,当我们完成第一遍查询后,关闭 session ,由于一级缓存是基于 session 存在的,所以,**再次打开一个 session 时,原一级缓存失效,执行第二遍查询,命中缓存,控制台打印缓存命中率为:0.5;**因为共查询了两次,第一次未命中,第二次命中。执行完第二次查询,我们关闭 session ,重新创建一个 SqlSessionFactory 对象,再次执行查询,可以看到,控制台打印处未命中二级缓存,命中率打印:0.0;重新从查询数据库。

下一篇来说说 《MyBatis 的 基于 JVM 虚拟机存储的 Ehcache 缓存》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值