記錄是一種精神,是加深理解最好的方式之一酬屉。
最近看了下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)票堵。