posts - 495,comments - 227,trackbacks - 0
          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 方法代碼為:
          Java代碼  收藏代碼
          1. //當調用 Mapper 所有的方法時,將都交由Proxy 中的 invoke 處理:  
          2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
          3.     try {  
          4.       if (!OBJECT_METHODS.contains(method.getName())) {  
          5.         final Class declaringInterface = findDeclaringInterface(proxy, method);  
          6.         // 最終交由 MapperMethod 類處理數據庫操作,初始化 MapperMethod 對象  
          7.         final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);  
          8.         // 執行 mapper method,返回執行結果   
          9.         final Object result = mapperMethod.execute(args);  
          10.         ....  
          11.         return result;  
          12.       }  
          13.     } catch (SQLException e) {  
          14.       e.printStackTrace();  
          15.     }  
          16.     return null;  
          17.   }  

          ③其中的 mapperMethod 中的 execute  方法代碼如下: 
          Java代碼  收藏代碼
          1. public Object execute(Object[] args) throws SQLException {  
          2.     Object result;  
          3.     // 根據不同的操作類別,調用 DefaultSqlSession 中的執行處理  
          4.     if (SqlCommandType.INSERT == type) {  
          5.       Object param = getParam(args);  
          6.       result = sqlSession.insert(commandName, param);  
          7.     } else if (SqlCommandType.UPDATE == type) {  
          8.       Object param = getParam(args);  
          9.       result = sqlSession.update(commandName, param);  
          10.     } else if (SqlCommandType.DELETE == type) {  
          11.       Object param = getParam(args);  
          12.       result = sqlSession.delete(commandName, param);  
          13.     } else if (SqlCommandType.SELECT == type) {  
          14.       if (returnsList) {  
          15.         result = executeForList(args);  
          16.       } else {  
          17.         Object param = getParam(args);  
          18.         result = sqlSession.selectOne(commandName, param);  
          19.       }  
          20.     } else {  
          21.       throw new BindingException("Unkown execution method for: " + commandName);  
          22.     }  
          23.     return result;  
          24.   }  
          由于這里是根據 ID 進行查詢,所以最終調用為 sqlSession.selectOne函數。也就是接下來的的 DefaultSqlSession.selectOne 執行; 
          ④ ⑤ 可以在 DefaultSqlSession 看到,其 selectOne 調用了 selectList 方法:
          Java代碼  收藏代碼
          1. public Object selectOne(String statement, Object parameter) {  
          2.     List list = selectList(statement, parameter);  
          3.     if (list.size() == 1) {  
          4.       return list.get(0);  
          5.     }   
          6.     ...  
          7. }  
          8.   
          9. public List selectList(String statement, Object parameter, RowBounds rowBounds) {  
          10.     try {  
          11.       MappedStatement ms = configuration.getMappedStatement(statement);  
          12.       // 如果啟動用了Cache 才調用 CachingExecutor.query,反之則使用 BaseExcutor.query 進行數據庫查詢   
          13.       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
          14.     } catch (Exception e) {  
          15.       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  
          16.     } finally {  
          17.       ErrorContext.instance().reset();  
          18.     }  
          19. }  
          ⑥到這里,已經執行到具體數據查詢的流程,在分析 CachingExcutor.query 前,先看看 MyBatis 中 Executor 的結構及構建過程。 


          執行器(Executor): 
          Executor:  執行器接口。也是最終執行數據獲取及更新的實例。其類結構如下: 
           
          BaseExecutor: 基礎執行器抽象類。實現一些通用方法,如createCacheKey 之類。并且采用 模板模式 將具體的數據庫操作邏輯(doUpdate、doQuery)交由子類實現。另外,可以看到變量 localCache: PerpetualCache,在該類采用 PerpetualCache 實現基于 Map 存儲的一級緩存,其 query 方法如下:
          Java代碼  收藏代碼
          1. public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
          2.     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());  
          3.     // 執行器已關閉  
          4.     if (closed) throw new ExecutorException("Executor was closed.");  
          5.     List list;  
          6.     try {  
          7.       queryStack++;   
          8.       // 創建緩存Key  
          9.       CacheKey key = createCacheKey(ms, parameter, rowBounds);   
          10.       // 從本地緩存在中獲取該 key 所對應 的結果集  
          11.       final List cachedList = (List) localCache.getObject(key);   
          12.       // 在緩存中找到數據  
          13.       if (cachedList != null) {   
          14.         list = cachedList;  
          15.       } else { // 未從本地緩存中找到數據,開始調用數據庫查詢  
          16.         //為該 key 添加一個占位標記  
          17.         localCache.putObject(key, EXECUTION_PLACEHOLDER);   
          18.         try {  
          19.           // 執行子類所實現的數據庫查詢 操作  
          20.           list = doQuery(ms, parameter, rowBounds, resultHandler);   
          21.         } finally {  
          22.           // 刪除該 key 的占位標記  
          23.           localCache.removeObject(key);  
          24.         }  
          25.         // 將db中的數據添加至本地緩存中  
          26.         localCache.putObject(key, list);  
          27.       }  
          28.     } finally {  
          29.       queryStack--;  
          30.     }  
          31.     // 刷新當前隊列中的所有 DeferredLoad實例,更新 MateObject  
          32.     if (queryStack == 0) {   
          33.       for (DeferredLoad deferredLoad : deferredLoads) {  
          34.         deferredLoad.load();  
          35.       }  
          36.     }  
          37.     return list;  
          38.   }  
          BatchExcutorReuseExcutor SimpleExcutor: 這幾個就沒什么好說的了,繼承了 BaseExcutor 的實現其 doQuery、doUpdate 等方法,同樣都是采用 JDBC 對數據庫進行操作;三者區別在于,批量執行、重用 Statement 執行、普通方式執行。具體應用及場景在Mybatis 的文檔上都有詳細說明。 

          CachingExecutor: 二級緩存執行器。個人覺得這里設計的不錯,靈活地使用 delegate機制。其委托執行的類是 BaseExcutor。 當無法從二級緩存獲取數據時,同樣需要從 DB 中進行查詢,于是在這里可以直接委托給 BaseExcutor 進行查詢。其大概流程為: 

          流程為: 從二級緩存中進行查詢 -> [如果緩存中沒有,委托給 BaseExecutor] -> 進入一級緩存中查詢 -> [如果也沒有] -> 則執行 JDBC 查詢,其 query 代碼如下:
          Java代碼  收藏代碼
          1. public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {  
          2.     if (ms != null) {  
          3.       // 獲取二級緩存實例  
          4.       Cache cache = ms.getCache();  
          5.       if (cache != null) {  
          6.         flushCacheIfRequired(ms);  
          7.         // 獲取 讀鎖( Read鎖可由多個Read線程同時保持)  
          8.         cache.getReadWriteLock().readLock().lock();  
          9.         try {  
          10.           // 當前 Statement 是否啟用了二級緩存  
          11.           if (ms.isUseCache()) {  
          12.             // 將創建 cache key 委托給 BaseExecutor 創建  
          13.             CacheKey key = createCacheKey(ms, parameterObject, rowBounds);  
          14.             final List cachedList = (List) cache.getObject(key);  
          15.             // 從二級緩存中找到緩存數據  
          16.             if (cachedList != null) {  
          17.               return cachedList;  
          18.             } else {  
          19.               // 未找到緩存,很委托給 BaseExecutor 執行查詢  
          20.               List list = delegate.query(ms, parameterObject, rowBounds, resultHandler);  
          21.               tcm.putObject(cache, key, list);  
          22.               return list;  
          23.             }  
          24.           } else { // 沒有啟動用二級緩存,直接委托給 BaseExecutor 執行查詢   
          25.             return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
          26.           }  
          27.         } finally {  
          28.           // 當前線程釋放 Read 鎖  
          29.           cache.getReadWriteLock().readLock().unlock();  
          30.         }  
          31.       }  
          32.     }  
          33.     return delegate.query(ms, parameterObject, rowBounds, resultHandler);  
          34. }  
          至此,已經完完了整個緩存執行器的整個流程分析,接下來是對緩存的 緩存數據管理實例進行分析,也就是其 Cache 接口,用于對緩存數據 put 、get及remove的實例對象。 


          Cache 委托鏈構建: 
          正如最開始的緩存概述所描述道,其緩存類的設計采用 裝飾模式,基于委托的調用機制。 
          緩存實例構建: 
          緩存實例的構建 ,Mybatis 在解析其 Mapper 配置文件時就已經將該實現初始化,在 org.apache.ibatis.builder.xml.XMLMapperBuilder 類中可以看到: 
          Java代碼  收藏代碼
          1. private void cacheElement(XNode context) throws Exception {  
          2.     if (context != null) {  
          3.       // 基礎緩存類型  
          4.       String type = context.getStringAttribute("type", "PERPETUAL");  
          5.       Class typeClass = typeAliasRegistry.resolveAlias(type);  
          6.       // 排除算法緩存類型  
          7.       String eviction = context.getStringAttribute("eviction", "LRU");  
          8.       Class evictionClass = typeAliasRegistry.resolveAlias(eviction);  
          9.       // 緩存自動刷新時間  
          10.       Long flushInterval = context.getLongAttribute("flushInterval");  
          11.       // 緩存存儲實例引用的大小  
          12.       Integer size = context.getIntAttribute("size");  
          13.       // 是否是只讀緩存  
          14.       boolean readWrite = !context.getBooleanAttribute("readOnly", false);  
          15.       Properties props = context.getChildrenAsProperties();  
          16.       // 初始化緩存實現  
          17.       builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);  
          18.     }  
          19.   }  
          以下是  useNewCache 方法實現: 
          Java代碼  收藏代碼
          1. public Cache useNewCache(Class typeClass,  
          2.                            Class evictionClass,  
          3.                            Long flushInterval,  
          4.                            Integer size,  
          5.                            boolean readWrite,  
          6.                            Properties props) {  
          7.     typeClass = valueOrDefault(typeClass, PerpetualCache.class);  
          8.     evictionClass = valueOrDefault(evictionClass, LruCache.class);  
          9.     // 這里構建 Cache 實例采用 Builder 模式,每一個 Namespace 生成一個  Cache 實例  
          10.     Cache cache = new CacheBuilder(currentNamespace)  
          11.         // Builder 前設置一些從XML中解析過來的參數  
          12.         .implementation(typeClass)  
          13.         .addDecorator(evictionClass)  
          14.         .clearInterval(flushInterval)  
          15.         .size(size)  
          16.         .readWrite(readWrite)  
          17.         .properties(props)  
          18.         // 再看下面的 build 方法實現  
          19.         .build();  
          20.     configuration.addCache(cache);  
          21.     currentCache = cache;  
          22.     return cache;  
          23. }  
          24.   
          25. public Cache build() {  
          26.     setDefaultImplementations();  
          27.     // 創建基礎緩存實例  
          28.     Cache cache = newBaseCacheInstance(implementation, id);  
          29.     setCacheProperties(cache);  
          30.     // 緩存排除算法初始化,并將其委托至基礎緩存中  
          31.     for (Class<? extends Cache> decorator : decorators) {  
          32.       cache = newCacheDecoratorInstance(decorator, cache);  
          33.       setCacheProperties(cache);  
          34.     }  
          35.     // 標準裝飾器緩存設置,如LoggingCache之類,同樣將其委托至基礎緩存中  
          36.     cache = setStandardDecorators(cache);  
          37.     // 返回最終緩存的責任鏈對象  
          38.     return cache;  
          39. }  
          最終生成后的緩存實例對象結構: 
           
          可見,所有構建的緩存實例已經通過責任鏈方式將其串連在一起,各 Cache 各負其責、依次調用,直到緩存數據被 Put 至 基礎緩存實例中存儲。 


          Cache 實例解剖: 
          實例類:SynchronizedCache 
          說   明:用于控制 ReadWriteLock,避免并發時所產生的線程安全問題。 
          解   剖: 
          對于 Lock 機制來說,其分為 Read 和 Write 鎖,其 Read 鎖允許多個線程同時持有,而 Write 鎖,一次能被一個線程持有,如果當 Write 鎖沒有釋放,其它需要 Write 的線程只能等待其釋放才能去持有。 
          其代碼實現:
          Java代碼  收藏代碼
          1. public void putObject(Object key, Object object) {  
          2.     acquireWriteLock();  // 獲取 Write 鎖  
          3.     try {  
          4.       delegate.putObject(key, object); // 委托給下一個 Cache 執行 put 操作  
          5.     } finally {  
          6.       releaseWriteLock(); // 釋放 Write 鎖  
          7.     }  
          8.   }  
          對于 Read 數據來說,也是如此,不同的是 Read 鎖允許多線程同時持有 : 
          Java代碼  收藏代碼
          1. public Object getObject(Object key) {  
          2.     acquireReadLock();  
          3.     try {  
          4.       return delegate.getObject(key);  
          5.     } finally {  
          6.       releaseReadLock();  
          7.     }  
          8.   }  
          其具體原理可以看看 jdk concurrent 中的 ReadWriteLock 實現。 


          實例類:LoggingCache 
          說   明:用于日志記錄處理,主要輸出緩存命中率信息。 
          解   剖: 
          說到緩存命中信息的統計,只有在 get 的時候才需要統計命中率: 
          Java代碼  收藏代碼
          1. public Object getObject(Object key) {  
          2.     requests++; // 每調用一次該方法,則獲取次數+1  
          3.     final Object value = delegate.getObject(key);  
          4.     if (value != null) {  // 命中! 命中+1  
          5.       hits++;  
          6.     }  
          7.     if (log.isDebugEnabled()) {  
          8.       // 輸出命中率。計算方法為: hits / requets 則為命中率  
          9.       log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());  
          10.     }  
          11.     return value;  
          12. }  



          實例類:SerializedCache 
          說   明:向緩存中 put 或 get 數據時的序列化及反序列化處理。 
          解   剖: 
          序列化在Java里面已經是最基礎的東西了,這里也沒有什么特殊之處: 
          Java代碼  收藏代碼
          1. public void putObject(Object key, Object object) {  
          2.      // PO 類需要實現 Serializable 接口  
          3.     if (object == null || object instanceof Serializable) {  
          4.       delegate.putObject(key, serialize((Serializable) object));   
          5.     } else {  
          6.       throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);  
          7.     }  
          8.   }  
          9.   
          10.   public Object getObject(Object key) {  
          11.     Object object = delegate.getObject(key);  
          12.     // 獲取數據時對 二進制數據進行反序列化  
          13.     return object == null ? null : deserialize((byte[]) object);  
          14.   }  
          其 serialize 及 deserialize 代碼就不必要貼了。 


          實例類:LruCache 
          說   明:最近最少使用的:移除最長時間不被使用的對象,基于LRU算法。 
          解   剖: 
          這里的 LRU 算法基于 LinkedHashMap 覆蓋其 removeEldestEntry 方法實現。好象之前看過 XMemcached 的 LRU 算法也是這樣實現的。 
          初始化 LinkedHashMap,默認為大小為 1024 個元素: 
          Java代碼  收藏代碼
          1. public LruCache(Cache delegate) {  
          2.     this.delegate = delegate;  
          3.     setSize(1024); // 設置 map 默認大小  
          4. }  
          5. public void setSize(final int size) {  
          6.     // 設置其 capacity 為size, 其 factor 為.75F  
          7.     keyMap = new LinkedHashMap(size, .75F, true) {  
          8.       // 覆蓋該方法,當每次往該map 中put 時數據時,如該方法返回 True,便移除該map中使用最少的Entry  
          9.       // 其參數  eldest 為當前最老的  Entry  
          10.       protected boolean removeEldestEntry(Map.Entry eldest) {  
          11.         boolean tooBig = size() > size;  
          12.         if (tooBig) {  
          13.           eldestKey = eldest.getKey(); //記錄當前最老的緩存數據的 Key 值,因為要委托給下一個 Cache 實現刪除  
          14.         }  
          15.         return tooBig;  
          16.       }  
          17.     };  
          18.   }  
          19.   
          20. public void putObject(Object key, Object value) {  
          21.     delegate.putObject(key, value);  
          22.     cycleKeyList(key);  // 每次 put 后,調用移除最老的 key  
          23. }  
          24. // 看看當前實現是否有 eldestKey, 有的話就調用 removeObject ,將該key從cache中移除  
          25. private void cycleKeyList(Object key) {  
          26.     keyMap.put(key, key); // 存儲當前 put 到cache中的 key 值  
          27.     if (eldestKey != null) {  
          28.       delegate.removeObject(eldestKey);  
          29.       eldestKey = null;  
          30.     }  
          31.   }  
          32.   
          33. public Object getObject(Object key) {  
          34.     keyMap.get(key); // 便于 該 Map 統計 get該key的次數  
          35.     return delegate.getObject(key);  
          36.   }  


          實例類:PerpetualCache 
          說   明:這個比較簡單,直接通過一個 HashMap 來存儲緩存數據。所以沒什么說的,直接看下面的 MemcachedCache 吧。 


          自定義二級緩存/Memcached 
          其自定義二級緩存也較為簡單,它本身默認提供了對 Ehcache 及 Hazelcast 的緩存支持:Mybatis-Cache,我這里參考它們的實現,自定義了針對 Memcached 的緩存支持,其代碼如下: 
          Java代碼  收藏代碼
          1. package com.xx.core.plugin.mybatis;  
          2.   
          3. import java.util.LinkedList;  
          4. import java.util.concurrent.locks.ReadWriteLock;  
          5. import java.util.concurrent.locks.ReentrantReadWriteLock;  
          6.   
          7. import org.apache.ibatis.cache.Cache;  
          8. import org.slf4j.Logger;  
          9. import org.slf4j.LoggerFactory;  
          10.   
          11. import com.xx.core.memcached.JMemcachedClientAdapter;  
          12. import com.xx.core.memcached.service.CacheService;  
          13. import com.xx.core.memcached.service.MemcachedService;  
          14.   
          15. /** 
          16.  * Cache adapter for Memcached. 
          17.  *  
          18.  * @author denger 
          19.  */  
          20. public class MemcachedCache implements Cache {  
          21.   
          22.     // Sf4j logger reference  
          23.     private static Logger logger = LoggerFactory.getLogger(MemcachedCache.class);  
          24.   
          25.     /** The cache service reference. */  
          26.     protected static final CacheService CACHE_SERVICE = createMemcachedService();  
          27.   
          28.     /** The ReadWriteLock. */  
          29.     private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
          30.   
          31.     private String id;  
          32.     private LinkedList<String> cacheKeys = new LinkedList<String>();  
          33.   
          34.     public MemcachedCache(String id) {  
          35.         this.id = id;  
          36.     }  
          37.     // 創建緩存服務類,基于java-memcached-client  
          38.     protected static CacheService createMemcachedService() {  
          39.         JMemcachedClientAdapter memcachedAdapter;  
          40.   
          41.         try {  
          42.             memcachedAdapter = new JMemcachedClientAdapter();  
          43.         } catch (Exception e) {  
          44.             String msg = "Initial the JMmemcachedClientAdapter Error.";  
          45.             logger.error(msg, e);  
          46.             throw new RuntimeException(msg);  
          47.         }  
          48.         return new MemcachedService(memcachedAdapter);  
          49.     }  
          50.   
          51.     @Override  
          52.     public String getId() {  
          53.         return this.id;  
          54.     }  
          55.   
          56.     // 根據 key 從緩存中獲取數據  
          57.     @Override  
          58.     public Object getObject(Object key) {  
          59.         String cacheKey = String.valueOf(key.hashCode());  
          60.         Object value = CACHE_SERVICE.get(cacheKey);  
          61.         if (!cacheKeys.contains(cacheKey)){  
          62.             cacheKeys.add(cacheKey);  
          63.         }  
          64.         return value;  
          65.     }  
          66.   
          67.     @Override  
          68.     public ReadWriteLock getReadWriteLock() {  
          69.         return this.readWriteLock;  
          70.     }  
          71.   
          72.     // 設置數據至緩存中  
          73.     @Override  
          74.     public void putObject(Object key, Object value) {  
          75.         String cacheKey = String.valueOf(key.hashCode());  
          76.   
          77.         if (!cacheKeys.contains(cacheKey)){  
          78.             cacheKeys.add(cacheKey);  
          79.         }  
          80.         CACHE_SERVICE.put(cacheKey, value);  
          81.     }  
          82.     // 從緩存中刪除指定 key 數據  
          83.     @Override  
          84.     public Object removeObject(Object key) {  
          85.         String cacheKey = String.valueOf(key.hashCode());  
          86.   
          87.         cacheKeys.remove(cacheKey);  
          88.         return CACHE_SERVICE.delete(cacheKey);  
          89.     }  
          90.     //清空當前 Cache 實例中的所有緩存數據  
          91.     @Override  
          92.     public void clear() {  
          93.         for (int i = 0; i < cacheKeys.size(); i++){  
          94.             String cacheKey = cacheKeys.get(i);  
          95.             CACHE_SERVICE.delete(cacheKey);  
          96.         }  
          97.         cacheKeys.clear();  
          98.     }  
          99.   
          100.     @Override  
          101.     public int getSize() {  
          102.         return cacheKeys.size();  
          103.     }  
          104. }  

          在  ProductMapper 中增加配置: 
          Xml代碼  收藏代碼
          1. <cache eviction="LRU" type="com.xx.core.plugin.mybatis.MemcachedCache" />  

          啟動Memcached: 
          Shell代碼  收藏代碼
          1. memcached -c 2000 -p 11211 -vv -U 0 -l 192.168.1.2 -v  

          執行Mapper 中的查詢、修改等操作,Test: 
          Java代碼  收藏代碼
          1. @Test  
          2.     public void testSelectById() {  
          3.         Long pid = 100L;  
          4.   
          5.         Product dbProduct = productMapper.selectByID(pid);  
          6.         Assert.assertNotNull(dbProduct);  
          7.   
          8.         Product cacheProduct = productMapper.selectByID(pid);  
          9.         Assert.assertNotNull(cacheProduct);  
          10.   
          11.         productMapper.updateName("IPad", pid);  
          12.   
          13.         Product product = productMapper.selectByID(pid);  
          14.         Assert.assertEquals(product.getName(), "IPad");  
          15.     }  

          Memcached Loging: 
           
          看上去沒什么問題~ OK了。
          posted on 2014-05-30 13:53 SIMONE 閱讀(604) 評論(1)  編輯  收藏 所屬分類: JAVA

          FeedBack:
          # re: MyBatis 緩存機制深度解剖 / 自定義二級緩存
          2014-08-19 19:54 | 碎片時間
          又來博主這學習了。
          【微觀晚報】- 碎片時間閱讀 8.19
          http://www.ma4.net  回復  更多評論
            
          主站蜘蛛池模板: 崇信县| 保靖县| 海兴县| 万全县| 五常市| 高邑县| 嘉祥县| 日喀则市| 纳雍县| 凤山县| 汉沽区| 静安区| 镇平县| 内江市| 丰原市| 印江| 江华| 沧州市| 松桃| 福泉市| 长治县| 木里| 龙陵县| 连城县| 兰州市| 泸溪县| 静乐县| 南丹县| 固安县| 林州市| 交城县| 金门县| 曲靖市| 新疆| 茌平县| 和林格尔县| 申扎县| 石泉县| 宿松县| 沙洋县| 华蓥市|