一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储做用域为 Session,当 Session flush 或 close 以后,该Session中的全部 Cache 就将清空。java
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不一样在于其存储做用域为 Mapper(Namespace),而且可自定义存储源,如 Ehcache、Hazelcast等。算法
对于缓存数据更新机制,当某一个做用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操做后,默认该做用域下全部 select 中的缓存将被clear。spring
MyBatis 的缓存采用了delegate机制 及 装饰器模式设计,当put、get、remove时,其中会通过多层 delegate cache 处理,其Cache类别有:BaseCache(基础缓存)、EvictionCache(排除算法缓存) 、DecoratorCache(装饰器缓存):sql
通常缓存框架的数据结构基本上都是 Key-Value 方式存储,MyBatis 对于其 CacheKey 的生成采起规则为:数据库
[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。apache
对于并发 Read/Write 时缓存数据的同步问题,MyBatis 默认基于 JDK/concurrent中的ReadWriteLock,使用 ReentrantReadWriteLock 的实现,从而经过 Lock 机制防止在并发 Write Cache 过程当中线程安全问题。缓存
测试 User user=userService.get(55); 安全
2、Executor框架数据结构
解析器:结合mybatis-spring框架,读取spring关于mybatis的配置文件。具体看是否开启缓存(这里指二级缓存),若是开启,生成的执行器为CachingExecutor。 mybatis
动态代理:实现调用mapper接口的时候执行mybatis逻辑
执行器:执行缓存处理逻辑。在这里二级缓存和一级缓存有所区别。
BatchExcutor、ReuseExcutor、 SimpleExcutor: 这几个就没什么好说的了,继承了 BaseExcutor 的实现其 doQuery、doUpdate 等方法,一样都是采用 JDBC 对数据库进行操做;三者区别在于,批量执行、重用 Statement 执行、普通方式执行。具体应用及场景在Mybatis 的文档上都有详细说明。
CachingExecutor: 二级缓存执行器。我的以为这里设计的不错,灵活地使用 delegate机制。其委托执行的类是 BaseExcutor。 当没法从二级缓存获取数据时,一样须要从 DB 中进行查询,因而在这里能够直接委托给 BaseExcutor 进行查询。其大概流程为:
流程为: 从二级缓存中进行查询 -> [若是缓存中没有,委托给 BaseExecutor] -> 进入一级缓存中查询 -> [若是也没有] -> 则执行 JDBC 查询,其 query 代码以下:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); // 当前 Statement 是否启用了二级缓存 if (cache != null) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { // 未找到缓存,很委托给 BaseExecutor 执行查询 list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } // 没有启动用二级缓存,直接委托给 BaseExecutor 执行查询 return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { // 将建立 cache key 委托给 BaseExecutor 建立 return this.delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); }
Cache 委托链构建:
正如最开始的缓存概述所描述道,其缓存类的设计采用 装饰模式,基于委托的调用机制。
缓存实例构建:
缓存实例的构建 ,Mybatis 在解析其 Mapper 配置文件时就已经将该实现初始化,在 org.apache.ibatis.builder.xml.XMLMapperBuilder 类中能够看到:
private void cacheElement(XNode context) throws Exception { if (context != null) { // 基础缓存类型 String type = context.getStringAttribute("type", "PERPETUAL"); Class typeClass = typeAliasRegistry.resolveAlias(type); // 排除算法缓存类型 String eviction = context.getStringAttribute("eviction", "LRU"); Class evictionClass = typeAliasRegistry.resolveAlias(eviction); // 缓存自动刷新时间 Long flushInterval = context.getLongAttribute("flushInterval"); // 缓存存储实例引用的大小 Integer size = context.getIntAttribute("size"); // 是不是只读缓存 boolean readWrite = !context.getBooleanAttribute("readOnly", false); Properties props = context.getChildrenAsProperties(); // 初始化缓存实现 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); } }
如下是 useNewCache 方法实现:
public Cache useNewCache(Class typeClass, Class evictionClass, Long flushInterval, Integer size, boolean readWrite, Properties props) { typeClass = valueOrDefault(typeClass, PerpetualCache.class); evictionClass = valueOrDefault(evictionClass, LruCache.class); // 这里构建 Cache 实例采用 Builder 模式,每个 Namespace 生成一个 Cache 实例 Cache cache = new CacheBuilder(currentNamespace) // Builder 前设置一些从XML中解析过来的参数 .implementation(typeClass) .addDecorator(evictionClass) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .properties(props) // 再看下面的 build 方法实现 .build(); configuration.addCache(cache); currentCache = cache; return cache; } public Cache build() { setDefaultImplementations(); // 建立基础缓存实例 Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); // 缓存排除算法初始化,并将其委托至基础缓存中 for (Class<? extends Cache> decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } // 标准装饰器缓存设置,如LoggingCache之类,一样将其委托至基础缓存中 cache = setStandardDecorators(cache); // 返回最终缓存的责任链对象 return cache; }
最终生成后的缓存实例对象结构:
可见,全部构建的缓存实例已经经过责任链方式将其串连在一块儿,各 Cache 各负其责、依次调用,直到缓存数据被 Put 至 基础缓存实例中存储。
Cache 实例解剖:
实例类:SynchronizedCache
说 明:用于控制 ReadWriteLock,避免并发时所产生的线程安全问题。
解 剖:
对于 Lock 机制来讲,其分为 Read 和 Write 锁,其 Read 锁容许多个线程同时持有,而 Write 锁,一次能被一个线程持有,若是当 Write 锁没有释放,其它须要 Write 的线程只能等待其释放才能去持有。
其代码实现:
其具体原理能够看看 jdk concurrent 中的 ReadWriteLock 实现。
实例类:LoggingCache
说 明:用于日志记录处理,主要输出缓存命中率信息。
解 剖:
说到缓存命中信息的统计,只有在 get 的时候才须要统计命中率:
public Object getObject(Object key) { requests++; // 每调用一次该方法,则获取次数+1 final Object value = delegate.getObject(key); if (value != null) { // 命中! 命中+1 hits++; } if (log.isDebugEnabled()) { // 输出命中率。计算方法为: hits / requets 则为命中率 log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); } return value; }
实例类:SerializedCache
说 明:向缓存中 put 或 get 数据时的序列化及反序列化处理。
解 剖:
序列化在Java里面已是最基础的东西了,这里也没有什么特殊之处:
public void putObject(Object key, Object object) { // PO 类须要实现 Serializable 接口 if (object == null || object instanceof Serializable) { delegate.putObject(key, serialize((Serializable) object)); } else { throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object); } } public Object getObject(Object key) { Object object = delegate.getObject(key); // 获取数据时对 二进制数据进行反序列化 return object == null ? null : deserialize((byte[]) object); }
其 serialize 及 deserialize 代码:
private byte[] serialize(Serializable value) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(value); oos.flush(); oos.close(); return bos.toByteArray(); } catch (Exception var4) { throw new CacheException("Error serializing object. Cause: " + var4, var4); } } private Serializable deserialize(byte[] value) { try { ByteArrayInputStream bis = new ByteArrayInputStream(value); ObjectInputStream ois = new SerializedCache.CustomObjectInputStream(bis); Serializable result = (Serializable)ois.readObject(); ois.close(); return result; } catch (Exception var5) { throw new CacheException("Error deserializing object. Cause: " + var5, var5); } }
实例类:LruCache
说 明:最近最少使用的:移除最长时间不被使用的对象,基于LRU算法。
解 剖:
这里的 LRU 算法基于 LinkedHashMap 覆盖其 removeEldestEntry 方法实现。好象以前看过 XMemcached 的 LRU 算法也是这样实现的。
初始化 LinkedHashMap,默认为大小为 1024 个元素:
public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); // 设置 map 默认大小 } public void setSize(final int size) { // 设置其 capacity 为size, 其 factor 为.75F keyMap = new LinkedHashMap(size, .75F, true) { // 覆盖该方法,当每次往该map 中put 时数据时,如该方法返回 True,便移除该map中使用最少的Entry // 其参数 eldest 为当前最老的 Entry protected boolean removeEldestEntry(Map.Entry eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); //记录当前最老的缓存数据的 Key 值,由于要委托给下一个 Cache 实现删除 } return tooBig; } }; } public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); // 每次 put 后,调用移除最老的 key } // 看看当前实现是否有 eldestKey, 有的话就调用 removeObject ,将该key从cache中移除 private void cycleKeyList(Object key) { keyMap.put(key, key); // 存储当前 put 到cache中的 key 值 if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } public Object getObject(Object key) { keyMap.get(key); // 便于 该 Map 统计 get该key的次数 return delegate.getObject(key); }
实例类:PerpetualCache
说 明:这个比较简单,直接经过一个 HashMap 来存储缓存数据。因此没什么说的,直接看下面的 MemcachedCache 吧。