Mybatis基础模块-缓存管理

文章目录

  • 1. 模块简介
    • 1.1 Cache接口
    • 1.2 PerpetualCache
    • 1.3 BlockingCache
    • 1.4 其他
  • 2. 相关代码
  • 3. 一级缓存
  • 4. 二级缓存
  • 5. 相关解析
    • 5.1 缓存对象注册别名
    • 5.2 参数默认设置
  • 6. 执行sql过程中如何使用缓存的

1. 模块简介

mybatis的缓存模块在org.apache.ibatis.cache包下,包括如下接口和类
Mybatis基础模块-缓存管理_第1张图片

1.1 Cache接口

Cache是核心实现接口。每个namespace会创建一个Cache实例,Cache的实现必须要有一个String类型的id作为参数的构造函数,mybatis将传namespace作为id。


public interface Cache {

  /**
   * 缓存对象的 ID
   * @return The identifier of this cache
   */
  String getId();

  /**
   * 向缓存中添加数据,一般情况下 key是CacheKey  value是查询结果
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**
   * 根据指定的key,在缓存中查找对应的结果对象
   * @param key The key
   * @return The object stored in the cache.
   */
  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.
   *   删除key对应的缓存数据
   *
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance.
   * 清空缓存
   */
  void clear();

  /**
   * Optional. This method is not called by the core.
   * 缓存的个数。
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();

  /**
   * Optional. As of 3.2.6 this method is no longer called by the core.
   * 

* Any locking needed by the cache must be provided internally by the cache provider. * 获取读写锁 * @return A ReadWriteLock */ default ReadWriteLock getReadWriteLock() { return null; } }

1.2 PerpetualCache

PerpetualCache是Cache的简单实现,持有一个HashMap变量,作为缓存。

public class PerpetualCache implements Cache {

  private final String id; // Cache 对象的唯一标识

  // 用于记录缓存的Map对象
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    // 只关心ID
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    // 只关心ID
    return getId().hashCode();
  }

}

1.3 BlockingCache

decorators包下类,使用装饰器模式,都继承Cache接口,在PerpetualCache的基础上,扩展功能。

见名知意,这是一个阻塞的缓存,保证只有一个线程能能查询到指定key的缓存。

public class BlockingCache implements Cache {

  private long timeout; // 阻塞超时时长
  private final Cache delegate; // 被装饰的底层 Cache 对象
  // 每个key 都有对象的 ReentrantLock 对象
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    // 被装饰的 Cache 对象
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

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

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

  @Override
  public void putObject(Object key, Object value) {
    try {
      // 执行 被装饰的 Cache 中的方法
      delegate.putObject(key, value);
    } finally {
      // 释放锁
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    acquireLock(key); // 获取锁
    Object value = delegate.getObject(key); // 获取缓存数据
    if (value != null) { // 有数据就释放掉锁,否则继续持有锁
      releaseLock(key);
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }

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

  private ReentrantLock getLockForKey(Object key) {
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
  }

  private void acquireLock(Object key) {
    Lock lock = getLockForKey(key);
    if (timeout > 0) {
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {
      lock.lock();
    }
  }

  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }
}

1.4 其他

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

2. 相关代码

 @Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Integer param = 1;
        User user = mapper.selectUserById(param);
        System.out.println(user);

        System.out.println("------------间隔---------------");
        // 第二次查询
        User user2 = mapper.selectUserById(param);
        System.out.println(user2);

        // 5.关闭会话
        sqlSession.close();
    }

3. 一级缓存

一级缓存(local cache)的作用域是同一个SqlSession。默认开启,如果要关闭,设置参数localCacheScope:STATEMENT。
官方文档:https://mybatis.org/mybatis-3/configuration.html#settings。
在这里插入图片描述

4. 二级缓存

作用域:

  • 同一个mapper的namespace,同一个namespace中查询sql可以从缓存中命中。
  • 跨sqlSession,不同的SqlSession可以从二级缓存中命中

如何开启

  • 在映射文件中,添加标签
  • 在全局配置文件中,设置cacheEnabled参数为true,默认已开启。

注意:

  • 由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,所以第一个sqlSession必须先关闭。
  • 二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。

5. 相关解析

5.1 缓存对象注册别名

在初始化XMLConfigBuilder的构造函数,初始化Configuration对象的时候创建别名。
Mybatis基础模块-缓存管理_第2张图片

5.2 参数默认设置

在解析完settings标签之后,在settingsElement(Properties props) 方法中设置参数。
Mybatis基础模块-缓存管理_第3张图片
cacheEnabled默认值:true
localCacheScope默认值:SESSION。

解析映射文件时候:configurationElement(XNode context)
Mybatis基础模块-缓存管理_第4张图片
具体参数的解析:
Mybatis基础模块-缓存管理_第5张图片
具体可以查看官方文档:https://mybatis.org/mybatis-3/sqlmap-xml.html
Mybatis基础模块-缓存管理_第6张图片

继续:

Mybatis基础模块-缓存管理_第7张图片
Mybatis基础模块-缓存管理_第8张图片

然后我们可以发现 如果存储 cache 标签,那么对应的 Cache对象会被保存在 currentCache 属性中。

进而在 Cache 对象 保存在了 MapperStatement 对象的 cache 属性中。

6. 执行sql过程中如何使用缓存的

CachingExecutor的方法,这里查询二级缓存

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    // cache 对象是在哪里创建的?  XMLMapperBuilder类 xmlconfigurationElement()
    // 由  标签决定  控制二级缓存的开关有两个 一个是 CacheEnabled 全局配置文件中的settings 配置  --》 装饰执行器 Executor  CachingExecutor
    //                                         一个是 cache 标签 映射文件中的配置  创建 Cache 对象
    if (cache != null) {
      // flushCache="true" 清空一级二级缓存 >>
      flushCacheIfRequired(ms);
      // 在 select 标签中 配置了 useCache 属性
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 获取二级缓存
        // 缓存通过 TransactionalCacheManager、TransactionalCache 管理
        @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;
      }
    }
    // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

BaseExecutor的查询方法,这里查询一级缓存。


  /**
   * queryStack 和 占位符的作用
   * 1.查询 学校    正常查询 --> 放入一级缓存
   * 2.查询  学校的学生  正常查询 --> 放入一级缓存
   * 3.查询  学生的学校  延迟加载,从缓存中获取
   */
  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 异常体系之 ErrorContext
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // flushCache="true"时,即使是查询,也清空一级缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 防止递归查询重复处理缓存
      queryStack++;
      // 查询一级缓存
      // ResultHandler 和 ResultSetHandler的区别  一级缓存
      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();
      // 如果LocalCacheScope的值设置为 STATEMENT 则一级缓存失效
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  • 一级缓存在BaseExecutor的localCache变量中,
  • 二级缓存在Configuration的变量caches,同时每个MapperStatement都有自己的cache。

你可能感兴趣的:(#,MyBatis,mybatis,缓存)