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ù)查詢結(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ù)查詢結(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ī)制很有幫助,那話不多說(shuō),我們直接進(jìn)入主題了。

二、一級(jí)緩存

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

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

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

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

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

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

2.2 一級(jí)緩存配置在 MyBatis 的配置文件中添加如下語(yǔ)句,就可以使用一級(jí)緩存。共有兩個(gè)選項(xiàng),SESSION 或者 STATEMENT,默認(rèn)是 SESSION 級(jí)別,即在一個(gè) MyBatis 會(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)類是 DefaultSqlSession,DefaultSqlSession 類持有 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è)試類中加上帶有 @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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

開啟兩個(gè) SqlSession,在 sqlSession1 中查詢數(shù)據(jù),使一級(jí)緩存生效,在 sqlSession2 中更新數(shù)據(jù)庫(kù),驗(yàn)證一級(jí)緩存只在數(shù)據(jù)庫(kù)會(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));

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

????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 的用戶的姓名,從 riemann 改為了 andy,但 session1 之后的查詢中,id 為 1 的學(xué)生的名字還是 riemann,出現(xiàn)了臟數(shù)據(jù),也證明了之前的設(shè)想,一級(jí)緩存只在數(shù)據(jù)庫(kù)會(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ì)外提供了用戶和數(shù)據(jù)庫(kù)之間交互需要的所有方法,隱藏了底層的細(xì)節(jié)。默認(rèn)實(shí)現(xiàn)類是 DefaultSqlSession。

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

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

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

在一級(jí)緩存的源碼分析中,主要學(xué)習(xí) BaseExecutor 的內(nèi)部實(shí)現(xiàn)。BaseExecutor:BaseExecutor 是一個(gè)實(shí)現(xiàn)了 Executor 接口的抽象類,定義若干抽象方法,在執(zhí)行的時(shí)候,把具體的操作委托給子類進(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 的查詢和寫入是在 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)類,使用裝飾器模式互相組裝,提供豐富的操控緩存的能力,部分實(shí)現(xiàn)類如下圖所示:
深入淺出?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)研了一下,畫出工作流程圖:

深入淺出?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ù)上面我們畫的工作流程,明顯在 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í)緩存更多的用于查詢操作。我們跟蹤到 query 方法:
深入淺出?MyBatis?的一級(jí)、二級(jí)緩存機(jī)制

如果查不到的話,就從數(shù)據(jù)庫(kù)查,在 queryFromDatabase 中,會(huì)對(duì) localcache 進(jìn)行寫入。在 query 方法執(zhí)行的最后,會(huì)判斷一級(jí)緩存級(jí)別是否是 STATEMENT 級(jí)別,如果是的話,就清空緩存,這也就是 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ù)寫操作會(huì)引起臟數(shù)據(jù),建議設(shè)定緩存級(jí)別為 Statement。

六、二級(jí)緩存

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

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

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

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

6.1 二級(jí)緩存配置

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

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


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

mapper 代理模式:


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

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

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

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

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

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

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

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

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

第一次查詢時(shí),將查詢結(jié)果放入緩存中,第二次查詢,即使 sqlSession1.close(); 清空了一級(jí)緩存,第二次查詢依然不發(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);

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

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

????//?第二次查詢id為1的用戶
????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 下的查詢走了數(shù)據(jù)庫(kù),沒(méi)有走 Cache。7.3 實(shí)驗(yàn)3

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

通常我們會(huì)為每個(gè)單表創(chuàng)建單獨(dú)的映射文件,由于 MyBatis 的二級(jí)緩存是基于 namespace 的,多表查詢語(yǔ)句所在的 namspace 無(wú)法感應(yīng)到其他 namespace 中的語(yǔ)句對(duì)多表查詢中涉及的表進(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í)緩存的話,像這種多表查詢的,很容易造成臟讀數(shù)據(jù)不一致,這在線上的話是致命的。

7.4 useCache 和 flushCache

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

<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í)緩存類似,只是在一級(jí)緩存處理前,用 CachingExecutor 裝飾了 BaseExecutor 的子類,在委托具體職責(zé)給 delegate 之前,實(shí)現(xiàn)了二級(jí)緩存的查詢和寫入功能,具體類關(guān)系圖如下圖所示。

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

源碼分析從 CachingExecutor 的 query 方法展開,首先會(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)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉