myBatis源碼解析-二級緩存的實現(xiàn)方式
5點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??超人小冰?
來源 |? urlify.cn/22am2u
1. 前言
前面近一個月去寫自己的mybatis框架了,對mybatis源碼分析止步不前,此文繼續(xù)前面的文章。開始分析mybatis一,二級緩存的實現(xiàn)。
附上自己的項目github地址:https://github.com/xbcrh/simple-ibatis
對mybatis感興趣的同學可關注下,全手寫的一個orm框架,實現(xiàn)了sql的基本功能和對象關系映射。
廢話不說,開始解析mybatis緩存源碼實現(xiàn)。
2. mybatis中緩存的實現(xiàn)方式
見mybatis源碼包 org.apache.ibatis.cache
2.1 mybatis緩存實現(xiàn)接口類:cache
public?interface?Cache?{
??//?獲取緩存的ID
??String?getId();
??//?放入緩存
??void?putObject(Object?key,?Object?value);
??//?從緩存中獲取
??Object?getObject(Object?key);
??//?移除緩存
??Object?removeObject(Object?key);
??//?清除緩存
??void?clear();
??//?獲取緩存大小
??int?getSize();?
??//?獲取鎖
??ReadWriteLock?getReadWriteLock();
}
mybatis自定義了緩存接口類,提供了基本的緩存增刪改查的操作。在此基礎上,提供了基礎緩存實現(xiàn)類PerpetualCache。源碼如下:
2.2 mybatis緩存基本實現(xiàn)類:PerpetualCache
public?class?PerpetualCache?implements?Cache?{
??//?緩存的ID
??private?String?id;
??//?使用HashMap充當緩存(老套路,緩存底層實現(xiàn)基本都是map)
??private?MapPerpetualCache 類其實是對HashMap的封裝,通過對map的put和get等操作實現(xiàn)緩存的存取等功能。mybatis中除了基本的緩存實現(xiàn)類外還提供了一系列的裝飾類(此處是用到裝飾者模式),此處拿較為重要的裝飾類LruCache進行分析。
2.3 Lru淘汰策略實現(xiàn)分析
Lru是一種緩存淘汰策略,其核心思想是”如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高“,LruCache 是基于LinkedHashMap實現(xiàn),LinkedHashMap繼承自HashMap,來分析下為什么LinkedHashMap可以當做Lru緩存實現(xiàn)。
public?class?LinkedHashMap
????extends?HashMap
????implements?Map
LinkedHashMap繼承HashMap類,實際上就是對HashMap的一個封裝。
//?內(nèi)部維護了一個自定義的Entry,集成HashMap中的node類
static?class?Entry?extends?HashMap.Node?{
????????//?linkedHashmap用來連接節(jié)點的字段,根據(jù)這兩個字段可查找按順序插入的節(jié)點
????????Entry?before,?after;
????????Entry(int?hash,?K?key,?V?value,?Node?next)?{
????????????super(hash,?key,?value,?next);
????????}
????}
構(gòu)造方法見如下:
public?LinkedHashMap(int?initialCapacity,
?????????????????????????float?loadFactor,
?????????????????????????boolean?accessOrder)?{
????????//?調(diào)用HashMap的構(gòu)造方法
????????super(initialCapacity,?loadFactor);
????????//?訪問順序維護,默認false不開啟
????????this.accessOrder?=?accessOrder;
????}
引入兩種圖來理解HashMap與LinkedHashMap

?
?
?以上是HashMap的結(jié)構(gòu),采用拉鏈法解決沖突。LinkedHashMap在HashMap基礎上增加了一個雙向鏈表來表示節(jié)點插入順序。

?
?
如上,節(jié)點上多出的紅色和藍色箭頭代表了Entry中的before和after。在put元素時,會自動在尾節(jié)點后加上該元素,維持雙向鏈表。了解LinkedHashMap結(jié)構(gòu)后,在看看究竟什么是維護節(jié)點的訪問順序。先說結(jié)論,當開啟accessOrder后,在對元素進行get操作時,會將該元素放在雙向鏈表的隊尾節(jié)點。源碼如下:
public?V?get(Object?key)?{
????????Node?e;
???????//?調(diào)用HashMap的getNode方法,獲取元素
????????if?((e?=?getNode(hash(key),?key))?==?null)
????????????return?null;
???????//?默認為false,如果開啟維護鏈表訪問順序,執(zhí)行如下方法
????????if?(accessOrder)
????????????afterNodeAccess(e);
????????return?e.value;
????}
//?方法實現(xiàn)(將e放入尾節(jié)點處)
void?afterNodeAccess(Node?e)?{?//?move?node?to?last
????????LinkedHashMap.Entry?last;
????????//?當節(jié)點不是雙向鏈表的尾節(jié)點時
????????if?(accessOrder?&&?(last?=?tail)?!=?e)?{
????????????LinkedHashMap.Entry?p?=
????????????????(LinkedHashMap.Entry)e,?b?=?p.before,?a?=?p.after;?//?將待調(diào)整的e節(jié)點賦值給p
???????????
????????????p.after?=?null;
????????????if?(b?==?null)?//?說明e為頭節(jié)點,將老e的下一節(jié)點值為頭節(jié)點
????????????????head?=?a;
????????????else
????????????????b.after?=?a;//?否則,e的上一節(jié)點直接指向e的下一節(jié)點
????????????if?(a?!=?null)
????????????????a.before?=?b;?//?e的下一節(jié)點的上節(jié)點為e的上一節(jié)點
????????????else
????????????????last?=?b;??
????????????if?(last?==?null)
????????????????head?=?p;??
????????????else?{
????????????????p.before?=?last;???//?last和p互相連接
????????????????last.after?=?p;
????????????}
????????????tail?=?p;???//?將雙向鏈表的尾節(jié)點指向p
????????????++modCount;?//?修改次數(shù)加以
????????}
????}
代碼很簡單,如上面的圖,我訪問了節(jié)點值為3的節(jié)點,那木經(jīng)過get操作后,結(jié)構(gòu)變成如下:

?
?
?經(jīng)過如上分析我們知道,如果限制雙向鏈表的長度,每次刪除頭節(jié)點的值,就變?yōu)橐粋€lru的淘汰策略了。舉個例子,我想限制雙向鏈表的長度為3,依次put 1 2 3,鏈表為 1 -> 2 -> 3,訪問元素2,鏈表變?yōu)?1 -> 3-> 2,然后put 4 ,發(fā)現(xiàn)鏈表長度超過3了,淘汰1,鏈表變?yōu)? -> 2 ->4;
那木linkedHashMap是怎樣知道自定義的限制策略,看代碼,因為LinkedHashMap中沒有提供自己的put方法,是直接調(diào)用的HashMap的put方法,查看hashMap代碼如下:
//?hashMap
final?V?putVal(int?hash,?K?key,?V?value,?boolean?onlyIfAbsent,
???????????????????boolean?evict)?{
????????Node[]?tab;?Node?p;?int?n,?i;
????????if?((tab?=?table)?==?null?||?(n?=?tab.length)?==?0)
????????????n?=?(tab?=?resize()).length;
????????if?((p?=?tab[i?=?(n?-?1)?&?hash])?==?null)
????????????tab[i]?=?newNode(hash,?key,?value,?null);
????????else?{
????????????Node?e;?K?k;
????????????if?(p.hash?==?hash?&&
????????????????((k?=?p.key)?==?key?||?(key?!=?null?&&?key.equals(k))))
????????????????e?=?p;
????????????else?if?(p?instanceof?TreeNode)
????????????????e?=?((TreeNode)p).putTreeVal(this,?tab,?hash,?key,?value);
????????????else?{
????????????????for?(int?binCount?=?0;?;?++binCount)?{
????????????????????if?((e?=?p.next)?==?null)?{
????????????????????????p.next?=?newNode(hash,?key,?value,?null);
????????????????????????if?(binCount?>=?TREEIFY_THRESHOLD?-?1)?//?-1?for?1st
????????????????????????????treeifyBin(tab,?hash);
????????????????????????break;
????????????????????}
????????????????????if?(e.hash?==?hash?&&
????????????????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))
????????????????????????break;
????????????????????p?=?e;
????????????????}
????????????}
????????????if?(e?!=?null)?{?//?existing?mapping?for?key
????????????????V?oldValue?=?e.value;
????????????????if?(!onlyIfAbsent?||?oldValue?==?null)
????????????????????e.value?=?value;
????????????????afterNodeAccess(e);
????????????????return?oldValue;
????????????}
????????}
????????++modCount;
????????if?(++size?>?threshold)
????????????resize();
????????//?看這個方法
????????afterNodeInsertion(evict);
????????return?null;
????}
//?linkedHashMap重寫了此方法
?void?afterNodeInsertion(boolean?evict)?{?//?possibly?remove?eldest
????????LinkedHashMap.Entry?first;
????????//?removeEldestEntry默認返回fasle
????????if?(evict?&&?(first?=?head)?!=?null?&&?removeEldestEntry(first))?{
????????????K?key?=?first.key;
????????????//?移除雙向鏈表中的頭指針元素
????????????removeNode(hash(key),?key,?null,?false,?true);
????????}
????}
原來只需要重新實現(xiàn)removeEldestEntry就可以自定義實現(xiàn)lru功能了。了解基本的lru原理后,開始分析LruCache。
2.4 緩存包裝類 - LruCache
public?class?LruCache?implements?Cache?{
??//?被裝飾的緩存類,即真實的緩存類,提供真正的緩存能力
??private?final?Cache?delegate;
??//?內(nèi)部維護的一個linkedHashMap,用來實現(xiàn)LRU功能
??private?Map?keyMap;
??//?待淘汰的緩存元素
??private?Object?eldestKey;
??//?唯一構(gòu)造方法
??public?LruCache(Cache?delegate)?{
????this.delegate?=?delegate;?//?被裝飾的緩存類
????setSize(1024);?//?設置緩存大小
??}
??....
?}
經(jīng)分析,LruCache還是個裝飾類。內(nèi)部除了維護真正的Cache外,還維護了一個LinkedHashMap,用來實現(xiàn)Lru功能,查看其構(gòu)造方法。
//?唯一構(gòu)造方法
??public?LruCache(Cache?delegate)?{
????this.delegate?=?delegate;?//?被裝飾的緩存類
????setSize(1024);?//?設置緩存大小
??}
??
???//?setSize()是構(gòu)造方法中方法
??public?void?setSize(final?int?size)?{
????//?初始化keyMap
????keyMap?=?new?LinkedHashMap(size,?.75F,?true)?{
??????private?static?final?long?serialVersionUID?=?4267176411845948333L;
??????//?什么時候自動刪除緩存元素,此處是根據(jù)當緩存數(shù)量超過指定的數(shù)量,在LinkedHashMap內(nèi)部刪除元素
??????protected?boolean?removeEldestEntry(Map.Entry?eldest)?{
????????boolean?tooBig?=?size()?>?size;
????????if?(tooBig)?{
??????????//?將待刪除元素賦值給eldestKey,后續(xù)會根據(jù)此值是否為空在真實緩存中刪除
??????????eldestKey?=?eldest.getKey();
????????}
????????return?tooBig;
??????}
????};
??}
和上文分析一樣,重寫了removeEldestEntry方法。此方法返回一個boolean值,當緩存的大小超過自定義大小,返回true,此時linkedHashMap中會自動刪除eldest元素。在真實緩存cache中也將此元素刪除。保持真實cache和linkedHashMap元素一致。其實就是用linkedHashMap的lru特性來保證cache也具有此lru特性。
分析put方法和get方法驗證此結(jié)論.。
@Override
??public?Object?getObject(Object?key)?{
????keyMap.get(key);?//?觸發(fā)linkedHashMap中get方法,將key對應的元素放入隊尾
????return?delegate.getObject(key);?//?調(diào)用真實的緩存get方法
??}
??
??//?放入緩存時,除了在真實緩存中放一份外,還會在LinkedHashMap中放一份
???@Override
??public?void?putObject(Object?key,?Object?value)?{
????delegate.putObject(key,?value);
????//?調(diào)用LinkedHashMap的方法
????cycleKeyList(key);
??}
??
??private?void?cycleKeyList(Object?key)?{
????//?linkedHashMap中put,會觸發(fā)removeEldestEntry方法,如果緩存大小超過指定大小,則將雙向鏈表對頭值賦值給eldestKey
????keyMap.put(key,?key);?
????//?檢查eldestKey是否為空。不為空,則代表此元素是淘汰的元素了,需要在真實緩存中刪除。
????if?(eldestKey?!=?null)?{
??????//?真實緩存中刪除
??????delegate.removeObject(eldestKey);
??????eldestKey?=?null;
????}
??}
介紹完Cache基本實現(xiàn)后,開始分析mybatis中一級緩存
3.?mybatis一級緩存使用源碼分析
此處是僅介紹mybatis的實現(xiàn),沒有涉及到與Spring整合,先介紹mybatis最基本的sql執(zhí)行語法。默認大家掌握了SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession用法。后面我會寫一篇博客分析SQL在mybatis中執(zhí)行的過程,會介紹到這些基礎知識。
InputStream?inputStream?=?Resources.getResourceAsStream("com/xiaobing/resource/mybatisConfig.xml");?//?構(gòu)建字節(jié)流
SqlSessionFactoryBuilder?builder?=?new?SqlSessionFactoryBuilder();???//?構(gòu)建SqlSessionFactoryBuilder
SqlSessionFactory?factory?=?builder.build(inputStream);??//?構(gòu)建SqlSessionFactory
SqlSession?sqlSession?=?factory.openSession();?//?生成SqlSession
List?userList?=?sqlSession.selectList("com.xiaobing.mapper.SysUserMapper.getSysUser");?//?執(zhí)行SysUserMapper類的getSysUser方法
前文構(gòu)建SqlSession的內(nèi)容大家感興趣可自行查看,此處僅分析執(zhí)行過程。查看selectList方法,mybatis中sqlSession的默認實現(xiàn)為DefaultSqlSession
public??List?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{
try?{
??//?每個mapper文件會解析生成一個MappedStatement
??MappedStatement?ms?=?configuration.getMappedStatement(statement);
??//?調(diào)用真實的查詢方法,此處是調(diào)用executor的方法。executor采用了裝飾者模式,若該mapper文件未啟用二級緩存,則默認為BaseExecutor。
??//?若該mapper文件啟用了二級緩存,則使用的是CachingExecutor
??List?result?=?executor.query(ms,?wrapCollection(parameter),?rowBounds,?Executor.NO_RESULT_HANDLER);
??return?result;
}?catch?(Exception?e)?{
??throw?ExceptionFactory.wrapException("Error?querying?database.??Cause:?"?+?e,?e);
}?finally?{
??ErrorContext.instance().reset();
}
}
因為此處使用的是裝飾者模式,BaseExecutor是最基礎的執(zhí)行器,使用了一級緩存,CachingExecutor是對BaseExecutor進行一次封裝,若打開二級緩存開關,在使用一級緩存前,先使用二級緩存。后文介紹二級緩存會分析這兩個Executor生成地方。先分析BaseExecutor的一級緩存實現(xiàn)。
//?BaseExecutor.java
/**
???*?查詢,并創(chuàng)建好CacheKey對象
???*?@param?ms?Mapper.xml文件的select,delete,update,insert這些DML標簽的封裝類
???*?@param?parameter?參數(shù)對象
???*?@param?rowBounds?Mybatis的分頁對象
???*?@param?resultHandler?結(jié)果處理器對象
???*/
??public??List?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler)?throws?SQLException?{
????BoundSql?boundSql?=?ms.getBoundSql(parameter);?//?獲取boundSql對象
????CacheKey?key?=?createCacheKey(ms,?parameter,?rowBounds,?boundSql);??//?生成緩存KEY
????return?query(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);?//?執(zhí)行如下方法
?}
??@SuppressWarnings("unchecked")
??public??List?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
????ErrorContext.instance().resource(ms.getResource()).activity("executing?a?query").object(ms.getId());
????if?(closed)?throw?new?ExecutorException("Executor?was?closed.");
????//如果將flushCacheRequired為true,則會在執(zhí)行器執(zhí)行之前就清空本地一級緩存
????if?(queryStack?==?0?&&?ms.isFlushCacheRequired())?{
??????clearLocalCache();
????}
????List?list;
????try?{
??????queryStack++;?//?請求堆棧加一
??????//?如果此次查詢的resultHandler為null(默認為null),則嘗試從本地緩存中獲取已經(jīng)緩存的的結(jié)果
??????list?=?resultHandler?==?null???(List)?localCache.getObject(key)?:?null;
????????if?(list?!=?null)?{
????????//如果查到localCache緩存,處理localOutputParameterCache,即對存儲過程的sql進行特殊處理
????????handleLocallyCachedOutputParameters(ms,?key,?parameter,?boundSql);
??????}?else?{
????????//?從數(shù)據(jù)庫中查詢,并將結(jié)果放入到localCache
????????list?=?queryFromDatabase(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);
??????}
????}?finally?{
??????//?請求堆棧減一
??????queryStack--;
????}
????if?(queryStack?==?0)?{
??????//?加載延遲加載List
??????for?(DeferredLoad?deferredLoad?:?deferredLoads)?{
????????deferredLoad.load();
??????}
??????deferredLoads.clear();?//?issue?#601
??????if?(configuration.getLocalCacheScope()?==?LocalCacheScope.STATEMENT)?{
????????clearLocalCache();?//?issue?#482
??????}
????}
????return?list;
??}
?
?private??List?queryFromDatabase(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
????List?list;
????localCache.putObject(key,?EXECUTION_PLACEHOLDER);?//?先放置一個占位符
????try?{
??????list?=?doQuery(ms,?parameter,?rowBounds,?resultHandler,?boundSql);??//?從數(shù)據(jù)庫中查找
????}?finally?{
??????localCache.removeObject(key);?//?移除占位符
????}
????localCache.putObject(key,?list);?//?放入緩存
????if?(ms.getStatementType()?==?StatementType.CALLABLE)?{
??????localOutputParameterCache.putObject(key,?parameter);??//?若是存儲過程,則放入存儲過程緩存中
????}
????return?list;?//?返回查詢結(jié)果
}
mybatis一級緩存很好理解,對于同一個SqlSession對象(即同一個Executor),執(zhí)行同一條語句時,BaseExecutor會先從自己的緩存中查找,是否存在此條語句的結(jié)果,若能找到,則直接返回(暫且忽略存儲過程處理)。若沒有找到,則查詢數(shù)據(jù)庫,將結(jié)果放入此緩存,供下次使用。mybatis默認打開一級緩存。
4.?mybatis二級緩存使用源碼分析
4.1 配置方式
在全局配置文件中mybatis-config.xml中加入如下設置
????
"cacheEnabled"?value="true"/>
在具體mapper.xml中配置
或者
或者采用注解配置方式,在mapper.java文件上配置注解
@CacheNamespace 或者 @CacheNamespaceRef
4.1 mybatis解析二級緩存標簽
還是采用上面sqlSession方式代碼來debug
InputStream?inputStream?=?Resources.getResourceAsStream("com/xiaobing/resource/mybatisConfig.xml");?//?構(gòu)建字節(jié)流
SqlSessionFactoryBuilder?builder?=?new?SqlSessionFactoryBuilder();???//?構(gòu)建SqlSessionFactoryBuilder
SqlSessionFactory?factory?=?builder.build(inputStream);??//?構(gòu)建SqlSessionFactory進入查看builder.build()方法
//?SqlSessionFactoryBuilder.java
??/**根據(jù)流構(gòu)建SqlSessionFactory*/
??public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{
????try?{
??????/**構(gòu)建XML文件解析器*/
??????XMLConfigBuilder?parser?=?new?XMLConfigBuilder(inputStream,?environment,?properties);
??????/**開始解析mybatis-config.xml文件并構(gòu)建全局變量Configuration*/
??????return?build(parser.parse());
????}?catch?(Exception?e)?{
??????throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?e);
????}?finally?{
??????ErrorContext.instance().reset();
??????try?{
????????inputStream.close();
??????}?catch?(IOException?e)?{
????????//?Intentionally?ignore.?Prefer?previous?error.
??????}
????}
?}
進入parser.parse()方法,,進一步分析
public?Configuration?parse()?{
????if?(parsed)?{
??????throw?new?BuilderException("Each?XMLConfigBuilder?can?only?be?used?once.");
????}
????parsed?=?true;
????parseConfiguration(parser.evalNode("/configuration"));
????return?configuration;
??}
??private?void?parseConfiguration(XNode?root)?{
????try?{
??????propertiesElement(root.evalNode("properties"));?//issue?#117?read?properties?first?//?讀取properties配置
??????typeAliasesElement(root.evalNode("typeAliases"));?//?讀取別名設置
??????pluginElement(root.evalNode("plugins"));?//?讀取插件設置
??????objectFactoryElement(root.evalNode("objectFactory"));?//?讀取對象工廠設置
??????objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));?//?讀取對象包裝工廠設置
??????settingsElement(root.evalNode("settings"));?//?讀取setting設置
??????environmentsElement(root.evalNode("environments"));?//?read?it?after?objectFactory?and?objectWrapperFactory?issue?#631?//?讀取環(huán)境設置
??????databaseIdProviderElement(root.evalNode("databaseIdProvider"));?//?讀取數(shù)據(jù)庫ID提供信息
??????typeHandlerElement(root.evalNode("typeHandlers"));??//?讀取類型轉(zhuǎn)換處理器
??????mapperElement(root.evalNode("mappers"));??//?解析mapper文件
????}?catch?(Exception?e)?{
??????throw?new?BuilderException("Error?parsing?SQL?Mapper?Configuration.?Cause:?"?+?e,?e);
????}
??}
此處僅分析
????"com.xiaobing.mapper.SysUserMapper"/>
分析mapperElement()方法
/**
???*?映射文件支持四種配置,package,resource,url,class四種
???*?如在mybatis-config.xml中配置
???*?
???"com.xiaobing.mapper.SysUserMapper"/>
???
???*?*/
??private?void?mapperElement(XNode?parent)?throws?Exception?{
????if?(parent?!=?null)?{
??????for?(XNode?child?:?parent.getChildren())?{
????????if?("package".equals(child.getName()))?{?//?若配置的是package,在講package下的所有mapper文件進行解析
??????????String?mapperPackage?=?child.getStringAttribute("name");
??????????configuration.addMappers(mapperPackage);
????????}?else?{
??????????String?resource?=?child.getStringAttribute("resource");
??????????String?url?=?child.getStringAttribute("url");
??????????String?mapperClass?=?child.getStringAttribute("class");
??????????if?(resource?!=?null?&&?url?==?null?&&?mapperClass?==?null)?{??//?若配置的是resource,在解析resource對應的mapper.xml
????????????ErrorContext.instance().resource(resource);
????????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);?//?獲取xml文件字節(jié)流
????????????XMLMapperBuilder?mapperParser?=?new?XMLMapperBuilder(inputStream,?configuration,?resource,?configuration.getSqlFragments());?//?構(gòu)建xml文件構(gòu)造器
????????????mapperParser.parse();?//?解析xml文件
??????????}?else?if?(resource?==?null?&&?url?!=?null?&&?mapperClass?==?null)?{?//?若配置的是url,在解析url對應的mapper.xml
????????????ErrorContext.instance().resource(url);
????????????InputStream?inputStream?=?Resources.getUrlAsStream(url);
????????????XMLMapperBuilder?mapperParser?=?new?XMLMapperBuilder(inputStream,?configuration,?url,?configuration.getSqlFragments());
????????????mapperParser.parse();
??????????}?else?if?(resource?==?null?&&?url?==?null?&&?mapperClass?!=?null)?{?//?若配置的是class,在解析class對應的mapper文件
????????????Class>?mapperInterface?=?Resources.classForName(mapperClass);
????????????configuration.addMapper(mapperInterface);?//?分析addMapper()方法
??????????}?else?{
????????????throw?new?BuilderException("A?mapper?element?may?only?specify?a?url,?resource?or?class,?but?not?more?than?one.");
??????????}
????????}
??????}
????}
??}
因為我采用的是class配置,所以分析configuration.addMapper()方法
?//?Configuration.java
??public??void?addMapper(Class?type)?{
????mapperRegistry.addMapper(type);
??} 繼續(xù)進入mapperRegistry.addMapper進行分析
//?MapperRegistry.java
public??void?addMapper(Class?type)?{
????if?(type.isInterface())?{?//?mapper接口
??????if?(hasMapper(type))?{?//?若mapper已被注冊
????????throw?new?BindingException("Type?"?+?type?+?"?is?already?known?to?the?MapperRegistry.");
??????}
??????boolean?loadCompleted?=?false;
??????try?{
????????knownMappers.put(type,?new?MapperProxyFactory(type));??//?注冊映射接口
????????//?It's?important?that?the?type?is?added?before?the?parser?is?run
????????//?otherwise?the?binding?may?automatically?be?attempted?by?the
????????//?mapper?parser.?If?the?type?is?already?known,?it?won't?try.
????????MapperAnnotationBuilder?parser?=?new?MapperAnnotationBuilder(config,?type);?//?生成注解構(gòu)造器
????????parser.parse();?//?解析mapper上的注解
????????loadCompleted?=?true;
??????}?finally?{
????????if?(!loadCompleted)?{
??????????knownMappers.remove(type);
????????}
??????}
????}
??}
knownMappers.put(type, new MapperProxyFactory
parser.parse()是對mapper文件進行解析的關鍵,繼續(xù)分析
//?MapperAnnotationBuilder.java
?//?解析配置文件
??public?void?parse()?{
????String?resource?=?type.toString();?//?接口的全限定名?class?com.test.userMapper
????if?(!configuration.isResourceLoaded(resource))?{??//?是否加載過
??????loadXmlResource();?//?在默認路徑下(默認和mapper接口同個包下),加載xml文件
??????configuration.addLoadedResource(resource);?//?設為該mapper配置文件已解析
??????assistant.setCurrentNamespace(type.getName());?//?設置構(gòu)建助力器當前命名空間?com.test.userMapper
??????parseCache();?//?解析CacheNamespace注解,構(gòu)建一個Cache對象,并保存到Mybatis全局配置信息中
??????parseCacheRef();?//解析CacheNamespace注解,引用CacheRef對應的Cache對象。
??????//?由此可知,當引入了 和 后,該命名空間的緩存對象變?yōu)榱薈acheRef引用的緩存對象
??????Method[]?methods?=?type.getMethods();?//?獲取方法
??????for?(Method?method?:?methods)?{
????????try?{
??????????if?(!method.isBridge())?{?//?issue?#237?若該方法不是橋接方法
????????????parseStatement(method);?//構(gòu)建MapperStatement對象,并添加到Mybatis全局配置信息中
??????????}
????????}?catch?(IncompleteElementException?e)?{
??????????//當出現(xiàn)未完成元素時,添加構(gòu)建Method時拋出異常的MethodResolver實例,到下個Mapper的解析時再次嘗試解析
??????????configuration.addIncompleteMethod(new?MethodResolver(this,?method));
????????}
??????}
????}
????parsePendingMethods();?//?解析未完成解析的Method
??}
通過上面的代碼注釋,可知,當解析mapper.java文件前,會先在同個文件夾下查看是否存在mapper.xml文件,若存在,則先解析mapper.xml文件。在解析mapper.xml文件時,若在mapper.xml中寫了緩存
//?MapperAnnotationBuilder.java
private?void?parseCache()?{
????//?獲取是否有@CacheNamespace?注解
????CacheNamespace?cacheDomain?=?type.getAnnotation(CacheNamespace.class);
????if?(cacheDomain?!=?null)?{
??????/*
??????*?構(gòu)建一個緩存對象,具體分析
??????*?*/
??????assistant.useNewCache(cacheDomain.implementation(),?cacheDomain.eviction(),?cacheDomain.flushInterval(),?cacheDomain.size(),?cacheDomain.readWrite(),?null);
????}
??}
//?mapperBuilderAssistant.java
public?Cache?useNewCache(Class?extends?Cache>?typeClass,?//?基本緩存類
??????Class?extends?Cache>?evictionClass,??//?緩存裝飾類
??????Long?flushInterval,?//?緩存刷新間隔
??????Integer?size,?//?緩存大小
??????boolean?readWrite,?//?緩存可讀寫
??????Properties?props)?{
????typeClass?=?valueOrDefault(typeClass,?PerpetualCache.class);?//?沒有設置則采用默認的PerpetualCache
????evictionClass?=?valueOrDefault(evictionClass,?LruCache.class);?//?沒有設置則采用默認的LruCache
????Cache?cache?=?new?CacheBuilder(currentNamespace)?//?命名空間作為緩存唯一ID
????????.implementation(typeClass)
????????.addDecorator(evictionClass)
????????.clearInterval(flushInterval)
????????.size(size)
????????.readWrite(readWrite)
????????.properties(props)
????????.build();
????configuration.addCache(cache);?//?加入到全局緩存
????currentCache?=?cache;?//?當前緩存設為cache,由此可知,緩存是mapper級別
????return?cache;
??}
此處是生成了二級緩存的地方,并設置當前mapper文件的緩存為這個生成的二級緩存。若沒有配置@CacheNamespaceRef,那木此mapper文件就使用了這個自己生成的二級緩存。那@CacheNamespaceRef是用來干嘛的?回到上面代碼處進行分析。
//?MapperAnnotationBuilder.java
?
??private?void?parseCacheRef()?{
????//?@CacheNamespaceRef?相當于 標簽
????CacheNamespaceRef?cacheDomainRef?=?type.getAnnotation(CacheNamespaceRef.class);
????if?(cacheDomainRef?!=?null)?{
??????assistant.useCacheRef(cacheDomainRef.value().getName());?//?構(gòu)建緩存引用,進入分析
????}
??}
public?Cache?useCacheRef(String?namespace)?{
????if?(namespace?==?null)?{
??????throw?new?BuilderException("cache-ref?element?requires?a?namespace?attribute.");
????}
????try?{
??????unresolvedCacheRef?=?true;
??????Cache?cache?=?configuration.getCache(namespace);?//?獲取被引用的緩存
??????if?(cache?==?null)?{?//被引用的緩存是否存在
????????throw?new?IncompleteElementException("No?cache?for?namespace?'"?+?namespace?+?"'?could?be?found.");
??????}
??????currentCache?=?cache;?//?設置當前緩存對象為被引用的緩存對象
??????unresolvedCacheRef?=?false;?//?標志設置為false,代表有緩存引用。
??????return?cache;
????}?catch?(IllegalArgumentException?e)?{
??????throw?new?IncompleteElementException("No?cache?for?namespace?'"?+?namespace?+?"'?could?be?found.",?e);
????}
??}
由上文可知,當配置了@CacheNamespaceRef和@CacheNamespace后,該mapper文件對應的緩存以@CacheNamespaceRef引用的緩存為準。這樣可是使得不同的mapper文件有相同的緩存。
4.2?緩存具體使用場景
上文說了,開啟二級緩存后,sqlSession中的Executor是CachingExecutor,查看生成CachingExecutor具體位置。繼續(xù)從那段測試代碼分析
SqlSession?sqlSession?=?factory.openSession();?//?生成SqlSession
List?userList?=?sqlSession.selectList("com.xiaobing.mapper.SysUserMapper.getSysUser");?//?執(zhí)行SysUserMapper類的getSysUser方法 debug進入DefaultSqlSessionfactory.openSession()方法
//?DefaultSqlSessionfactory.java
??public?SqlSession?openSession()?{
????return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);
??}
??...
??private?SqlSession?openSessionFromDataSource(ExecutorType?execType,?TransactionIsolationLevel?level,?boolean?autoCommit)?{
????Transaction?tx?=?null;
????try?{
??????final?Environment?environment?=?configuration.getEnvironment();?//?獲取當前配置設置的環(huán)境,有事務工廠,數(shù)據(jù)源
??????final?TransactionFactory?transactionFactory?=?getTransactionFactoryFromEnvironment(environment);?//?創(chuàng)建事務工廠
??????tx?=?transactionFactory.newTransaction(environment.getDataSource(),?level,?autoCommit);?//?事務類
??????final?Executor?executor?=?configuration.newExecutor(tx,?execType);?//?生成執(zhí)行器
??????return?new?DefaultSqlSession(configuration,?executor,?autoCommit);
????}?catch?(Exception?e)?{
??????closeTransaction(tx);?//?may?have?fetched?a?connection?so?lets?call?close()
??????throw?ExceptionFactory.wrapException("Error?opening?session.??Cause:?"?+?e,?e);
????}?finally?{
??????ErrorContext.instance().reset();
????}
??}
??....
分析Executor executor = configuration.newExecutor(tx, execType);此段代碼
//?Configuration.java
public?Executor?newExecutor(Transaction?transaction,?ExecutorType?executorType)?{
????executorType?=?executorType?==?null???defaultExecutorType?:?executorType;?//?默認為SimpleExecutor
????executorType?=?executorType?==?null???ExecutorType.SIMPLE?:?executorType;
????Executor?executor;
????.......
????if?(cacheEnabled)?{???//?若開啟二級緩存,則生成CachingExecutor
??????executor?=?new?CachingExecutor(executor);
????}
????.......
??}
當執(zhí)行查詢語句時,會執(zhí)行Executor的query()方法。分析CachingExecutor中query()方法究竟是怎樣使用二級緩存。
public??List?query(MappedStatement?ms,?Object?parameterObject,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)
??????throws?SQLException?{
????//?mapper.xml設置了或者mapper.java使用了二級緩存注解
????Cache?cache?=?ms.getCache();
????if?(cache?!=?null)?{
??????//?若該mapper文件中執(zhí)行的上一條語句是更新語句(增刪改),則會清空該mapper文件對應的二級緩存
??????flushCacheIfRequired(ms);
??????if?(ms.isUseCache()?&&?resultHandler?==?null)?{
????????ensureNoOutParams(ms,?parameterObject,?boundSql);
????????@SuppressWarnings("unchecked")??
????????List?list?=?(List)?tcm.getObject(cache,?key);?//?從二級緩存中獲取
????????if?(list?==?null)?{?//?若二級緩存中不存在
??????????list?=?delegate.?query(ms,?parameterObject,?rowBounds,?resultHandler,?key,?boundSql);?//?調(diào)用后續(xù)的Executor執(zhí)行語句,后續(xù)的Executor會繼續(xù)使用一級緩存。
??????????tcm.putObject(cache,?key,?list);?//?issue?#578.?Query?must?be?not?synchronized?to?prevent?deadlocks??//?放入二級緩存中
????????}
????????return?list;
??????}
????}
????return?delegate.?query(ms,?parameterObject,?rowBounds,?resultHandler,?key,?boundSql);?//?若沒開啟二級緩存,則調(diào)用后續(xù)的Executor執(zhí)行語句。后續(xù)的Executor會繼續(xù)使用一級緩存。
??}
?
?//?此處的update包括增刪改
?public?int?update(MappedStatement?ms,?Object?parameterObject)?throws?SQLException?{
????//?清空二級緩存
????flushCacheIfRequired(ms);
????return?delegate.update(ms,?parameterObject);
??}
通過上面分析可知,二級緩存的實現(xiàn)是mapper級別的。只要對這個mapper文件使用@CacheNamespace注解或?qū)膞ml使用
對這一mapper文件任何查詢語句進程操作的時候,都會使用到這個二級緩存。二級緩存就相當于在一級緩存上在加入一個緩存。二級緩存Cache的實現(xiàn)是在LruCache上在封裝了一層TransactionCache,為了防止臟數(shù)據(jù)的產(chǎn)生。感興趣的可以自行去查看。以上便是關于mybatis緩存的內(nèi)容。
5. 總結(jié)驗證
我們知道,二級緩存是mapper級別的,在mybatis初始化時便生成了。當此mapper文件中有更新語句時,才會刷新二級緩存。舉個例子,有MapperA.java和MapperB.java兩個文件,并都開啟了二級緩存,cacheA和cacheB。MapperA.java中有一條查詢語句select1,此查詢語句關聯(lián)了B的表。在第一次執(zhí)行MapperA.java中select1時,會從庫中取出數(shù)據(jù),并放入在cacheA中。當mapperB.java中如果有一條更新語句update2,執(zhí)行update2,會刷新二級緩存cacheB。但不會刷新cacheA,因為update2并不在MapperA.java中。那此時cacheA中存在的數(shù)據(jù)便是臟數(shù)據(jù)了。
其實也有解決辦法,即在MapperA.java中使用@CacheNamespaceRef = "mapperB.java".讓兩個文件公用同一個二級緩存。這樣就OK啦

粉絲福利:108本java從入門到大神精選電子書領取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
