mybatis 之 缓存
Posted better_hui
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis 之 缓存相关的知识,希望对你有一定的参考价值。
目录
一、简介
在缓存中有需要的数据,就不用从数据库中获取转而从缓存中获取,这样就大大提高了系统性能。
PerpetualCache
mybatis顶级缓存接口cache , 有且仅有一个默认实现PerpetualCache,其提供了基础的缓存服务
增强的缓存功能
缓存需要提供更多额外的功能,比如回收策略、日志记录、定时刷新等,这里我们使用了装饰器模式,比如:LruCache 、LoggingCache等
分类
缓存可以大体归为三类:基本类缓存、淘汰算法缓存、装饰器缓存。
二、原理
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 之 缓存的主要内容,如果未能解决你的问题,请参考以下文章