- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- 解析Mybatis延遲加載問(wèn)題
MyBatis針對關(guān)聯(lián)表中的數據支持延遲加載。延遲加載其實(shí)就是將數據加載時(shí)機推遲,比如推遲嵌套查詢(xún)的執行時(shí)機。
延遲加載可以實(shí)現先查詢(xún)主表,按需實(shí)時(shí)做關(guān)聯(lián)查詢(xún),返回關(guān)聯(lián)表結果集,一定程度上提高了效率。
<settings> <!-- 啟用延遲加載特性,不配置默認關(guān)閉該特性--> <setting name="lazyLoadingEnabled" value="true" /> <!-- 按需加載: false:使用關(guān)聯(lián)屬性時(shí)才進(jìn)行加載; true加載對象,則加載所有屬性 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
lazyLoadingEnabled:是否啟用延遲加載,默認值為false,不啟用延遲加載。lazyLoadingEnabled屬性控制全局是否使用延遲加載,特殊關(guān)聯(lián)關(guān)系也可以通過(guò)嵌套查詢(xún)中fetchType屬性單獨配置(fetchType屬性值可以是lazy或者eager)
aggressiveLazyLoading:是否按需加載屬性,默認值false,lazyLoadingEnabled屬性啟用時(shí)只要加載對象,就會(huì )加載該對象的所有屬性;關(guān)閉該屬性則會(huì )按需加載,即使用到某關(guān)聯(lián)屬性時(shí),實(shí)時(shí)執行嵌套查詢(xún)加載該屬性
對一
<resultMap id="ExtResultMap" type="com.yan.entity.User" extends="BaseResultMap"> <association property="role" select="com.yan.dao.RoleMapper.selectByPrimaryKey" column="role_id"/> </resultMap>
如果不訪(fǎng)問(wèn)role屬性,則不會(huì )執行t_roles表的查詢(xún)。當訪(fǎng)問(wèn)role屬性時(shí)才會(huì )執行查詢(xún)操作,而且如果session關(guān)閉,則自動(dòng)新打開(kāi)session執行查詢(xún)
對多
<resultMap id="ExtResultMap" type="com.yan.entity.Role" extends="BaseResultMap"> <collection property="users" ofType="com.yan.entity.User" column="id" select="com.yan.dao.UserMapper.selectByRoleId"/> </resultMap>
MyBatis支持一二級緩存
Mybatis提供查詢(xún)緩存,如果緩存中有數據就不用從數據庫中獲取,用于減輕數據壓力,提高系統性能
緩存的重要性是不言而喻的。 使用緩存可以避免頻繁的與數據庫進(jìn)行交互, 尤其是在查詢(xún)越多、緩存命中率越高的情況下, 使用緩存對性能的提高更明顯。
mybatis也提供了對緩存的支持, 分為一級緩存和二級緩存。 但是在默認的情況下, 只開(kāi)啟一級緩存(一級緩存是對同一個(gè) SqlSession 而言的)
一級緩存即local cache本地緩存,作用域默認為sqlSession。當Session flush或close后該Session中的所有Cache將被清空
RoleMapper rm= MybatisSessionFactory.getMapper(RoleMapper.class); Role role=rm.selectByPrimaryKey(1L); System.out.println(role.getId()+":"+role.getName()); System.out.println("--------------------------------"); MybatisSessionFactory.closeSession(); rm= MybatisSessionFactory.getMapper(RoleMapper.class); role=rm.selectByPrimaryKey(1L); System.out.println(role.getId()+":"+role.getName());
兩次查詢(xún)操作,分別是2次sql語(yǔ)句,證明緩存是session級的。如果不關(guān)閉session,即使2次獲取Mapper執行的查詢(xún)仍舊只有一個(gè)sql語(yǔ)句
每個(gè)SqlSession中持有了Executor,每個(gè)Executor中有一個(gè)LocalCache。當用戶(hù)發(fā)起查詢(xún)時(shí),MyBatis根據當前執行的語(yǔ)句生成MappedStatement,在Local Cache進(jìn)行查詢(xún),如果緩存命中的話(huà),直接返回結果給用戶(hù),如果緩存沒(méi)有命中的話(huà),查詢(xún)數據庫,結果寫(xiě)入Local Cache,最后返回結果給用戶(hù)
開(kāi)發(fā)者只需在MyBatis的配置文件localCacheScope中可以設置使用一級緩存。共有兩個(gè)選項SESSION或者STATEMENT,默認是SESSION級別,即在一個(gè)MyBatis會(huì )話(huà)中執行的所有語(yǔ)句,都會(huì )共享這一個(gè)緩存。一種是STATEMENT級別,可以理解為緩存只對當前執行的這一個(gè)Statement有效。
UserMapper userMapper = MyBatisSessionFactory.getMapper(UserMapper.class); User user1=userMapper.loadById(1L); System.out.println(user1); SqlSession session = MyBatisSessionFactory.openSession(); session.clearCache();//清空緩存 ,后續查詢(xún)會(huì )發(fā)送SQL語(yǔ)句 User user2=userMapper.loadById(1L); System.out.println(user2); System.out.println(user1==user2); MyBatisSessionFactory.closeSession();
1、第一次發(fā)起查詢(xún)用戶(hù)id為1的用戶(hù)信息,先去找緩存中是否有id為1的用戶(hù)信息,如果沒(méi)有,從數據庫查詢(xún)用戶(hù)信息。得到用戶(hù)信息,將用戶(hù)信息存儲到一級緩存中。
2、如果中間sqlSession去執行commit操作(執行插入、更新、刪除),則會(huì )清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。
3、第二次發(fā)起查詢(xún)用戶(hù)id為1的用戶(hù)信息,先去找緩存中是否有id為1的用戶(hù)信息,緩存中有,直接從緩存中獲取用戶(hù)信息。
SqlSession對外提供了用戶(hù)和數據庫之間交互需要的所有方法,隱藏了底層的細節。默認實(shí)現類(lèi)是DefaultSqlSession
Executor: SqlSession向用戶(hù)提供操作數據庫的方法,但和數據庫操作有關(guān)的職責都會(huì )委托給Executor。
BaseExecutor是一個(gè)實(shí)現了Executor接口的抽象類(lèi),定義若干抽象方法,在執行的時(shí)候,把具體的操作委托給子類(lèi)進(jìn)行執行。
Cache: MyBatis中的Cache接口,提供了和緩存相關(guān)的最基本的操作
BaseExecutor成員變量之一的PerpetualCache,是對Cache接口最基本的實(shí)現,其實(shí)現非常簡(jiǎn)單,內部持有HashMap,對一級緩存的操作實(shí)則是對HashMap的操作。
1、為執行和數據庫的交互,首先需要初始化SqlSession,通過(guò)DefaultSqlSessionFactory開(kāi)啟SqlSession
2、在初始化SqlSesion時(shí),會(huì )使用Configuration類(lèi)創(chuàng )建一個(gè)全新的Executor,作為DefaultSqlSession構造函數的參數
3、SqlSession創(chuàng )建完畢后,根據Statment的不同類(lèi)型,會(huì )進(jìn)入SqlSession的不同方法中,如果是Select語(yǔ)句的話(huà),最后會(huì )執行到SqlSession的selectList
4、SqlSession把具體的查詢(xún)職責委托給了Executor。如果只開(kāi)啟了一級緩存的話(huà),首先會(huì )進(jìn)入BaseExecutor的query方法。
5、會(huì )先根據傳入的參數生成CacheKey,默認將MappedStatement的Id、sql的offset、Sql的limit、Sql本身以及Sql中的參數傳入了CacheKey這個(gè)類(lèi),最終構成CacheKey
6、如果查不到的話(huà),就從數據庫查,在queryFromDatabase中,會(huì )對localcache進(jìn)行寫(xiě)入。 在query方法執行的最后,會(huì )判斷一級緩存級別是否是STATEMENT級別,如果是的話(huà),就清空緩存,這也就是STATEMENT級別的一級緩存無(wú)法共享localCache的原因。
MyBatis一級緩存的生命周期和SqlSession一致。MyBatis一級緩存內部設計簡(jiǎn)單,只是一個(gè)沒(méi)有容量限定的HashMap,在緩存的功能性上有所欠缺。MyBatis的一級緩存最大范圍是SqlSession內部,有多個(gè)SqlSession或者分布式的環(huán)境下,數據庫寫(xiě)操作會(huì )引起臟數據,建議設定緩存級別為Statement。
MyBatis在開(kāi)啟一個(gè)數據庫會(huì )話(huà)時(shí),會(huì ) 創(chuàng )建一個(gè)新的SqlSession對象,SqlSession對象中會(huì )有一個(gè)新的Executor對象,Executor對象中持有一個(gè)新的PerpetualCache對象;當會(huì )話(huà)結束時(shí),SqlSession對象及其內部的Executor對象還有PerpetualCache對象也一并釋放掉。如果SqlSession調用了close()方法,會(huì )釋放掉一級緩存PerpetualCache對象,一級緩存將不可用;如果SqlSession調用了clearCache(),會(huì )清空PerpetualCache對象中的數據,但是該對象仍可使用;SqlSession中執行了任何一個(gè)update操作(update()、delete()、insert()) ,都會(huì )清空PerpetualCache對象的數據,但是該對象可以繼續使用
使用一級緩存的時(shí)候,因為緩存不能跨會(huì )話(huà)共享,不同的會(huì )話(huà)之間對于相同的數據可能有不一樣的緩存。在有多個(gè)會(huì )話(huà)或者分布式環(huán)境下,會(huì )存在臟數據的問(wèn)題。如果要解決這個(gè)問(wèn)題,就要用到二級緩存。
MyBatis 一級緩存(MyBaits 稱(chēng)其為 Local Cache)無(wú)法關(guān)閉,但是有兩種級別可選:
session級別的緩存,在同一個(gè) sqlSession 內,對同樣的查詢(xún)將不再查詢(xún)數據庫,直接從緩存中。
statement級別的緩存,session級別緩存不能獲取最新數據: 為了避免這個(gè)問(wèn)題,可以將一級緩存的級別設為 statement 級別的,這樣每次查詢(xún)結束都會(huì )清掉一級緩存。
由于不同的sqlSession之間的緩存數據區域不共享,如果使用多個(gè)SqlSession對數據庫進(jìn)行操作時(shí),就會(huì )出現臟數據
一級緩存中,其最大的共享范圍就是一個(gè)SqlSession內部,如果多個(gè)SqlSession之間需要共享緩存,則需要使用到二級緩存。開(kāi)啟二級緩存后,會(huì )使用CachingExecutor裝飾Executor,進(jìn)入一級緩存的查詢(xún)流程前,先在CachingExecutor進(jìn)行二級緩存的查詢(xún)
二級緩存開(kāi)啟后,同一個(gè)namespace下的所有操作語(yǔ)句,都影響著(zhù)同一個(gè)Cache,即二級緩存被多個(gè)SqlSession共享,是一個(gè)全局的變量。 當開(kāi)啟緩存后,數據的查詢(xún)執行的流程就是 二級緩存 -> 一級緩存 -> 數據庫。
二級緩存(全局緩存):基于namespace級別的緩存,一個(gè)namespace對應一個(gè)二級緩存
1、在MyBatis的配置文件中開(kāi)啟二級緩存。
cacheEnabled 全局性地開(kāi)啟或關(guān)閉所有映射器配置文件中已配置的任何緩存。
2、在MyBatis的映射XML中配置cache或者 cache-ref
<mapper namespace="com.yan.dao.RoleMapper"> <cache/>
cache-ref代表引用別的命名空間的Cache配置,兩個(gè)命名空間的操作使用的是同一個(gè)Cache。
1、測試二級緩存效果,不提交事務(wù),sqlSession1查詢(xún)完數據后,sqlSession2相同的查詢(xún)是否會(huì )從緩存中獲取數據。 可以看到,當sqlsession沒(méi)有調用commit()方法時(shí),二級緩存并沒(méi)有起到作用。
SqlSession session1=MybatisSessionFactory.getFactory().openSession(); SqlSession session2=MybatisSessionFactory.getSession(); RoleMapper rm1=session1.getMapper(RoleMapper.class); RoleMapper rm2=session2.getMapper(RoleMapper.class); Role r1=rm1.selectByPrimaryKey(1L); session1.commit(); //如果不進(jìn)行提交,則緩存無(wú)效 Role r2=rm2.selectByPrimaryKey(1L); System.out.println(r1==r2); //不是同一個(gè)對象,應該是對象的深克隆
2、測試二級緩存效果,當提交事務(wù)時(shí),sqlSession1查詢(xún)完數據后,sqlSession2相同的查詢(xún)是否會(huì )從緩存中獲取數據。 sqlsession2的查詢(xún),使用了緩存,緩存的命中率是0.5。
3、測試update操作是否會(huì )刷新該namespace下的二級緩存。 可以看到,在sqlSession3更新數據庫,并提交事務(wù)后,sqlsession2的StudentMapper namespace下的查詢(xún)走了數據庫,沒(méi)有走Cache。
4、驗證MyBatis的二級緩存不適應用于映射文件中存在多表查詢(xún)的情況。 通常我們會(huì )為每個(gè)單表創(chuàng )建單獨的映射文件,由于MyBatis的二級緩存是基于namespace的,多表查詢(xún)語(yǔ)句所在的namspace無(wú)法感應到其他namespace中的語(yǔ)句對多表查詢(xún)中涉及的表進(jìn)行的修改,引發(fā)臟數據問(wèn)題。
5、為了解決實(shí)驗4的問(wèn)題呢,可以使用Cache ref,讓ClassMapper引用StudenMapper命名空間,這樣兩個(gè)映射文件對應的Sql操作都使用的是同一塊緩存了。 不過(guò)這樣做的后果是,緩存的粒度變粗了,多個(gè)Mapper namespace下的所有操作都會(huì )對緩存使用造成影響。
<mapper namespace="com.yan.dao.UserMapper"> <cache-ref namespace="com.yan.dao.RoleMapper"/>
在一級緩存處理前,用CachingExecutor裝飾了BaseExecutor的子類(lèi),在委托具體職責給delegate之前,實(shí)現了二級緩存的查詢(xún)和寫(xiě)入功能
CachingExecutor的query方法,首先會(huì )從MappedStatement中獲得在配置初始化時(shí)賦予的Cache。
本質(zhì)上是裝飾器模式的使用,具體的裝飾鏈是SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。
到此這篇關(guān)于Mybatis的延遲加載問(wèn)題的文章就介紹到這了,更多相關(guān)Mybatis延遲加載內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站