<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          myBatis源碼解析-二級緩存的實現(xiàn)方式

          共 20467字,需瀏覽 41分鐘

           ·

          2020-09-20 19:49

          5點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          ? 作者?|??超人小冰?

          來源 |? urlify.cn/22am2u

          66套java從入門到精通實戰(zhàn)課程分享

          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?Map?cache?=?new?HashMap();
          ??//?唯一構(gòu)造方法(即緩存必須有ID)
          ??public?PerpetualCache(String?id)?{
          ????this.id?=?id;
          ??}
          ??//?獲取緩存的唯一ID
          ??public?String?getId()?{
          ????return?id;
          ??}
          ??//?獲取緩存的大小,實際就是hashmap的大小
          ??public?int?getSize()?{
          ????return?cache.size();
          ??}
          ??//?放入緩存,實際就是放入hashmap
          ??public?void?putObject(Object?key,?Object?value)?{
          ????cache.put(key,?value);
          ??}
          ??//?從緩存獲取,實際就是從hashmap中獲取
          ??public?Object?getObject(Object?key)?{
          ????return?cache.get(key);
          ??}
          ??//?從緩存移除
          ??public?Object?removeObject(Object?key)?{
          ????return?cache.remove(key);
          ??}
          ??//?hashmap清除數(shù)據(jù)方法
          ??public?void?clear()?{
          ????cache.clear();
          ??}
          ??//?暫時沒有其實現(xiàn)
          ??public?ReadWriteLock?getReadWriteLock()?{
          ????return?null;
          ??}
          ??//?緩存是否相同
          ??public?boolean?equals(Object?o)?{
          ????if?(getId()?==?null)?throw?new?CacheException("Cache?instances?require?an?ID.");
          ????if?(this?==?o)?return?true;?//?緩存本身,肯定相同
          ????if?(!(o?instanceof?Cache))?return?false;?//?沒有實現(xiàn)cache類,直接返回false

          ????Cache?otherCache?=?(Cache)?o;?//?強制轉(zhuǎn)換為cache
          ????return?getId().equals(otherCache.getId());?//?直接比較ID是否相等
          ??}
          ??//?獲取hashCode
          ??public?int?hashCode()?{
          ????if?(getId()?==?null)?throw?new?CacheException("Cache?instances?require?an?ID.");
          ????return?getId().hashCode();
          ??}

          }

          PerpetualCache 類其實是對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);
          ????}
          ??}

          此處僅分析標簽的解析,存在具體的mapper.xml文件中,分析mapperElement()方法。因為在mybatis-config.xml文件中關于標簽的值可配置package,resource,url,class等配置。如


          ????"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(type));這里很重要,是注冊mapper文件代理對象。此處只做緩存的解釋,不做注冊詳解,后面在分析sql執(zhí)行流程時單獨去分析。

          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中寫了緩存,也會生成二級緩存。若同時還在mapper.java文件里寫了@CacheNamespace注解。則會進行報錯,因為出現(xiàn)了兩個緩存。此時我們根據(jù)注解配置去分析。去分析parseCache()和parseCacheRef(),看配置了注解@CacheNamespace和CacheNamespaceRef之后緩存具體怎樣生成。

          //?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?typeClass,?//?基本緩存類
          ??????Class?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在生成時就會注冊一個mapper級別的緩存。在后續(xù)
          對這一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官方微信群



          感謝點贊支持下哈?

          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产永久性人人视频 | 中文av字幕 | 色五月激情五月 | 免费污污网站在线观看 | 美女日逼豆花视频 |