Mybatis二级缓存原理
記錄是一種精神,是加深理解最好的方式之一。
最近看了下Mybatis的源碼,分析了二級緩存的實(shí)現(xiàn)方式,在這里把他記下來。雖然這不復(fù)雜,對這方面的博客也有很多,寫的也很好。但我堅(jiān)信看懂了是其一,能夠教別人或者描述清楚記下來才能真正的掌握。
曹金桂 cao_jingui@163.com (如有欠缺還請指教)
時(shí)間:2016年10月11日16:00
這篇文章能夠幫你
- 學(xué)會(huì)對Mybatis配置二級緩存
- 學(xué)會(huì)Mybatis二級緩存的實(shí)現(xiàn)方式
- 學(xué)會(huì)整合外部緩存框架(如:Ehcache)
- 學(xué)會(huì)自定義二級緩存
1. Mybatis內(nèi)部二級緩存的配置
要使用Mybatis的二級緩存,需要對Mybatis進(jìn)行配置,配置分三步
- Mybatis全局配置中啟用二級緩存配置
- 在對應(yīng)的Mapper.xml中配置cache節(jié)點(diǎn)
- 在對應(yīng)的select查詢節(jié)點(diǎn)中添加useCache=true
- 高級配置
a. 為每一個(gè)Mapper分配一個(gè)Cache緩存對象(使用<cache>節(jié)點(diǎn)配置)
b. 多個(gè)Mapper共用一個(gè)Cache緩存對象(使用<cache-ref>節(jié)點(diǎn)配置)
只要簡單的三步配置即可開啟Mybatis的二級緩存了。在使用mybatis查詢時(shí)候("userMapper.findUserById"),不同會(huì)話(Sqlsession)在查詢時(shí)候,只會(huì)查詢數(shù)據(jù)庫一次,第二次會(huì)從二級緩存中讀取。
@Before public void before() {String mybatisConfigFile = "MybatisConfig/Mybatis-conf.xml";InputStream stream = TestMybatis.class.getClassLoader().getResourceAsStream(mybatisConfigFile);sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); //構(gòu)建sqlSession的工廠 } @Test public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();User i = sqlSession.selectOne("userMapper.findUserById", 1);System.out.println(i);sqlSession.close();sqlSession = sqlSessionFactory.openSession();User x = sqlSession.selectOne("userMapper.findUserById", 1); // 讀取二級緩存數(shù)據(jù)System.out.println(x);sqlSession.close(); }2. Mybatis內(nèi)部二級緩存的設(shè)計(jì)及工作模式
?
首先我們要知道,mybatis的二級緩存是通過CacheExecutor實(shí)現(xiàn)的。CacheExecutor其實(shí)是Executor的代理對象。所有的查詢操作,在CacheExecutor中都會(huì)先匹配緩存中是否存在,不存在則查詢數(shù)據(jù)庫。
3. 內(nèi)部二級緩存的實(shí)現(xiàn)詳解
竟然知道Mybatis二級緩存是通過CacheExecotur實(shí)現(xiàn)的,那看下Mybatis中創(chuàng)建Executor的過程
// 創(chuàng)建執(zhí)行器(Configuration.newExecutor) public Executor newExecutor(Transaction transaction, ExecutorType executorType) {//確保ExecutorType不為空(defaultExecutorType有可能為空)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) { //重點(diǎn)在這里,如果啟用全局代理對象,返回Executor的Cache包裝類對象executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor; }重點(diǎn)在cacheEnabled這個(gè)參數(shù)。如果你看了我的文章[Mybatis配置文件解析過程詳解],就應(yīng)該知道了怎么設(shè)置cacheEnabled。對,就是此文章第一點(diǎn)說的開啟Mybatis的全局配置項(xiàng)。我們繼續(xù)看下CachingExecutor具體怎么實(shí)現(xiàn)的。
public class CachingExecutor implements Executor {private Executor delegate;public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}public int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms); //是否需要更緩存return delegate.update(ms, parameterObject); //更新數(shù)據(jù)}...... }很清晰,靜態(tài)代理模式。在CachingExecutor的所有操作都是通過調(diào)用內(nèi)部的delegate對象執(zhí)行的。緩存只應(yīng)用于查詢,我們看下CachingExecutor的query方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);//創(chuàng)建緩存值CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//獲取記錄return 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();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// ensureNoOutParamsif (ms.getStatementType() == StatementType.CALLABLE) {for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {if (parameterMapping.getMode() != ParameterMode.IN) {throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");}}}List<E> list = (List<E>) tcm.getObject(cache, key); //從緩存中獲取數(shù)據(jù)if (list == null) {list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // 結(jié)果保存到緩存中}return list;}}return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }如果MappedStatement中對應(yīng)的Cache存在,并且對于的查詢開啟了二級緩存(useCache="true"),那么在CachingExecutor中會(huì)先從緩存中根據(jù)CacheKey獲取數(shù)據(jù),如果緩存中不存在則從數(shù)據(jù)庫獲取。這里的代碼很簡單,很容易理解。
說到緩存,有效期和緩存策略不得不提。在Mybatis中二級緩存也實(shí)現(xiàn)了有效期的控制和緩存策略。Mybatis中是使用裝飾模式實(shí)現(xiàn)的,具體可以看下mybatis的cache包
?
?
具體于配置如下:
<cache eviction="FIFO|LRU|SOFT|WEAK" flushInterval="300" size="100" />對應(yīng)具體實(shí)現(xiàn)源碼可以參考CacheBuilder類的源碼。
public Cache build() {if (implementation == null) { //緩存實(shí)現(xiàn)類implementation = PerpetualCache.class;if (decorators.size() == 0) {decorators.add(LruCache.class);}}Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}// 采用默認(rèn)緩存包裝類cache = setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache; }4. 一級緩存和二級緩存的使用順序
如果你的MyBatis使用了二級緩存,并且你的Mapper和select語句也配置使用了二級緩存,那么在執(zhí)行select查詢的時(shí)候,MyBatis會(huì)先從二級緩存中取輸入,其次才是一級緩存,即MyBatis查詢數(shù)據(jù)的順序是:
二級緩存 ———> 一級緩存——> 數(shù)據(jù)庫
5. mybatis二級緩存和分頁插件同時(shí)使用產(chǎn)生的問題
問題:分頁插件開啟二級緩存后,分頁查詢時(shí)無論查詢哪一頁都返回第一頁的數(shù)據(jù)
在之前講解Mybatis的執(zhí)行流程的時(shí)候提到,在開啟cache的前提下,Mybatis的executor會(huì)先從緩存里讀取數(shù)據(jù),讀取不到才去數(shù)據(jù)庫查詢。問題就出在這里,sql自動(dòng)生成插件和分頁插件執(zhí)行的時(shí)機(jī)是在statementhandler里,而statementhandler是在executor之后執(zhí)行的,無論sql自動(dòng)生成插件和分頁插件都是通過改寫sql來實(shí)現(xiàn)的,executor在生成讀取cache的key(key由sql以及對應(yīng)的參數(shù)值構(gòu)成)時(shí)使用都是原始的sql,這樣當(dāng)然就出問題了。
找到問題的原因后,解決起來就方便了。只要通過攔截器改寫executor里生成key的方法,在生成可以時(shí)使用自動(dòng)生成的sql(對應(yīng)sql自動(dòng)生成插件)或加入分頁信息(對應(yīng)分頁插件)就可以了。
參考:http://blog.csdn.net/hupanfeng/article/details/16950161
6. mybatis整合第三方緩存框架
?
我們以ehcache為例。對于ehcache我只會(huì)簡單的使用。這里我只是介紹Mybatis怎么使用ehcache,不對ehcache配置作說明。我們知道,在配置二級緩存時(shí)候,我們可以指定對應(yīng)的實(shí)現(xiàn)類。這里需要mybatis-ehcache-1.0.3.jar這個(gè)jar包。在Mapper中我們只要配置如下即可。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>當(dāng)然,項(xiàng)目中ehcache的配置還是需要的。
小結(jié)
對于Mybatis整合第三方的緩存,實(shí)現(xiàn)騎士很簡單,只要在配置的地方制定實(shí)現(xiàn)類即可。
Mybatis默認(rèn)二級緩存的實(shí)現(xiàn)在集群或者分布式部署下是有問題的,Mybatis默認(rèn)緩存只在當(dāng)節(jié)點(diǎn)內(nèi)有效,并且對緩存的失效操作無法同步的其他節(jié)點(diǎn)。需要整合第三方分布式緩存實(shí)現(xiàn),如ehcache或者自定義實(shí)現(xiàn)。
作者:曹金桂
鏈接:https://www.jianshu.com/p/5ff874fa696f
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。
總結(jié)
以上是生活随笔為你收集整理的Mybatis二级缓存原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《深入理解mybatis原理》 MyBa
- 下一篇: 通过源码分析MyBatis的缓存