http://denger.iteye.com/blog/1126423
缓存概述 - 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;
- 一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
- 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
- MyBatis 的缓存采用了delegate机制 及 装饰器模式设计,当put、get、remove时,其中会经过多层 delegate cache 处理,其Cache类别有:BaseCache(基础缓存)、EvictionCache(排除算法缓存) 、DecoratorCache(装饰器缓存):BaseCache         :为缓存数据最终存储的处理类,默认为 PerpetualCache,基于Map存储;可自定义存储处理,如基于EhCache、Memcached等;
          EvictionCache    :当缓存数量达到一定大小后,将通过算法对缓存数据进行清除。默认采用 Lru 算法(LruCache),提供有 fifo 算法(FifoCache)等; 
          DecoratorCache:缓存put/get处理前后的装饰器,如使用 LoggingCache 输出缓存命中日志信息、使用 SerializedCache 对 Cache的数据 put或get 进行序列化及反序列化处理、当设置flushInterval(默认1/h)后,则使用 ScheduledCache 对缓存数据进行定时刷新等。- 一般缓存框架的数据结构基本上都是 Key-Value 方式存储,MyBatis 对于其 Key 的生成采取规则为:[hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。
- 对于并发 Read/Write 时缓存数据的同步问题,MyBatis 默认基于 JDK/concurrent中的ReadWriteLock,使用ReentrantReadWriteLock 的实现,从而通过 Lock 机制防止在并发 Write Cache 过程中线程安全问题。
源码剖解 接下来将结合 MyBatis 序列图进行源码分析。在分析其Cache前,先看看其整个处理过程。 执行过程: ① 通常情况下,我们需要在 Service 层调用 Mapper Interface 中的方法实现对数据库的操作,上述根据产品 ID 获取 Product 对象。 ② 当调用 ProductMapper 时中的方法时,其实这里所调用的是 MapperProxy 中的方法,并且 MapperProxy已经将将所有方法拦截,其具体原理及分析,参考 MyBatis+Spring基于接口编程的原理分析,其 invoke 方法代码为:
① 通常情况下,我们需要在 Service 层调用 Mapper Interface 中的方法实现对数据库的操作,上述根据产品 ID 获取 Product 对象。 ② 当调用 ProductMapper 时中的方法时,其实这里所调用的是 MapperProxy 中的方法,并且 MapperProxy已经将将所有方法拦截,其具体原理及分析,参考 MyBatis+Spring基于接口编程的原理分析,其 invoke 方法代码为:Java代码  

- //当调用 Mapper 所有的方法时,将都交由Proxy 中的 invoke 处理:  
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
-     try {  
-       if (!OBJECT_METHODS.contains(method.getName())) {  
-         final Class declaringInterface = findDeclaringInterface(proxy, method);  
-         // 最终交由 MapperMethod 类处理数据库操作,初始化 MapperMethod 对象  
-         final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);  
-         // 执行 mapper method,返回执行结果   
-         final Object result = mapperMethod.execute(args);  
-         ....  
-         return result;  
-       }  
-     } catch (SQLException e) {  
-       e.printStackTrace();  
-     }  
-     return null;  
-   }  
Java代码  

- public Object execute(Object[] args) throws SQLException {  
-     Object result;  
-     // 根据不同的操作类别,调用 DefaultSqlSession 中的执行处理  
-     if (SqlCommandType.INSERT == type) {  
-       Object param = getParam(args);  
-       result = sqlSession.insert(commandName, param);  
-     } else if (SqlCommandType.UPDATE == type) {  
-       Object param = getParam(args);  
-       result = sqlSession.update(commandName, param);  
-     } else if (SqlCommandType.DELETE == type) {  
-       Object param = getParam(args);  
-       result = sqlSession.delete(commandName, param);  
-     } else if (SqlCommandType.SELECT == type) {  
-       if (returnsList) {  
-         result = executeForList(args);  
-       } else {  
-         Object param = getParam(args);  
-         result = sqlSession.selectOne(commandName, param);  
-       }  
-     } else {  
-       throw new BindingException("Unkown execution method for: " + commandName);  
-     }  
-     return result;  
-   }  
Java代码  

- public Object selectOne(String statement, Object parameter) {  
-     List list = selectList(statement, parameter);  
-     if (list.size() == 1) {  
-       return list.get(0);  
-     }   
-     ...  
- }  
-   
- public List selectList(String statement, Object parameter, RowBounds rowBounds) {  
-     try {  
-       MappedStatement ms = configuration.getMappedStatement(statement);  
-       // 如果启动用了Cache 才调用 CachingExecutor.query,反之则使用 BaseExcutor.query 进行数据库查询   
-       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
-     } catch (Exception e) {  
-       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
-     } finally {  
-       ErrorContext.instance().reset();  
-     }  
- }  
 BaseExecutor: 基础执行器抽象类。实现一些通用方法,如createCacheKey 之类。并且采用 模板模式 将具体的数据库操作逻辑(doUpdate、doQuery)交由子类实现。另外,可以看到变量 localCache: PerpetualCache,在该类采用 PerpetualCache 实现基于 Map 存储的一级缓存,其 query 方法如下:
 BaseExecutor: 基础执行器抽象类。实现一些通用方法,如createCacheKey 之类。并且采用 模板模式 将具体的数据库操作逻辑(doUpdate、doQuery)交由子类实现。另外,可以看到变量 localCache: PerpetualCache,在该类采用 PerpetualCache 实现基于 Map 存储的一级缓存,其 query 方法如下:Java代码  

- public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
-     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
-     // 执行器已关闭  
-     if (closed) throw new ExecutorException("Executor was closed.");  
-     List list;  
-     try {  
-       queryStack++;   
-       // 创建缓存Key  
-       CacheKey key = createCacheKey(ms, parameter, rowBounds);   
-       // 从本地缓存在中获取该 key 所对应 的结果集  
-       final List cachedList = (List) localCache.getObject(key);   
-       // 在缓存中找到数据  
-       if (cachedList != null) {   
-         list = cachedList;  
-       } else { // 未从本地缓存中找到数据,开始调用数据库查询  
-         //为该 key 添加一个占位标记  
-         localCache.putObject(key, EXECUTION_PLACEHOLDER);   
-         try {  
-           // 执行子类所实现的数据库查询 操作  
-           list = doQuery(ms, parameter, rowBounds, resultHandler);   
-         } finally {  
-           // 删除该 key 的占位标记  
-           localCache.removeObject(key);  
-         }  
-         // 将db中的数据添加至本地缓存中  
-         localCache.putObject(key, list);  
-       }  
-     } finally {  
-       queryStack--;  
-     }  
-     // 刷新当前队列中的所有 DeferredLoad实例,更新 MateObject  
-     if (queryStack == 0) {   
-       for (DeferredLoad deferredLoad : deferredLoads) {  
-         deferredLoad.load();  
-       }  
-     }  
-     return list;  
-   }  
 流程为: 从二级缓存中进行查询 -> [如果缓存中没有,委托给 BaseExecutor] -> 进入一级缓存中查询 -> [如果也没有] -> 则执行 JDBC 查询,其 query 代码如下:
流程为: 从二级缓存中进行查询 -> [如果缓存中没有,委托给 BaseExecutor] -> 进入一级缓存中查询 -> [如果也没有] -> 则执行 JDBC 查询,其 query 代码如下:Java代码  

- public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
-     if (ms != null) {  
-       // 获取二级缓存实例  
-       Cache cache = ms.getCache();  
-       if (cache != null) {  
-         flushCacheIfRequired(ms);  
-         // 获取 读锁( Read锁可由多个Read线程同时保持)  
-         cache.getReadWriteLock().readLock().lock();  
-         try {  
-           // 当前 Statement 是否启用了二级缓存  
-           if (ms.isUseCache()) {  
-             // 将创建 cache key 委托给 BaseExecutor 创建  
-             CacheKey key = createCacheKey(ms, parameterObject, rowBounds);  
-             final List cachedList = (List) cache.getObject(key);  
-             // 从二级缓存中找到缓存数据  
-             if (cachedList != null) {  
-               return cachedList;  
-             } else {  
-               // 未找到缓存,很委托给 BaseExecutor 执行查询  
-               List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);  
-               tcm.putObject(cache, key, list);  
-               return list;  
-             }  
-           } else { // 没有启动用二级缓存,直接委托给 BaseExecutor 执行查询   
-             return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
-           }  
-         } finally {  
-           // 当前线程释放 Read 锁  
-           cache.getReadWriteLock().readLock().unlock();  
-         }  
-       }  
-     }  
-     return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
- }  
Java代码  

- 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);  
-     }  
-   }  
Java代码  

- 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 的线程只能等待其释放才能去持有。 其代码实现:
 可见,所有构建的缓存实例已经通过责任链方式将其串连在一起,各 Cache 各负其责、依次调用,直到缓存数据被 Put 至 基础缓存实例中存储。 Cache 实例解剖: 实例类:SynchronizedCache 说   明:用于控制 ReadWriteLock,避免并发时所产生的线程安全问题。 解   剖: 对于 Lock 机制来说,其分为 Read 和 Write 锁,其 Read 锁允许多个线程同时持有,而 Write 锁,一次能被一个线程持有,如果当 Write 锁没有释放,其它需要 Write 的线程只能等待其释放才能去持有。 其代码实现:Java代码  

- public void putObject(Object key, Object object) {  
-     acquireWriteLock();  // 获取 Write 锁  
-     try {  
-       delegate.putObject(key, object); // 委托给下一个 Cache 执行 put 操作  
-     } finally {  
-       releaseWriteLock(); // 释放 Write 锁  
-     }  
-   }  
Java代码  

- public Object getObject(Object key) {  
-     acquireReadLock();  
-     try {  
-       return delegate.getObject(key);  
-     } finally {  
-       releaseReadLock();  
-     }  
-   }  
Java代码  

- 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;  
- }  
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);  
-   }  
Java代码  

- 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);  
-   }  
Xml代码  

- <cache eviction="LRU" type="com.xx.core.plugin.mybatis.MemcachedCache" />  
Shell代码  

- memcached -c 2000 -p 11211 -vv -U 0 -l 192.168.1.2 -v  
Java代码  

- @Test  
-     public void testSelectById() {  
-         Long pid = 100L;  
-   
-         Product dbProduct = productMapper.selectByID(pid);  
-         Assert.assertNotNull(dbProduct);  
-   
-         Product cacheProduct = productMapper.selectByID(pid);  
-         Assert.assertNotNull(cacheProduct);  
-   
-         productMapper.updateName("IPad", pid);  
-   
-         Product product = productMapper.selectByID(pid);  
-         Assert.assertEquals(product.getName(), "IPad");  
-     }  
 看上去没什么问题~ OK了。
 看上去没什么问题~ OK了。posted on 2014-05-30 13:53 
SIMONE 阅读(617) 
评论(1)  编辑  收藏  所属分类: 
JAVA