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全局配置中啟用二級緩存配置
<setting name="cacheEnabled" value="true"/>
  • 在對應(yīng)的Mapper.xml中配置cache節(jié)點(diǎn)
<mapper namespace="userMapper">
    <cache />
    <result ... />
    <select ... />
</mapper>
  • 在對應(yīng)的select查詢節(jié)點(diǎn)中添加useCache=true
<select id="findUserById" parameterType="int" resultMap="user" useCache="true">
    select * from users where id=#{id};
</select>
  • 高級配置

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) {
            // ensureNoOutParams
            if (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)票堵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扼睬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子悴势,更是在濱河造成了極大的恐慌窗宇,老刑警劉巖措伐,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異担映,居然都是意外死亡废士,警方通過查閱死者的電腦和手機(jī)叫潦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蝇完,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矗蕊,你說我怎么就攤上這事短蜕。” “怎么了傻咖?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵朋魔,是天一觀的道長。 經(jīng)常有香客問我卿操,道長警检,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任害淤,我火速辦了婚禮扇雕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窥摄。我一直安慰自己镶奉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布崭放。 她就那樣靜靜地躺著哨苛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪币砂。 梳的紋絲不亂的頭發(fā)上建峭,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音决摧,去河邊找鬼亿蒸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜜徽,可吹牛的內(nèi)容都是我干的祝懂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼拘鞋,長吁一口氣:“原來是場噩夢啊……” “哼砚蓬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盆色,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤灰蛙,失蹤者是張志新(化名)和其女友劉穎祟剔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摩梧,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡物延,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仅父。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叛薯。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖笙纤,靈堂內(nèi)的尸體忽然破棺而出耗溜,到底是詐尸還是另有隱情,我是刑警寧澤省容,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布抖拴,位于F島的核電站,受9級特大地震影響腥椒,放射性物質(zhì)發(fā)生泄漏阿宅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一笼蛛、第九天 我趴在偏房一處隱蔽的房頂上張望洒放。 院中可真熱鬧,春花似錦伐弹、人聲如沸拉馋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煌茴。三九已至,卻和暖如春日川,著一層夾襖步出監(jiān)牢的瞬間蔓腐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工龄句, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留回论,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓分歇,卻偏偏與公主長得像傀蓉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子职抡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容