www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]一、MyBatis緩存緩存就是內(nèi)存中的數(shù)據(jù),常常來(lái)自對(duì)數(shù)據(jù)庫(kù)查詢(xún)結(jié)果的保存。使用緩存,我們可以避免頻繁與數(shù)據(jù)庫(kù)進(jìn)行交互,從而提高響應(yīng)速度。MyBatis也提供了對(duì)緩存的支持,分為一級(jí)緩存和二級(jí)緩存,來(lái)看下下面這張圖:一級(jí)緩存是SqlSession級(jí)別的緩存。在操作數(shù)據(jù)庫(kù)時(shí)需要構(gòu)造...



一、MyBatis 緩存

緩存就是內(nèi)存中的數(shù)據(jù),常常來(lái)自對(duì)數(shù)據(jù)庫(kù)查詢(xún)結(jié)果的保存。使用緩存,我們可以避免頻繁與數(shù)據(jù)庫(kù)進(jìn)行交互,從而提高響應(yīng)速度。

MyBatis 也提供了對(duì)緩存的支持,分為一級(jí)緩存和二級(jí)緩存,來(lái)看下下面這張圖:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

一級(jí)緩存是 SqlSession 級(jí)別的緩存。在操作數(shù)據(jù)庫(kù)時(shí)需要構(gòu)造 SqlSession 對(duì)象,在對(duì)象中有一個(gè)數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲(chǔ)緩存數(shù)據(jù)。不同的是 SqlSession 之間的緩存數(shù)據(jù)區(qū)(HashMap)是互相不影響。二級(jí)緩存是 Mapper 級(jí)別的緩存,多個(gè) SqlSession 去操作同一個(gè) Mapper 的 sql 語(yǔ)句,多個(gè) SqlSession 可以共用二級(jí)緩存,二級(jí)緩存是跨 SqlSession 的。

相信大家看完這張圖和解釋心里應(yīng)該有個(gè)底了吧,這對(duì)后面分析 MyBatis 的一級(jí)、二級(jí)緩存機(jī)制很有幫助,那話(huà)不多說(shuō),我們直接進(jìn)入主題了。

二、一級(jí)緩存

2.1 內(nèi)部結(jié)構(gòu)

在我們的應(yīng)用運(yùn)行期間,我們可能在一次數(shù)據(jù)庫(kù)會(huì)話(huà)中,執(zhí)行多次查詢(xún)條件相同的 SQL,要你來(lái)設(shè)計(jì)的話(huà)你會(huì)如何考慮?沒(méi)錯(cuò),加緩存,MyBatis 也是這樣去處理的,如果是相同的 SQL 語(yǔ)句,會(huì)優(yōu)先命中一級(jí)緩存,避免直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行查詢(xún),造成數(shù)據(jù)庫(kù)的壓力,以提高性能。具體執(zhí)行過(guò)程如下圖所示:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制
SqlSession 是一個(gè)接口,提供了一些 CRUD 的方法,而 SqlSession 的默認(rèn)實(shí)現(xiàn)類(lèi)是 DefaultSqlSession,DefaultSqlSession 類(lèi)持有 Executor 接口對(duì)象,而 Executor 的默認(rèn)實(shí)現(xiàn)是 BaseExecutor 對(duì)象,每個(gè) BaseExecutor 對(duì)象都有一個(gè) PerpetualCache 緩存,也就是上圖的 ?Local Cache。

當(dāng)用戶(hù)發(fā)起查詢(xún)時(shí),MyBatis 根據(jù)當(dāng)前執(zhí)行的語(yǔ)句生成 MappedStatement,在 Local Cache 進(jìn)行查詢(xún),如果緩存命中的話(huà),直接返回結(jié)果給用戶(hù),如果緩存沒(méi)有命中的話(huà),查詢(xún)數(shù)據(jù)庫(kù),結(jié)果寫(xiě)入 Local Cache,最后返回結(jié)果給用戶(hù)。

啊,老周,關(guān)系還是有點(diǎn)抽象,感覺(jué)一直在套娃,沒(méi)關(guān)系,看下面這張圖你立馬豁然開(kāi)朗。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

2.2 一級(jí)緩存配置在 MyBatis 的配置文件中添加如下語(yǔ)句,就可以使用一級(jí)緩存。共有兩個(gè)選項(xiàng),SESSION 或者 STATEMENT,默認(rèn)是 SESSION 級(jí)別,即在一個(gè) MyBatis 會(huì)話(huà)中執(zhí)行的所有語(yǔ)句,都會(huì)共享這一個(gè)緩存。一種是 STATEMENT 級(jí)別,可以理解為緩存只對(duì)當(dāng)前執(zhí)行的這一個(gè) Statement 有效。

STATEMENT 級(jí)別粒度更細(xì),我們上面說(shuō)到,每個(gè) SqlSession 中持有了 Executor,SqlSession 的默認(rèn)實(shí)現(xiàn)類(lèi)是 DefaultSqlSession,DefaultSqlSession 類(lèi)持有 Executor 接口對(duì)象,而 Executor 的默認(rèn)實(shí)現(xiàn)是 BaseExecutor 對(duì)象,每個(gè) BaseExecutor 對(duì)象很多方法中都有傳 MappedStatement 對(duì)象。所有 STATEMENT 級(jí)別是針對(duì) SESSION 級(jí)別粒度更細(xì)的模式。

"localCacheScope"?value="SESSION"/>

三、一級(jí)緩存實(shí)驗(yàn)

下面老周通過(guò)幾組實(shí)驗(yàn)來(lái)帶你了解 MyBatis 一級(jí)緩存的效果,我們首先準(zhǔn)備一張簡(jiǎn)單的表 user,如下:

CREATE?TABLE?`user`?(
??`id`?int(11)?unsigned?NOT?NULL?AUTO_INCREMENT,
??`name`?varchar(64)?COLLATE?utf8_bin?DEFAULT?NULL,
??PRIMARY?KEY?(`id`)
)?ENGINE=InnoDB?AUTO_INCREMENT=2?DEFAULT?CHARSET=utf8?COLLATE=utf8_bin;
我們?cè)跍y(cè)試類(lèi)中加上帶有 @Before 標(biāo)注的 before 方法,省得每個(gè)單元測(cè)試方法都要重復(fù)獲取 sqlSession 以及 userMapper。

@Before
public?void?before()?throws?IOException?{
????InputStream?resourceAsStream?=?Resources.getResourceAsStream("SqlMapConfig.xml");
????sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(resourceAsStream);
????sqlSession?=?sqlSessionFactory.openSession(true);?//?自動(dòng)提交事務(wù)
????userMapper?=?sqlSession.getMapper(UserMapper.class);
}
3.1 實(shí)驗(yàn)1

開(kāi)啟一級(jí)緩存,范圍為會(huì)話(huà)級(jí)別,調(diào)用三次 firstLevelCacheFindUserById,代碼如下所示:

@Test
public?void?firstLevelCacheFindUserById()?{
????//?第一次查詢(xún)id為1的用戶(hù)
????User?user1?=?userMapper.findUserById(1);
????//?第二次查詢(xún)id為1的用戶(hù)
????User?user2?=?userMapper.findUserById(1);

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

????System.out.println(user1?==?user2);
}
控制臺(tái)日志輸出:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

我們可以看到,只有第一次真正查詢(xún)了數(shù)據(jù)庫(kù),后續(xù)的查詢(xún)使用了一級(jí)緩存。3.2 實(shí)驗(yàn)2

增加了對(duì)數(shù)據(jù)庫(kù)的修改操作,驗(yàn)證在一次數(shù)據(jù)庫(kù)會(huì)話(huà)中,如果對(duì)數(shù)據(jù)庫(kù)發(fā)生了修改操作,一級(jí)緩存是否會(huì)失效。

@Test
public?void?firstLevelCacheOfUpdate()?{
????//?第一次查詢(xún)id為1的用戶(hù)
????User?user1?=?userMapper.findUserById(1);
????System.out.println(user1);

????//?更新用戶(hù)
????User?user?=?new?User();
????user.setId(2);
????user.setUsername("tom");

????System.out.println("更新了"? ?userMapper.updateUser(user)? ?"個(gè)用戶(hù)");

????//?第二次查詢(xún)id為1的用戶(hù)
????User?user2?=?userMapper.findUserById(1);
????System.out.println(user2);

????System.out.println(user1?==?user2);
}
控制臺(tái)日志輸出:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

我們可以看到,在修改操作后執(zhí)行的相同查詢(xún),查詢(xún)了數(shù)據(jù)庫(kù),一級(jí)緩存失效。3.3 實(shí)驗(yàn)3

開(kāi)啟兩個(gè) SqlSession,在 sqlSession1 中查詢(xún)數(shù)據(jù),使一級(jí)緩存生效,在 sqlSession2 中更新數(shù)據(jù)庫(kù),驗(yàn)證一級(jí)緩存只在數(shù)據(jù)庫(kù)會(huì)話(huà)內(nèi)部共享。

@Test
public?void?firstLevelCacheOfScope()?{
????SqlSession?sqlSession2?=?sqlSessionFactory.openSession(true);
????UserMapper?userMapper2?=?sqlSession2.getMapper(UserMapper.class);

????System.out.println("userMapper讀取數(shù)據(jù):?"? ?userMapper.findUserById(1));
????System.out.println("userMapper讀取數(shù)據(jù):?"? ?userMapper.findUserById(1));

????//?更新用戶(hù)
????User?user?=?new?User();
????user.setId(1);
????user.setUsername("andy");
????System.out.println("userMapper2更新了"? ?userMapper2.updateUser(user)? ?"個(gè)用戶(hù)");

????System.out.println("userMapper讀取數(shù)據(jù):?"? ?userMapper.findUserById(1));
????System.out.println("userMapper2讀取數(shù)據(jù):?"? ?userMapper2.findUserById(1));
}
控制臺(tái)日志輸出:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

sqlSession2 更新了 id 為 1 的用戶(hù)的姓名,從 riemann 改為了 andy,但 session1 之后的查詢(xún)中,id 為 1 的學(xué)生的名字還是 riemann,出現(xiàn)了臟數(shù)據(jù),也證明了之前的設(shè)想,一級(jí)緩存只在數(shù)據(jù)庫(kù)會(huì)話(huà)內(nèi)部共享。

四、一級(jí)緩存工作流程以及源碼分析

4.1 工作流程

我們來(lái)看下一級(jí)緩存的時(shí)序圖:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

4.2 源碼分析看完上面的整體時(shí)序流程,我相信大家基本框架了解了,接下來(lái)針對(duì)這個(gè)框架再進(jìn)行源碼細(xì)化走讀。

SqlSession:對(duì)外提供了用戶(hù)和數(shù)據(jù)庫(kù)之間交互需要的所有方法,隱藏了底層的細(xì)節(jié)。默認(rèn)實(shí)現(xiàn)類(lèi)是 DefaultSqlSession。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

Executor:SqlSession 向用戶(hù)提供操作數(shù)據(jù)庫(kù)的方法,但和數(shù)據(jù)庫(kù)操作有關(guān)的職責(zé)都會(huì)委托給 Executor。
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

如下圖所示,Executor 有若干個(gè)實(shí)現(xiàn)類(lèi),為 Executor 賦予了不同的能力。
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

在一級(jí)緩存的源碼分析中,主要學(xué)習(xí) BaseExecutor 的內(nèi)部實(shí)現(xiàn)。BaseExecutor:BaseExecutor 是一個(gè)實(shí)現(xiàn)了 Executor 接口的抽象類(lèi),定義若干抽象方法,在執(zhí)行的時(shí)候,把具體的操作委托給子類(lèi)進(jìn)行執(zhí)行。

protected?abstract?int?doUpdate(MappedStatement?var1,?Object?var2)?throws?SQLException;
protected?abstract?List?doFlushStatements(boolean?var1)?throws?SQLException;
protected?abstract??List?doQuery(MappedStatement?var1,?Object?var2,?RowBounds?var3,?ResultHandler?var4,?BoundSql?var5)?throws?SQLException;
protected?abstract??Cursor?doQueryCursor(MappedStatement?var1,?Object?var2,?RowBounds?var3,?BoundSql?var4)?throws?SQLException;
在上文有提到對(duì) Local Cache 的查詢(xún)和寫(xiě)入是在 Executor 內(nèi)部完成的。在閱讀 BaseExecutor 的代碼后發(fā)現(xiàn) Local Cache 是 BaseExecutor 內(nèi)部的一個(gè)成員變量,如下代碼所示。

public?abstract?class?BaseExecutor?implements?Executor?{
????...
????protected?ConcurrentLinkedQueue?deferredLoads;
????protected?PerpetualCache?localCache;
????protected?PerpetualCache?localOutputParameterCache;
????...
}
Cache:MyBatis 中的 Cache 接口,提供了和緩存相關(guān)的最基本的操作,如下圖所示:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

有若干個(gè)實(shí)現(xiàn)類(lèi),使用裝飾器模式互相組裝,提供豐富的操控緩存的能力,部分實(shí)現(xiàn)類(lèi)如下圖所示:
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

BaseExecutor 成員變量之一的 PerpetualCache,是對(duì) Cache 接口最基本的實(shí)現(xiàn),其實(shí)現(xiàn)非常簡(jiǎn)單,內(nèi)部持有 HashMap,對(duì)一級(jí)緩存的操作實(shí)則是對(duì) HashMap 的操作。如下代碼所示:
public?class?PerpetualCache?implements?Cache?{
????private?final?String?id;
????private?Map?cache?=?new?HashMap();
????...
}
調(diào)研了一下,畫(huà)出工作流程圖:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

跟蹤到 PerpetualCache 中的 clear() 方法。
public?class?PerpetualCache?implements?Cache?{
????...
????private?Map?cache?=?new?HashMap();

????public?void?clear()?{
????????this.cache.clear();
????}
????...
}
也就是說(shuō)一級(jí)緩存的底層數(shù)據(jù)結(jié)構(gòu)就是 HashMap。所以說(shuō) cache.clear() 其實(shí)就是 map.clear(),也就是說(shuō),緩存其實(shí)是本地存放的一個(gè) map 對(duì)象,每一個(gè) SqlSession 都會(huì)存放一個(gè) map 對(duì)象的引用。

那么這個(gè) cache 是何時(shí)創(chuàng)建的呢?

根據(jù)上面我們畫(huà)的工作流程,明顯在 Executor 執(zhí)行器,執(zhí)行器用來(lái)執(zhí)行 sql 請(qǐng)求,而且清除緩存的方法也在 Executor 中執(zhí)行,去查看源碼,果真在里面。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

創(chuàng)建緩存 key 會(huì)經(jīng)過(guò)一系列的 update 方法,update 方法由一個(gè) cacheKey 這個(gè)對(duì)象來(lái)執(zhí)行的。這個(gè) update 方法最終由 updateList 的 list 來(lái)把六個(gè)值存進(jìn)去,對(duì)照上面的代碼,你應(yīng)該能理解下面六個(gè)值分別是啥了吧。
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

這里需要關(guān)注最后一個(gè)值 this.configuration.getEnvironment().getId(),這其實(shí)就是定義在 mybatis-config.xml 中的標(biāo)簽。如下:
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

那么問(wèn)題來(lái)了,創(chuàng)建緩存了,那具體在哪里用呢?我們一級(jí)緩存探究后,我們發(fā)現(xiàn)一級(jí)緩存更多的用于查詢(xún)操作。我們跟蹤到 query 方法:
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

如果查不到的話(huà),就從數(shù)據(jù)庫(kù)查,在 queryFromDatabase 中,會(huì)對(duì) localcache 進(jìn)行寫(xiě)入。在 query 方法執(zhí)行的最后,會(huì)判斷一級(jí)緩存級(jí)別是否是 STATEMENT 級(jí)別,如果是的話(huà),就清空緩存,這也就是 STATEMENT 級(jí)別的一級(jí)緩存無(wú)法共享 localCache 的原因。代碼如下所示:

if?(configuration.getLocalCacheScope()?==?LocalCacheScope.STATEMENT)?{
????clearLocalCache();
}
在源碼分析的最后,我們確認(rèn)一下,如果是 insert/delete/update 方法,緩存就會(huì)刷新的原因。

SqlSession 的 insert 方法和 delete 方法,都會(huì)統(tǒng)一走 update 的流程,代碼如下所示:

@Override
public?int?insert(String?statement,?Object?parameter)?{
????return?update(statement,?parameter);
}

@Override
public?int?delete(String?statement)?{
????return?update(statement,?null);
}
update 方法也是委托給了 Executor 執(zhí)行。BaseExecutor 的執(zhí)行方法如下所示:

@Override
public?int?update(MappedStatement?ms,?Object?parameter)?throws?SQLException?{
????ErrorContext.instance().resource(ms.getResource()).activity("executing?an?update").object(ms.getId());
????if?(closed)?{
??????throw?new?ExecutorException("Executor?was?closed.");
????}
????clearLocalCache();
????return?doUpdate(ms,?parameter);
}
每次執(zhí)行 update 前都會(huì)清空 localCache。

至此,一級(jí)緩存的工作流程講解以及源碼分析完畢。

五、一級(jí)緩存小結(jié)

  • MyBatis 一級(jí)緩存的生命周期和 SqlSession 一致。

  • MyBatis 一級(jí)緩存內(nèi)部設(shè)計(jì)簡(jiǎn)單,只是一個(gè)沒(méi)有容量限定的 HashMap,在緩存的功能性上有所欠缺。

  • MyBatis 的一級(jí)緩存最大范圍是 SqlSession 內(nèi)部,有多個(gè) SqlSession 或者分布式的環(huán)境下,數(shù)據(jù)庫(kù)寫(xiě)操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級(jí)別為 Statement。

六、二級(jí)緩存

在上文中提到的一級(jí)緩存中,其最大的共享范圍就是一個(gè) SqlSession 內(nèi)部,如果多個(gè) SqlSession 之間需要共享緩存,則需要使用到二級(jí)緩存。開(kāi)啟二級(jí)緩存后,會(huì)使用 CachingExecutor 裝飾 Executor,進(jìn)入一級(jí)緩存的查詢(xún)流程前,先在 CachingExecutor 進(jìn)行二級(jí)緩存的查詢(xún),具體的工作流程如下所示。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

二級(jí)緩存開(kāi)啟后,同一個(gè) namespace 下的所有操作語(yǔ)句,都影響著同一個(gè) Cache,即二級(jí)緩存被多個(gè) SqlSession 共享,是一個(gè)全局的變量。當(dāng)開(kāi)啟緩存后,數(shù)據(jù)的查詢(xún)執(zhí)行的流程就是 二級(jí)緩存 -> 一級(jí)緩存 -> 數(shù)據(jù)庫(kù)。

MyBatis 是默認(rèn)關(guān)閉二級(jí)緩存的,因?yàn)閷?duì)于增刪改操作頻繁的話(huà),那么二級(jí)緩存形同虛設(shè),每次都會(huì)被清空緩存。

6.1 二級(jí)緩存配置

和一級(jí)緩存默認(rèn)開(kāi)啟不一樣,二級(jí)緩存需要我們手動(dòng)開(kāi)啟。

6.1.1 首先在全局配置文件 SqlMapConfig.xml 文件中加入如下代碼:


<settings>
????<setting?name="cacheEnabled"?value="true"/>
settings>
6.1.2 其次在 UserMapper.xml 文件中開(kāi)啟二級(jí)緩存

mapper 代理模式:


<cache?/>
注解開(kāi)發(fā)模式:

@CacheNamespace(implementation?=?PerpetualCache.class)?//?開(kāi)啟二級(jí)緩存
public?interface?UserMapper?{
}
mapper 代理模式開(kāi)啟的二級(jí)緩存是一個(gè)空標(biāo)簽,其實(shí)這里可以配置,PerpetualCache 這個(gè)類(lèi)是 mybatis 默認(rèn)實(shí)現(xiàn)的二級(jí)緩存功能的類(lèi),我們不寫(xiě) type ,用 @CacheNamespace 直接默認(rèn) PerpetualCache 這個(gè)類(lèi),也可以去實(shí)現(xiàn) Cache 接口來(lái)自定義緩存。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制
6.2 實(shí)體類(lèi)實(shí)現(xiàn) Serializable 序列化接口

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

開(kāi)啟二級(jí)緩存后,還需要將要緩存的實(shí)體類(lèi)去實(shí)現(xiàn) Serializable 序列化接口,為了將緩存數(shù)據(jù)取出執(zhí)行反序列化操作,因?yàn)槎?jí)緩存數(shù)據(jù)存儲(chǔ)介質(zhì)多種多樣,不一定只存在內(nèi)存中,有可能存在硬盤(pán)中,如果我們?cè)偃〕鲞@個(gè)緩存的話(huà),就需要反序列化。所以 MyBatis 的所有 pojo 類(lèi)都要去實(shí)現(xiàn) Serializable 序列化接口。

七、二級(jí)緩存實(shí)驗(yàn)

7.1 實(shí)驗(yàn)1

測(cè)試二級(jí)緩存與 SqlSession 無(wú)關(guān)

@Test
public?void?secondLevelCache()?{
????SqlSession?sqlSession1?=?sqlSessionFactory.openSession();
????SqlSession?sqlSession2?=?sqlSessionFactory.openSession();

????UserMapper?userMapper1?=?sqlSession1.getMapper(UserMapper.class);
????UserMapper?userMapper2?=?sqlSession2.getMapper(UserMapper.class);

????//?第一次查詢(xún)id為1的用戶(hù)
????User?user1?=?userMapper1.findUserById(1);
????sqlSession1.close();?//?清空一級(jí)緩存
????System.out.println(user1);

????//?第二次查詢(xún)id為1的用戶(hù)
????User?user2?=?userMapper2.findUserById(1);
????System.out.println(user2);

????System.out.println(user1?==?user2);
}
控制臺(tái)日志輸出:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

第一次查詢(xún)時(shí),將查詢(xún)結(jié)果放入緩存中,第二次查詢(xún),即使 sqlSession1.close(); 清空了一級(jí)緩存,第二次查詢(xún)依然不發(fā)出 sql 語(yǔ)句。這里的你可能有個(gè)疑問(wèn),這里不是二級(jí)緩存了嗎?怎么 user1 與 user2 不相等?

這是因?yàn)槎?jí)緩存的是數(shù)據(jù),并不是對(duì)象。而 user1 與 user2 是兩個(gè)對(duì)象,所以地址值當(dāng)然也不想等。

7.2 實(shí)驗(yàn)2

測(cè)試執(zhí)行 commit(),二級(jí)緩存數(shù)據(jù)清空。

@Test
public?void?secondLevelCacheOfUpdate()?{
????SqlSession?sqlSession1?=?sqlSessionFactory.openSession();
????SqlSession?sqlSession2?=?sqlSessionFactory.openSession();
????SqlSession?sqlSession3?=?sqlSessionFactory.openSession();

????UserMapper?userMapper1?=?sqlSession1.getMapper(UserMapper.class);
????UserMapper?userMapper2?=?sqlSession2.getMapper(UserMapper.class);
????UserMapper?userMapper3?=?sqlSession3.getMapper(UserMapper.class);

????//?第一次查詢(xún)id為1的用戶(hù)
????User?user1?=?userMapper1.findUserById(1);
????sqlSession1.close();?//?清空一級(jí)緩存

????User?user?=?new?User();
????user.setId(3);
????user.setUsername("edgar");
????userMapper3.updateUser(user);
????sqlSession3.commit();

????//?第二次查詢(xún)id為1的用戶(hù)
????User?user2?=?userMapper2.findUserById(1);
????sqlSession2.close();

????System.out.println(user1?==?user2);
}
控制臺(tái)日志輸出:

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

我們可以看到,在 sqlSession3 更新數(shù)據(jù)庫(kù),并提交事務(wù)后,sqlsession2 的 UserMapper namespace 下的查詢(xún)走了數(shù)據(jù)庫(kù),沒(méi)有走 Cache。7.3 實(shí)驗(yàn)3

驗(yàn)證 MyBatis 的二級(jí)緩存不適應(yīng)用于映射文件中存在多表查詢(xún)的情況。

通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于 MyBatis 的二級(jí)緩存是基于 namespace 的,多表查詢(xún)語(yǔ)句所在的 namspace 無(wú)法感應(yīng)到其他 namespace 中的語(yǔ)句對(duì)多表查詢(xún)中涉及的表進(jìn)行的修改,引發(fā)臟數(shù)據(jù)問(wèn)題。

為了解決實(shí)驗(yàn)3的問(wèn)題呢,可以使用 Cache ref,讓 OrderMapper 引用 UserMapper 命名空間,這樣兩個(gè)映射文件對(duì)應(yīng)的 SQL 操作都使用的是同一塊緩存了。

不過(guò)這樣做的后果是,緩存的粒度變粗了,多個(gè) Mapper namespace 下的所有操作都會(huì)對(duì)緩存使用造成影響。

這里老周就不代碼演示了,有沒(méi)有感覺(jué)很雞肋,而且不熟用二級(jí)緩存的話(huà),像這種多表查詢(xún)的,很容易造成臟讀數(shù)據(jù)不一致,這在線上的話(huà)是致命的。

7.4 useCache 和 flushCache

useCache 是用來(lái)設(shè)置是否禁用二級(jí)緩存的,在 statement 中設(shè)置 useCache="false",可以禁用當(dāng)前 select 語(yǔ)句的二級(jí)緩存,即每次都會(huì)去數(shù)據(jù)庫(kù)查詢(xún)。如下:

<select?id="findAll"?resultMap="userMap"?useCache="false">
????select?*?from?user?u?left?join?orders?o?on?u.id?=?o.uid
select>
設(shè)置 statement 配置中的 flushCache=“true” 屬性,默認(rèn)情況下為 true,即刷新緩存,一般執(zhí)行完 commit 操作都需要刷新緩存,flushCache=“true” 表示刷新緩存,這樣可以避免增刪改操作而導(dǎo)致的臟讀問(wèn)題。默認(rèn)不要配置,如下:

<select?id="findAll"?resultMap="userMap"?useCache="false"?flushCache="true">
????select?*?from?user?u?left?join?orders?o?on?u.id?=?o.uid
select>

八、二級(jí)緩存源碼分析

MyBatis 二級(jí)緩存的工作流程和前文提到的一級(jí)緩存類(lèi)似,只是在一級(jí)緩存處理前,用 CachingExecutor 裝飾了 BaseExecutor 的子類(lèi),在委托具體職責(zé)給 delegate 之前,實(shí)現(xiàn)了二級(jí)緩存的查詢(xún)和寫(xiě)入功能,具體類(lèi)關(guān)系圖如下圖所示。

深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

源碼分析從 CachingExecutor 的 query 方法展開(kāi),首先會(huì)從 MappedStatement 中獲得在配置初始化時(shí)賦予的 Cache。
public??List?query(MappedStatement?ms,?Object?parameterObject,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?key,?BoundSql?boundSql)?throws?SQLException?{
????Cache?cache?=?ms.getCache();?//?首先會(huì)從?MappedStatement?中獲得在配置初始化時(shí)賦予的?Cache
????if?(cache?!=?null)?{
????????this.flushCacheIfRequired(ms);
????????if?(ms.isUseCache()?
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉