Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析

1 Mybatis框架的缓存模块

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。Mybatis框架中的缓存分为一级缓存和二级缓存,三级缓存基本都要借助自定义缓存或第三方服务来进行实现。但本质上是一样的,都是借助Cache接口实现的。缓存模块在Mybatis的源码结构中是在 org.apache.ibatis.cache包下面存放着的, 如下图:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第1张图片

2 Cache接口

Cache接口是缓存模块中最核心的接口, Mybatis框架中的实现类如下图所示,起始核心的实现类就只有一个, 就是
PerpetualCache类。
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第2张图片
其他的实现类都是通过装饰者模式对PerpetualCache类的功能扩展和增强, 有兴趣可以自行查看源码。

2.1 PerpetualCache实现类的基本实现

核心方法都比较简单,都是一些很容易理解的方法,说白了就是对Map集合基本操作的封装。
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第3张图片

2.2 Mybatis中缓存机制和属性

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

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML
映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

2.3 Mybatis中缓存属性说明

这些属性可以通过 cache 元素的属性来修改。比如:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

翻译一下上面这行代码:

    1. eviction属性代表缓存清除策略

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

Mybatis默认清除策略是 LRU

    1. flushInterval属性代表缓存刷新时间间隔

刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    1. size属性代表缓存对象数量

引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024

    1. readOnly属性代表缓存对象是否只读

(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。
因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。
速度上会慢一些,但是更安全,因此默认值是 false

注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的
insert/delete/update 语句时,缓存会获得更新。

3 Cache装饰器

3.1 为什么Myabtis框架要提供那么多的缓存装饰器

首先通过本文第2小节我们已经知道了Mybatis框架提供了一个Cache接口,同时提供了一个基本的缓存实现PerpetualCache,基本的缓存功能都已经提供了,但是这里有没有什么问题?

这里直接抛出几个问题, 大家思考一下:

  • 问题1: 你说缓存清除策略有好多种,如果我现在不想用LRU策略,我想改成其他的策略,你怎么让我通过简单的配置就能实现?
  • 问题2: 高并发的情况下, 怎么保证缓存操作的安全?
  • 问题3: 在开启事务的情况下,因为事务中可能执行的一组SQL指令,每个指令可能都会涉及到缓存操作,怎么保证缓存的批量操作及安全性?
  • 问题4: 缓存毕竟是在堆中开辟的内存空间,会消耗内存, 当内存占用超过一定比例我想把数据持久化到磁盘,怎么操作?
  • 问题5:在缓存的操作时我希望通过日志文件或者控制台打印的方式看到缓存操作的结果,比如缓存命中的结果,存入缓存的结果等等信息,该怎么实现?

这些问题大家想想,如果只依赖基础的PerpetualCache实现类, 能帮我们实现么, 肯定是不能的,需要我们自己去实现么? 肯定也是不需要的, Mybatis框架的设计者就这些问题结合设计模式为我们提供了现成的解决方案,就是一下这些缓存装饰器, 我们可以通过多个组合使用来实现我们在实际业务中的一些特殊的需求。

缓存实现类 实现类说明 装饰缓存属性条件 主要功能和作用
基本缓存 缓存基本实现类 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类
LruCache LRU策略的缓存 eviction=“LRU”(默认) 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use)
FifoCache FIFO策略的缓存 eviction=“FIFO” 当缓存到达上限时候,删除最先入队的缓存
SoftCacheWeakCache eviction="SOFT"eviction=“WEAK” 带清理策略的缓存 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference
LoggingCache 带日志功能的缓存 Base 比如:输出缓存命中率
SynchronizedCache 同步缓存 Base 基于synchronized关键字实现,解决并发问题
BlockingCache 阻塞缓存 blocking=true 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现
SerializedCache readOnly=false(默认) 支持序列化的缓存 将对象序列化以后存到缓存中,取出时反序列化
ScheduledCache 定时调度的缓存 flushInterval不为空 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存
TransactionalCache 事务缓存 在TransactionalCacheManager中用Map维护对应关系 在二级缓存中使用,可一次存入多个缓存,移除多个缓存

下面通过SynchronizedCache实现类来进行说明,

3.2 SynchronizedCache

/*
 *    Copyright 2009-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

import org.apache.ibatis.cache.Cache;

/**
 * @author Clinton Begin
 * 这个实现类的作用就是处理在并发缓存操作的安全问题, 
 * 所有的方法都加了synchronized关键字来保证线程安全
 */
public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

}

通过源码我们能够发现,SynchronizedCache本质上就是在我们操作缓存数据的实际调用方法上加了synchronized 同步锁保证了线程安全。其他具体实现类,大家可以自行查阅源码。

4 Mybatis框架中缓存应用

4.1 框架启动缓存初始化

4.1.1 缓存配置解析的几个相关点介绍

配置文件中缓存相关的配置参数解析
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第4张图片
Myabtis框架中一级缓存和二级缓存默认是开启的
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第5张图片
缓存默认的作用域是Session
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第6张图片
Configuration初始化的时候会为我们的各种Cache实现类完成别名注册
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第7张图片

4.1.2 缓存对象的创建及初始化过程

解析全局配置文件mybatis-config.xml时会解析cacheEnabled属性并赋值给Configuration类的cacheEnabled属性, 默认值为true
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第8张图片
如果你启动了二级缓存的配置, 那么在解析*Mapper.xml配置文件时,也会解析响应的cache属性,创建Cache对象并设置到Configuration类的caches属性中区,源码中具体实现见下图:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第9张图片
上图中的最后一行builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);调用了MapperBuilderAssistant类中的useNewCache()方法, 具体代码实现细节见下图, 也很简单:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第10张图片

4.1.3 缓存初始化流程图

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第11张图片

4.2 一级缓存

首先需要明白的是我们需要缓存的对象是什么? 基本上就可以猜测出来我们需要在框架的那个具体环节进行处理。

通常我们呢需要缓存的就是我们从数据库中匹配出来的数据,所以这个缓存的操作一定是发生在SQL语句执行阶段的, 在Mybatis框架中专门负责语句执行的就是Executor, 所有我们呢只需要在Executor的实现类中查找关于缓存的处理逻辑代码实现即可。

一级缓存也叫本地缓存(Local Cache)。

MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。

MyBatis的一级缓存是默认开启的,不需要做任何的配置。

如果我们需要关闭一级缓存, 只需要修改Configuration中localCacheScope属性的取值为STATEMENT即可。

4.2.1 一级缓存什么时候创建

在我们的Executor对象创建的时候, 我们的一级缓存localCache对象就已经创建了,Executor对象又是什么时候创建的,是在SqlSession对象创建的时候完成的创建。验证流程如下图:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第12张图片
一级缓存对象是在BaseExecutor的构造器执行的时候进行创建的, BaseExecutor的构造器是在new SimpleExecutor()的时候被调用的,Configuration类的newExecutor方法中创建了SimpleExecutor对象,
而Configuration类的newExecutor方法又在factory.openSession()方法执行时被调用。
所以结论就是在我们创建sqlSession对象完成的时候, 一级缓存对象就已经准备就绪了。

4.2.2 一级缓存如何关闭

Mybatis框架中提供了关闭一级缓存的示例代码,在BaseExecutor类的query()方法中,如下:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第13张图片

4.2.3 一级缓存的具体实现

一级缓存的实现是在BaseExecutor执行器实现类中实现的,具体代码如下:

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 执行查询方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
  
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    // 在执行查询语句之前,需要确保执行器是正常开启的,否则抛异常
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // flushCache="true"时,即使是查询操作也要先清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      // 防止递归查询重复处理缓存
      queryStack++;
      // 查询一级缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 一级缓存是否命中
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 如果没有命中,从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 如果当前本地缓存的作用域设置为STATEMENT,则将一级缓存关闭(清空相当于关闭操作)
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

4.2.3 一级缓存的实现流程图

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第14张图片

4.2.4 一级缓存的测试验证

验证思路, 根据上面的流程图设计, 如果一级缓存命令, 就不会执行数据库查询操作, 那么查询语句在控制台肯定就只会输出一次, 同时两次查询都可以查询出结果。按照这个思路我们写一个单元测试来验证一下:

1)验证一同一个SqlSession会话中多次调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            System.out.println("------------------------------------------");
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

验证结果
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第15张图片
验证结论:一级缓存只在同一sqlSession会话内的相同查询(方法相同、输入参数相同)才生效。

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }

验证结果
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第16张图片
验证结论:一级缓存的作用范围是"Session",只在Session会话级别才生效

4.3 二级缓存

二级缓存是用来解决一级缓存不能跨会话(SqlSession)共享的问题的,范围是Mapper级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

Mybatis框架中设计的二级缓存默认也是开启的, 但是我们在应用层调用时, 需要进行一些配置才可以生效。

同样我们通过缓存的作用范围再来反推一下, 二级缓存应该是发生在什么时候, 二级缓存的默认的作用范围是在Statement, 必然也是在Executor执行的时候才会进行缓存操作。

同时应为全局的cacheEnabled属性默认为true, 所以在创建Executor对象的时候会被CachingExecutor装饰, 那么二级缓存相关的操作大概率是在CachingExecutor类中实现的,下面我们到源码中验证一下

4.3.1 二级缓存生效配置

第一步:在全局配置文件mybatis-config.xml文件的settings标签对中开启全局配置二级缓存开启的配置

    <settings>
        
        <setting name="cacheEnabled" value="true" />
    settings>

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第17张图片
第二步:在mapper映射配置文件**Mapper.xml文件的增加如下配置:

<mapper namespace="com.kkarma.mapper.LibBookMapper">
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true" />	
mapper>

4.3.2 二级缓存源码解析

4.3.2.1 二级缓存对象如何存放

Myabtis框架中对于缓存的存储都是在Configuration类中的caches属性中存储的。通过CacheKey进行一级和二级缓存的区分
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第18张图片

4.3.2.2 二级缓存对象初始化过程

*Mapper.xml文件中的解析顺序如下:
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第19张图片
二级缓存对象是在**Mapper.xml文件解析的时候进行初始化, 因为如果开启二级缓存,*Mapper.xml文件中势必会配置cache标签,会先解析cache标签为当前mapper.xml文件的命名空间创建一个Cache对象。
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第20张图片
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第21张图片
在创建mappedStatement对象时, 会将单前命名空间中解析出来的二级缓存对象设置到当前mappedStatement对象中去。
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第22张图片
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第23张图片

4.3.2.3 二级缓存的具体实现

二级缓存的操作是在CachingExecutor中完成的,

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存KEY
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取当前mapper接口对应的缓存对象, 这里就是二级缓存
    // 二级缓存作为MappedStatement对象的一个属性,之前我们已经解析过, mapper映射文件解析之后会生成对应的MappedStatement对象
    // 所以二级缓存肯定是在MappedStatement创建的时候进行的初始化,验证一下
    Cache cache = ms.getCache();
    if (cache != null) {
      // 如果当前方法需要在执行之前刷新缓存, 这里就执行刷新缓存操作
      flushCacheIfRequired(ms);
      // 如果当前mappedStatement对象启用缓存并且未设置resultHandler
      if (ms.isUseCache() && resultHandler == null) {
        // 这里是排除存储过程语句执行的干扰
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
          // 从二级缓存中查询结果
        List<E> list = (List<E>) tcm.getObject(cache, key);
        // 如果缓存没命中,从数据库中进行数据查询
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 将查询结果设置到二级缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        // 返回查询结果为调用者
        return list;
      }
    }
    // 如果当前mappedStatement对象未启用缓存或设置了resultHandler
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

4.3.3 如何关闭二级缓存

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第24张图片
**加粗样式**
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第25张图片
那么如果关闭二级缓存呢?
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第26张图片

4.3.3 二级缓存的测试验证

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

验证结果
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第27张图片

验证结论:二级缓存的作用范围是"statement",不同的sqlSession会话针对与同一个mapper命名空间下的方法查询都会走二级缓存。

注意事项
在事务提交之前,并不会真正存储到二级缓存,而是先存储到一个临时属性,等事务提交之后才会真正存储到二级缓存。
这么做的目的就是防止脏读。
因为假如你在一个事务中修改了数据,然后去查询,这时候直接缓存了,那么假如事务回滚了呢?所以这里会先临时存储一下。
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第28张图片
二级缓存的操作最终都是借助TransactionalCache装饰器来实现的。因为二级缓存需要开启事务。
具体代码请自行查看TransactionalCache类的实现

4.4 三级缓存

4.4.1 三级缓存概述

三级缓存一般都是自定义缓存。分布式缓存框架:我们系统为了提高系统并发和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache,caffeine缓存框架,这里演示caffeine和redis缓存框架来进行实现:

4.4.2 借助mybatis-caffeine实现三级缓存

这里使用的是mybatis-encache框架来实现:

项目地址: https://github.com/mybatis/caffeine-cache

文档地址:https://mybatis.org/caffeine-cache/

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.2.1 mybatis-caffeine三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency>
   <groupId>org.mybatis.cachesgroupId>
   <artifactId>mybatis-caffeineartifactId>
   <version>1.0.0version>
 dependency>

第二步:开启二级缓存
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第29张图片
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第30张图片

第三步:将*Mapper.xml文件中的二级缓存类型替换成CaffeineCache

<mapper namespace="org.kkarma.mapper.LibBookMapper">
  <cache type="org.mybatis.caches.redis.CaffeineCache" />
mapper>
4.4.2.2 三级缓存测试验证

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第31张图片

4.4.3 借助mybatis-redis实现三级缓存

这里使用的是mybatis-redis框架来实现:

项目地址: https://github.com/mybatis/redis-cache

文档地址:http://mybatis.org/redis-cache/index.html

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.3.1 mybatis-redis三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency>
   <groupId>org.mybatis.cachesgroupId>
   <artifactId>mybatis-redisartifactId>
   <version>1.0.0-beta2version>
 dependency>

第二步:开启二级缓存
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第32张图片
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第33张图片
第三步:将*Mapper.xml文件中的二级缓存l类型替换成RedisCache

<mapper namespace="org.kkarma.mapper.LibBookMapper">
  <cache type="org.mybatis.caches.redis.RedisCache" />
mapper>

第四步:在resoure目录下增加redis连接的相关配置文件redis.properties,文件内容如下:

host=***.***.***.***
port=****
password=********
databse=*

Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第34张图片

4.4.3.2 三级缓存测试验证

验证代码

public static void main(String[] args) {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory factory = factoryBuilder.build(ins);
            SqlSession sqlSession = factory.openSession(true);
            SqlSession sqlSession1 = factory.openSession(true);
            LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
            LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);
            List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            // sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据
            // 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中
            sqlSession.commit();
            System.out.println("------------------------------------------");
            libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);
            libBooks.forEach(System.out::println);
            sqlSession.close();
            sqlSession1.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

查询缓存中是否存在数据
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第35张图片
Mybatis框架源码笔记(五)之Mybatis框架缓存机制原理解析_第36张图片

你可能感兴趣的:(Java,Mybatis,Spring全家桶,mybatis,缓存,学习)