<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的緩存——一級(jí)緩存和源碼分析

          共 4508字,需瀏覽 10分鐘

           ·

          2020-11-16 23:04

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

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

          ? 作者?|??isdxh

          來(lái)源 |? urlify.cn/Ynq6ry

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

          什么是緩存?

          緩存就是存在內(nèi)存中的數(shù)據(jù),而內(nèi)存讀取都是非常快的 ,通常我們會(huì)把更新變動(dòng)不太頻繁查詢頻繁的數(shù)據(jù),在第一次從數(shù)據(jù)庫(kù)查詢出后,存放在緩存中,這樣就可以避免之后多次的與數(shù)據(jù)庫(kù)進(jìn)行交互,從而提升響應(yīng)速度。

          mybatis 也提供了對(duì)緩存的支持,分為:

          • 一級(jí)緩存

          • 二級(jí)緩存

          1. 一級(jí)緩存:
            每個(gè)sqlSeesion對(duì)象都有一個(gè)一級(jí)緩存,我們?cè)诓僮鲾?shù)據(jù)庫(kù)時(shí)需要構(gòu)造sqlSeesion對(duì)象,在對(duì)象中有一個(gè)HashMap用于存儲(chǔ)緩存數(shù)據(jù)。不同的sqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互不影響的。

          2. 二級(jí)緩存:
            二級(jí)緩存是mapper級(jí)別(或稱為namespace級(jí)別)的緩存,多個(gè)sqlSession去操作同一個(gè)Mapper的sql語(yǔ)句,多個(gè)SqlSession可以共用二級(jí)緩存,二級(jí)緩存是跨sqlSession的。


          一級(jí)緩存

          首先我們來(lái)開一級(jí)緩存,一級(jí)緩存是默認(rèn)開啟的,所以我們可以很方便來(lái)體驗(yàn)一下一級(jí)緩存。

          測(cè)試一、

          準(zhǔn)備一張表,有兩個(gè)字段id和username

          在測(cè)試類中:

          public?class?TestCache?{
          ????private?SqlSession?sqlSession;
          ????private?UserMapper?mapper;
          ????@Before
          ????public?void?before()?throws?IOException?{
          ????????InputStream?resourceAsStream?=?Resources.getResourceAsStream("sqlMapConfig.xml");
          ????????SqlSessionFactory?build?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
          ????????sqlSession?=?build.openSession();
          ????????mapper?=?sqlSession.getMapper(UserMapper.class);
          ????}

          ????@Test
          ????public?void?testFirst(){
          ????????//第一次查詢————首先去一級(jí)緩存中查詢
          ????????User?user1?=?mapper.findById(1);
          ????????System.out.println("======"+user1);
          ??//第二次查詢
          ????????User?user2?=?mapper.findById(1);
          ????????System.out.println("======"+user2);
          ????????
          ????????System.out.println(user1==user2);
          ????}
          }

          我們用同一個(gè)sqlSession分別根據(jù)id來(lái)查詢用戶,id都為1,之后再比較它們的地址值。來(lái)看一下結(jié)果:

          23:16:25,818?DEBUG?findById:159?-?==>??Preparing:?select?*?from?user?where?id=??
          23:16:25,862?DEBUG?findById:159?-?==>?Parameters:?1(Integer)
          23:16:25,894?DEBUG?findById:159?-?<==??????Total:?1
          ======User{id=1,?username='lucy'}
          ======User{id=1,?username='lucy'}
          true

          我們發(fā)現(xiàn)只打印了一條SQL,同時(shí)它們的地址值一致。

          說(shuō)明第一次查詢,緩存中沒(méi)有,然后從數(shù)據(jù)庫(kù)中查詢——執(zhí)行SQL,然后存入緩存,第二次查詢時(shí)發(fā)現(xiàn)緩存中有了,所以直接從緩存中取出,不再執(zhí)行SQL了。

          我們剛才提到,一級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)是一個(gè)hashmap,也就是說(shuō)有key有value。
          value就是我們查詢出的結(jié)果,key是由多個(gè)值組成的:

          • statementid?:namespace.id組成

          • params:查詢時(shí)傳入的參數(shù)

          • boundsql:mybatis底層的對(duì)象,它封裝著我們要執(zhí)行的sql

          • rowbounds:分頁(yè)對(duì)象

          • ...還有一些會(huì)在源碼分析中道明

          測(cè)試二、

          我們現(xiàn)在修改一下,我們?cè)诓樵兊谝淮谓Y(jié)果后,修改一下數(shù)據(jù)庫(kù)的值,然后再進(jìn)行第二次查詢,我們來(lái)看一下查詢結(jié)果。id=1 的username為lucy

          @Test
          ????public?void?testFirst(){
          ????????//第一次查詢
          ????????User?user1?=?mapper.findById(1);
          ????????System.out.println("======"+user1);

          ????????//修改id為1的username
          ????????User?updateUser?=?new?User();
          ????????updateUser.setId(1);
          ????????updateUser.setUsername("李思");
          ????????mapper.updateUser(updateUser);
          ????????//手動(dòng)提交事務(wù)
          ????????sqlSession.commit();

          ????????//第二次查詢
          ????????User?user2?=?mapper.findById(1);
          ????????System.out.println("======"+user2);

          ????????System.out.println(user1==user2);
          ????}

          在提交事務(wù)的地方打一個(gè)斷點(diǎn),可以看到執(zhí)行了兩條sql,一個(gè)是查詢id為1,一個(gè)是修改id為1的username

          最終結(jié)果:

          23:50:15,933?DEBUG?findById:159?-?==>??Preparing:?select?*?from?user?where?id=??
          23:50:15,976?DEBUG?findById:159?-?==>?Parameters:?1(Integer)
          23:50:16,002?DEBUG?findById:159?-?<==??????Total:?1
          ======User{id=1,?username='lucy',?roleList=null,?orderList=null}
          23:50:16,003?DEBUG?updateUser:159?-?==>??Preparing:?update?user?set?username=??where?id?=??
          23:50:16,005?DEBUG?updateUser:159?-?==>?Parameters:?李思(String),?1(Integer)
          23:50:16,016?DEBUG?updateUser:159?-?<==????Updates:?1
          23:53:18,316?DEBUG?JdbcTransaction:70?-?Committing?JDBC?Connection?[com.mysql.jdbc.JDBC4Connection@421e361]
          23:53:22,306?DEBUG?findById:159?-?==>??Preparing:?select?*?from?user?where?id=??
          23:53:22,306?DEBUG?findById:159?-?==>?Parameters:?1(Integer)
          23:53:22,307?DEBUG?findById:159?-?<==??????Total:?1
          ======User{id=1,?username='李思',?roleList=null,?orderList=null}


          我們看到,最終打印了3條sql,再進(jìn)行修改后的第二次查詢也打印了。

          說(shuō)明在第二次查詢時(shí)在緩存中找不到所對(duì)應(yīng)的key了。在進(jìn)行修改操作時(shí),會(huì)刷新緩存

          我們也可以通過(guò)sqlSession.clearCache();手動(dòng)刷新一級(jí)緩存

          總結(jié):

          • 一級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)時(shí)HashMap

          • 不同的SqlSession的一級(jí)緩存互不影響

          • 一級(jí)緩存的key是由多個(gè)值組成的,value就是其查詢結(jié)果

          • 增刪改操作會(huì)刷新一級(jí)緩存

          • 通過(guò)sqlSession.clearCache()手動(dòng)刷新一級(jí)緩存

          一級(jí)緩存源碼分析:

          我們?cè)诜治鲆患?jí)緩存之前帶著一些疑問(wèn)來(lái)讀代碼

          1. 一級(jí)緩存是什么?真的是上面說(shuō)的HashMap嗎?

          2. 一級(jí)緩存什么時(shí)候被創(chuàng)建?

          3. 一級(jí)緩存的工作流程是怎么樣的?

          1. 一級(jí)緩存到底是什么?

          之前說(shuō)不同的SqlSession的一級(jí)緩存互不影響,所以我從SqlSession這個(gè)類入手

          可以看到,org.apache.ibatis.session.SqlSession中有一個(gè)和緩存有關(guān)的方法——clearCache()刷新緩存的方法,點(diǎn)進(jìn)去,找到它的實(shí)現(xiàn)類DefaultSqlSession

          @Override
          ??public?void?clearCache()?{
          ????executor.clearLocalCache();
          ??}

          再次點(diǎn)進(jìn)去executor.clearLocalCache(),再次點(diǎn)進(jìn)去并找到其實(shí)現(xiàn)類BaseExecutor

          @Override
          ??public?void?clearLocalCache()?{
          ????if?(!closed)?{
          ??????localCache.clear();
          ??????localOutputParameterCache.clear();
          ????}
          ??}

          進(jìn)入localCache.clear()方法。進(jìn)入到了org.apache.ibatis.cache.impl.PerpetualCache類中

          package?org.apache.ibatis.cache.impl;
          import?java.util.HashMap;
          import?java.util.Map;
          import?java.util.concurrent.locks.ReadWriteLock;
          import?org.apache.ibatis.cache.Cache;
          import?org.apache.ibatis.cache.CacheException;
          /**
          ?*?@author?Clinton?Begin
          ?*/
          public?class?PerpetualCache?implements?Cache?{
          ??private?final?String?id;

          ??private?Map?cache?=?new?HashMap();

          ??public?PerpetualCache(String?id)?{
          ????this.id?=?id;
          ??}

          ??//省略部分...
          ??@Override
          ??public?void?clear()?{
          ????cache.clear();
          ??}
          ??//省略部分...
          }

          我們看到了PerpetualCache類中有一個(gè)屬性private Map cache = new HashMap(),很明顯它是一個(gè)HashMap,我們所調(diào)用的.clear()方法,實(shí)際上就是調(diào)用的Map的clear方法

          得出結(jié)論:

          一級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)確實(shí)是HashMap

          2. 一級(jí)緩存什么時(shí)候被創(chuàng)建?

          我們進(jìn)入到org.apache.ibatis.executor.Executor
          看到一個(gè)方法CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)?,見名思意是一個(gè)創(chuàng)建CacheKey的方法
          找到它的實(shí)現(xiàn)類和方法org.apache.ibatis.executor.BaseExecuto.createCacheKey

          我們分析一下創(chuàng)建CacheKey的這塊代碼:

          public?CacheKey?createCacheKey(MappedStatement?ms,?Object?parameterObject,?RowBounds?rowBounds,?BoundSql?boundSql)?{
          ????if?(closed)?{
          ??????throw?new?ExecutorException("Executor?was?closed.");
          ????}
          ????//初始化CacheKey
          ????CacheKey?cacheKey?=?new?CacheKey();
          ????//存入statementId
          ????cacheKey.update(ms.getId());
          ????//分別存入分頁(yè)需要的Offset和Limit
          ????cacheKey.update(rowBounds.getOffset());
          ????cacheKey.update(rowBounds.getLimit());
          ????//把從BoundSql中封裝的sql取出并存入到cacheKey對(duì)象中
          ????cacheKey.update(boundSql.getSql());
          ????//下面這一塊就是封裝參數(shù)
          ????List?parameterMappings?=?boundSql.getParameterMappings();
          ????TypeHandlerRegistry?typeHandlerRegistry?=?ms.getConfiguration().getTypeHandlerRegistry();

          ????for?(ParameterMapping?parameterMapping?:?parameterMappings)?{
          ??????if?(parameterMapping.getMode()?!=?ParameterMode.OUT)?{
          ????????Object?value;
          ????????String?propertyName?=?parameterMapping.getProperty();
          ????????if?(boundSql.hasAdditionalParameter(propertyName))?{
          ??????????value?=?boundSql.getAdditionalParameter(propertyName);
          ????????}?else?if?(parameterObject?==?null)?{
          ??????????value?=?null;
          ????????}?else?if?(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))?{
          ??????????value?=?parameterObject;
          ????????}?else?{
          ??????????MetaObject?metaObject?=?configuration.newMetaObject(parameterObject);
          ??????????value?=?metaObject.getValue(propertyName);
          ????????}
          ????????cacheKey.update(value);
          ??????}
          ????}
          ????//從configuration對(duì)象中(也就是載入配置文件后存放的對(duì)象)把EnvironmentId存入
          ????????/**
          ?????*?????"development">
          ?????*?????????"development">?//就是這個(gè)id
          ?????*?????????????
          ?????*?????????????type="JDBC">
          ?????*?????????????
          ?????*?????????????type="POOLED">
          ?????*?????????????????"driver"?value="${jdbc.driver}"/>
          ?????*?????????????????"url"?value="${jdbc.url}"/>
          ?????*?????????????????"username"?value="${jdbc.username}"/>
          ?????*?????????????????"password"?value="${jdbc.password}"/>
          ?????*?????????????
          ?????*?????????
          ?????*?????
          ?????*/
          ????if?(configuration.getEnvironment()?!=?null)?{
          ??????//?issue?#176
          ??????cacheKey.update(configuration.getEnvironment().getId());
          ????}
          ????//返回
          ????return?cacheKey;
          ??}

          我們?cè)冱c(diǎn)進(jìn)去cacheKey.update()方法看一看

          /**
          ?*?@author?Clinton?Begin
          ?*/
          public?class?CacheKey?implements?Cloneable,?Serializable?{
          ??private?static?final?long?serialVersionUID?=?1146682552656046210L;
          ??public?static?final?CacheKey?NULL_CACHE_KEY?=?new?NullCacheKey();
          ??private?static?final?int?DEFAULT_MULTIPLYER?=?37;
          ??private?static?final?int?DEFAULT_HASHCODE?=?17;

          ??private?final?int?multiplier;
          ??private?int?hashcode;
          ??private?long?checksum;
          ??private?int?count;
          ??//值存入的地方
          ??private?transient?List?updateList;
          ??//省略部分方法......
          ??//省略部分方法......
          ??public?void?update(Object?object)?{
          ????int?baseHashCode?=?object?==?null???1?:?ArrayUtil.hashCode(object);?
          ????count++;
          ????checksum?+=?baseHashCode;
          ????baseHashCode?*=?count;
          ????hashcode?=?multiplier?*?hashcode?+?baseHashCode;
          ????//看到把值傳入到了一個(gè)list中
          ????updateList.add(object);
          ??}
          ?
          ??//省略部分方法......
          }

          我們知道了那些數(shù)據(jù)是在CacheKey對(duì)象中如何存儲(chǔ)的了。下面我們返回createCacheKey()方法。

          Ctrl+鼠標(biāo)左鍵 點(diǎn)擊方法名,查詢有哪些地方調(diào)用了此方法

          我們進(jìn)入BaseExecutor,可以看到一個(gè)query()方法:

          這里我們很清楚的看到,在執(zhí)行query()方法前,CacheKey方法被創(chuàng)建了

          3. 一級(jí)緩存的執(zhí)行流程

          我們可以看到,創(chuàng)建CacheKey后調(diào)用了query()方法,我們?cè)俅吸c(diǎn)進(jìn)去:

          在執(zhí)行SQL前如何在一級(jí)緩存中找不到Key,那么將會(huì)執(zhí)行sql,我們來(lái)看一下執(zhí)行sql前后會(huì)做些什么,進(jìn)入

          list?=?queryFromDatabase(ms,?parameter,?rowBounds,?resultHandler,?key,?boundSql);


          分析一下:

          private??List?queryFromDatabase(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
          ????List?list;
          ????//1.?把key存入緩存,value放一個(gè)占位符
          ?localCache.putObject(key,?EXECUTION_PLACEHOLDER);
          ????try?{
          ??????//2.?與數(shù)據(jù)庫(kù)交互
          ??????list?=?doQuery(ms,?parameter,?rowBounds,?resultHandler,?boundSql);
          ????}?finally?{
          ??????//3.?如果第2步出了什么異常,把第1步存入的key刪除
          ??????localCache.removeObject(key);
          ????}
          ??????//4.?把結(jié)果存入緩存
          ????localCache.putObject(key,?list);
          ????if?(ms.getStatementType()?==?StatementType.CALLABLE)?{
          ??????localOutputParameterCache.putObject(key,?parameter);
          ????}
          ????return?list;
          ??}

          至此,我們思路就非常的清晰了。

          結(jié)論:

          在執(zhí)行sql前,會(huì)首先根據(jù)CacheKey查詢緩存中有沒(méi)有,如果有,就處理緩存中的參數(shù),如果沒(méi)有,就執(zhí)行sql,執(zhí)行sql后把結(jié)果存入緩存。

          一級(jí)緩存源碼分析結(jié)論:

          1. 一級(jí)緩存的數(shù)據(jù)結(jié)構(gòu)是一個(gè)HashMap,它的value就是查詢結(jié)果,它的key是CacheKeyCacheKey中有一個(gè)list屬性,statementId,params,rowbounds,sql等參數(shù)都存入到了這個(gè)list

          2. 一級(jí)緩存在調(diào)用query()方法前被創(chuàng)建。并傳入到query()方法中

          3. 會(huì)首先根據(jù)CacheKey查詢緩存中有沒(méi)有,如果有,就處理緩存中的參數(shù),如果沒(méi)有,就執(zhí)行sql,執(zhí)行sql后把結(jié)果存入緩存。


          粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取

          ???

          ?長(zhǎng)按上方微信二維碼?2 秒
          即可獲取資料



          感謝點(diǎn)贊支持下哈?

          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                    伊人一区 | 麻豆操逼视频 | 狠狠躁日日躁XXXXAAAA | 免费高清亚洲视频 | 蜜臀久久久 |