mybatis 之 缓存

Posted better_hui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis 之 缓存相关的知识,希望对你有一定的参考价值。

目录

一、简介

         PerpetualCache

增强的缓存功能

分类

二、原理

1、PerpetualCache源码

2、LRUCache ,装饰器增强的缓存

3、CacheKey

4、一级缓存、二级缓存

三、一级缓存

访问&创建

删除

四、二级缓存

开启

命名空间划分

访问&更新

删除


一、简介

在缓存中有需要的数据,就不用从数据库中获取转而从缓存中获取,这样就大大提高了系统性能。

PerpetualCache

mybatis顶级缓存接口cache , 有且仅有一个默认实现PerpetualCache,其提供了基础的缓存服务

增强的缓存功能

缓存需要提供更多额外的功能,比如回收策略、日志记录、定时刷新等,这里我们使用了装饰器模式,比如:LruCache 、LoggingCache等

分类

缓存可以大体归为三类:基本类缓存、淘汰算法缓存、装饰器缓存。

https://mmbiz.qpic.cn/mmbiz_png/07BicZywOVtkPE6AqIbiavgwAs3QPdcXqbwShgNrel4IwLf4urQ5jZu30wetWXkhD2WQCopicJC2Y7XQtpqlQP4Dg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

二、原理

1、PerpetualCache源码

基础功能类缓存,内部使用HashMap实现。

public class PerpetualCache implements Cache {
​
  private String id;
    
  //缓存的容器
  private Map<Object, Object> cache = new HashMap<Object, Object>();
​
  public PerpetualCache(String id) {
    this.id = id;
  }
​
  public String getId() {
    return id;
  }
​
  public int getSize() {
    return cache.size();
  }
​
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }
​
  public Object getObject(Object key) {
    return cache.get(key);
  }
  // 移除缓存想
  public Object removeObject(Object key) {
    return cache.remove(key);
  }
    //清除缓存
  public void clear() {
    cache.clear();
  }
​
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
​
  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;
    return getId().equals(otherCache.getId());
  }
​
  public int hashCode() {
    if (getId() == null) throw new CacheException("Cache instances require an ID.");
    return getId().hashCode();
  }
​
}

2、LRUCache ,装饰器增强的缓存

是一种具有 LRU(Least recently used,最近最少使用)算法的缓存实现类。

public class LruCache implements Cache {
​
  private final Cache delegate;
  //缓存key的记录 , 用以删除过期的key , 后面使用了LinkedHashMap   
  private Map<Object, Object> keyMap;
  //待删除的key , 删除的时机是新增时 ,如果超过了容量 , 则被动删除  
  private Object eldestKey;
  //看到没,这里传入了一个Cache的实现     
  public LruCache(Cache delegate) {
    this.delegate = delegate;
    //设置缓存大小,并重写了LinkedHashMap删除的方法
    setSize(1024);
  }
​
  @Override
  public String getId() {
    return delegate.getId();
  }
​
  @Override
  public int getSize() {
    return delegate.getSize();
  }
​
  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;
​
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }
​
  @Override
  public void putObject(Object key, Object value) {
    //添加数据到PerPetualCache  
    delegate.putObject(key, value);
    //尝试删除过期的数据  
    cycleKeyList(key);
  }
​
  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }
​
  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }
​
  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }
​
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
​
  private void cycleKeyList(Object key) {
    //刷新当前key  
    keyMap.put(key, key);
    //如果当存在过期的key时 
    //从基础缓存里删除
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }
​
}

3、CacheKey

4、一级缓存、二级缓存

一级缓存是sqlSession级别的 、 二级缓存是 mapper级别的。

图片

三、一级缓存

一级缓存也叫本地缓存(LocalCache) , 是SqlSession级别的 , 绑定在BaseExecutor上,看代码:

public abstract class BaseExecutor implements Executor {
  //省略了其他代码 ,因为sqlSession 是短暂的 , 所以直接使用基础的缓存对象即可    
  protected PerpetualCache localCache;
}    

访问&创建

BaseExecutor.query()为例

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.");
  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();
    }
    deferredLoads.clear(); // issue #601
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache(); // issue #482
    }
  }
  return list;
}

删除

清空一级缓存的方式有:

  • update、insert、delete

  • flushCache="true"

  • commit、rollback

  • LocalCacheScope.STATEMENT

public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) throw new ExecutorException("Executor was closed.");
  //先删除缓存  
  clearLocalCache();
  //再更新数据库  
  return doUpdate(ms, parameter);
}

四、二级缓存

二级缓存是mapper级别的,所以会存在线程共享的问题

public final class MappedStatement {
​
  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;

开启

<configuration>
  <settings>
    <setting name="cacheEnabled" value="true|false" />
  </settings>
</configuration>
​
​
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
      //是否开启二级缓存
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

命名空间划分

namespace="com.tian.mybatis.mappers.UserMapper"
​
namespace="com.tian.mybatis.mappers.RoleMapper"
​
二级缓存是挂在mapper上的,所以不同的命名空间,对应不同的cache空间

访问&更新

CacheingExecutor.query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
        //从缓存获取
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
          //查询数据库
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //回写缓存
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
      }
      return list;
    }
  }
    //没有开启缓存 , 直接查库
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

删除

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //更新前删除缓存
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

以上是关于mybatis 之 缓存的主要内容,如果未能解决你的问题,请参考以下文章

mybatis之缓存模块

mybatis之缓存

mybatis 之缓存机制

MyBatis之缓存

聊聊MyBatis缓存机制

mybatis的缓存机制源码分析之二级缓存解析