延遲加載的含義
延遲加載又叫按需查詢(懶加載)叶撒,mybatis支持延遲加載,我們希望一次性把常用的級聯(lián)數(shù)據(jù)通過sql直接查詢出來,而對于那些不常用的的級聯(lián)數(shù)據(jù)不要取出蛆橡,而是等待要用的時候才取出,這些不常用的級聯(lián)數(shù)據(jù)可以采用延遲加載的功能矢空。
延遲加載的配置
在mybatis的settings配置中存在兩個元素可以配置級聯(lián)
<caption style="margin: 0px; padding: 0px;">延遲加載的配置項(xiàng)</caption>
| 配置項(xiàng) | 作用 | 配置選項(xiàng)說明 | 默認(rèn)值 |
| lazyLoadingEnabled | 延遲加載的全局開關(guān)航罗。當(dāng)開啟時,所有關(guān)聯(lián)對象都會延遲加載屁药。在特定關(guān)聯(lián)關(guān)系中粥血,可通過設(shè)置fetchType屬性來覆蓋該項(xiàng)的開關(guān)狀態(tài) | true|false | false |
| aggressiveLazyLoading | 當(dāng)啟用時柏锄,對任意延遲屬性的調(diào)用會使帶有延遲加載屬性的對象完整加載;反之复亏,則每種屬性按需加載趾娃。 | true|false | 版本3.4.1(包含)之前為true,之后為false |
lazyLoadingEnabled表示延遲加載的總開關(guān)缔御,如果將其設(shè)置為false抬闷,即使侵入式開關(guān)設(shè)置為true也不會生效。
aggressiveLazyLoading表示侵入式延遲加載開關(guān)耕突,在3.4.1版本之前默認(rèn)是true笤成,之后默認(rèn)是false。
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings></pre>
** 延遲加載的優(yōu)缺點(diǎn)**
優(yōu)點(diǎn):先從單表查詢眷茁,需要時再從關(guān)聯(lián)表去關(guān)聯(lián)查詢炕泳,大大提高數(shù)據(jù)庫的性能,因?yàn)椴樵儐伪硪汝P(guān)聯(lián)查詢多張表的速度快很多上祈。
缺點(diǎn):因?yàn)橹挥挟?dāng)需要用到數(shù)據(jù)時培遵,才會進(jìn)行數(shù)據(jù)庫查詢,這樣在大批量數(shù)據(jù)查詢時登刺,因?yàn)椴樵児ぷ饕残枰馁M(fèi)時間籽腕,所以可能造成用戶等待時間變長,造成用戶體驗(yàn)下降纸俭。
加載時機(jī)
直接加載:執(zhí)行完對主加載對象的 select 語句皇耗,馬上執(zhí)行對關(guān)聯(lián)對象的 select 查詢。
侵入式延遲: 執(zhí)行對主加載對象的查詢時掉蔬,不會執(zhí)行對關(guān)聯(lián)對象的查詢廊宪。但當(dāng)要訪問主加載對象的詳情屬性時,就會馬上執(zhí)行關(guān)聯(lián)對象的select查詢女轿。
深度延遲: 執(zhí)行對主加載對象的查詢時箭启,不會執(zhí)行對關(guān)聯(lián)對象的查詢。訪問主加載對象的詳情時也不會執(zhí)行關(guān)聯(lián)對象的select查詢蛉迹。只有當(dāng)真正訪問關(guān)聯(lián)對象的詳情時傅寡,才會執(zhí)行對關(guān)聯(lián)對象的 select 查詢。
如何實(shí)現(xiàn)延遲加載
MyBatis中對于延遲加載設(shè)置北救,只對于resultMap中的collection和association起作用荐操,可以應(yīng)用到一對一、一對多珍策、多對一托启、多對多的所有關(guān)聯(lián)關(guān)系查詢中。
需要注意的是攘宙, 延遲加載的應(yīng)用要求屯耸,關(guān)聯(lián)對象的查詢與主加載對象的查詢必須是分別進(jìn)行的 select 語句拐迁,不能是使用多表連接所進(jìn)行的select查詢。因?yàn)槎啾磉B接查詢疗绣,其實(shí)質(zhì)是對一張表的查詢线召,對由多個表連接后形成的一張表的查詢。會一次性將多張表的所有信息查詢出來多矮。
一對多的多表單獨(dú)查詢方式
修改之前的mapper.xml文件
<!--根據(jù)team的id查找player-->
<select id="selectPlayerByTeamId" resultType="Player"> select id,name from t_player WHERE tid=#{id} </select>
<!--關(guān)聯(lián)屬性映射關(guān)系-->
<!--集合的數(shù)據(jù)來自select查詢缓淹,該查詢的條件是selectTeamByIdAlone查詢出的id-->
<resultMap id="teamMapAlone" type="Team">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="playerList" ofType="Player" select="selectPlayerByTeamId" column="id"/>
</resultMap>
<select id="selectTeamByIdAlone" resultMap="teamMapAlone"> SELECT id,name FROM t_team where id=#{id} </select></pre>
修改之前的dao接口和測試類中的方法:
//dao接口
Team selectTeamByIdAlone(int id); //測試類
@Test public void selectTeamByIdAlone() {
Team team = teamDao.selectTeamByIdAlone(1);
}
執(zhí)行之后,可以看到在控制臺中分兩次發(fā)出了sql語句分別查詢t_team和t_player表塔逃。
開啟侵入式延遲
在mybatis.xml文件中添加下面內(nèi)容讯壶,注意該內(nèi)容的位置一定要在properties與typeAliases之間,在mybatis.xml配置文件中患雏,有些標(biāo)簽是需要按照順序編寫的:
<!--全局參數(shù)設(shè)置-->
<settings>
<!--延遲加載總開關(guān)-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--侵入式延遲加載開關(guān)-->
<!--3.4.1版本之前默認(rèn)是true鹏溯,之后默認(rèn)是false-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
將以上內(nèi)容配置好之后,再執(zhí)行上面測試類方法的時候淹仑,就會發(fā)現(xiàn)在控制臺中只發(fā)出了一條查詢t_team的sql語句,這是因?yàn)槲覀冮_啟了侵入式延遲加載開關(guān)肺孵,在java程序中并未訪問Team中的任何屬性匀借,所以mybatis不會去查詢其關(guān)聯(lián)的player對象數(shù)據(jù)。
修改后測試方法:
@Test public void selectTeamByIdAlone() {
Team team = teamDao.selectTeamByIdAlone(1);
System.out.println(team.getName());
}
再次執(zhí)行上面方法之后就會看到控制臺中發(fā)出兩條SQL語句分別查詢t_team和t_player,這是因?yàn)樵趈ava程序中訪問了Team的屬性name平窘,所以mybatis會將其關(guān)聯(lián)的player對象數(shù)據(jù)查詢出來吓肋。
開啟深度延遲加載
修改mybatis.xml文件:開啟總開關(guān),將aggressiveLazyLoading關(guān)閉即可瑰艘。
<!--全局參數(shù)設(shè)置-->
<settings>
<!--延遲加載總開關(guān)-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--侵入式延遲加載開關(guān)-->
<!--3.4.1版本之前默認(rèn)是true是鬼,之后默認(rèn)是false-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
繼續(xù)執(zhí)行上面的測試方法,可以看到控制臺中只打印了一條查詢t_team表的sql語句紫新。
修改測試方法如下:
@Test public void selectTeamByIdAlone() {
Team team = teamDao.selectTeamByIdAlone(1);
System.out.println(team.getName());
System.out.println(team.getPlayerList().size());
}</pre>
執(zhí)行上面測試方法之后均蜜,可以看到控制臺中打印了兩條sql語句分別查詢t_team和t_player,這說明當(dāng)開啟深度延遲后芒率,只要代碼中不使用player相關(guān)的數(shù)據(jù)囤耳,mybatis就不會進(jìn)行sql查詢,只有當(dāng)真正使用的時候才會去發(fā)出sql語句查詢偶芍。
在單個resultMap中使用延遲加載(fetchType)
上面都是通過在mybatis.xml文件中統(tǒng)一配置的深度延遲加載充择,倘若只希望某些查詢支持深度延遲加載的話可以在resultMap中的collection或association添加fetchType屬性,配置為lazy之后是開啟深度延遲匪蟀,配置eager是不開啟深度延遲椎麦。fetchType屬性將取代全局配置參數(shù)lazyLoadingEnabled的設(shè)置
延遲加載總結(jié)
通過上面的示例可以發(fā)現(xiàn)深度加載的方式最為懶,通過這種方式可以讓mybatis在執(zhí)行查詢的時候減少sql的查詢從而提高程序的執(zhí)行效率材彪,但是并不是所有場景下使用懶加載都能提高效率观挎,有些場景比如在查詢一對多時琴儿,就需要將一方和多方都查詢出來,這樣的話開啟懶加載反而有可能會拖慢程序的執(zhí)行效率
mybatis的延遲加載就是按需查詢键兜,在需要的時候進(jìn)行查詢凤类。
有兩張表:
圖書表(book):
圖書類型表(category):
他們之間通過類型id進(jìn)行關(guān)聯(lián),現(xiàn)在我要顯示圖書類型名普气,點(diǎn)擊類型名再顯示該類型下的所有圖書谜疤。
我們可以這樣做在類型實(shí)體類里面添加一個屬性存放該類型下的圖書
public class Category {
private int cid;
private String cname;
private List<Book> books;
//省略get set
}
一次性的把圖書類型和圖書查詢出來,Sql語句如下:
SELECT book.*,cname FROM book,category WHERE book.cid = category.cid
這樣做可以完成功能现诀,但是我們只是需要顯示圖書類型夷磕,點(diǎn)擊的時候才顯示該類型的圖書,如果能做到開始只查詢類型仔沿,點(diǎn)擊類型的時候再查詢該類型的圖書坐桩,就不需要進(jìn)行兩表聯(lián)查了,可以提高查詢的效率封锉,也比較節(jié)省內(nèi)存绵跷,這就是延遲加載。
延遲加載如何實(shí)現(xiàn)成福?
1. Category實(shí)體類同上
2. UserDao.xml:
<mapper namespace="cn.xh.dao.UserDao">
<select id="findCategoryWithLazingload" resultMap="categoryMap">
select * from category
</select>
<resultMap id="categoryMap" type="cn.xh.pojo.Category">
<id column="cid" property="cid"></id>
<result column="cname" property="cname"></result>
<collection property="books" column="cid" select="findBookWithLazy"></collection>
</resultMap>
<select id="findBookWithLazy" parameterType="int" resultType="cn.xh.pojo.Book">
select * from book where cid = #{cid}
</select>
</mapper>
只有我們點(diǎn)擊類型的時候才需要查詢該類型下的圖書碾局,所以這里我們沒有用兩表聯(lián)查,而是將類型表的查詢語句和圖書表的查詢語句分開奴艾。
重點(diǎn)來看下這個配置:
<collection property="books" column="cid" select="findBookWithLazy"></collection>
collection净当,association是支持延遲加載的,這里的select屬性表示要執(zhí)行的sql語句蕴潦,column表示執(zhí)行sql語句要傳的參數(shù)像啼,該參數(shù)為select *** **from category查詢出來的字段cid,property=”books”表示查詢出來的結(jié)果給到books屬性。
- 在mybatis的核心配置文件中配置:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
注意潭苞,這個配置必須寫在properties配置的后面忽冻,typeAliases的前面。
將lazyLoadingEnabled設(shè)置為true表示開啟延遲加載萄传,默認(rèn)為false.
將aggressiveLazyLoading設(shè)置為false表示按需加載甚颂,默認(rèn)為true
關(guān)于這兩個設(shè)置后面再詳加探討。
4. 測試:
@Test
public void testDo(){
SqlSession session = sqlSessionFactory.openSession();
UserDao u = session.getMapper(UserDao.class);//用動態(tài)代理的方式自動生成接口的實(shí)現(xiàn)類
List<Category> lst = u.findCategoryWithLazingload();
for (Category c : lst) {
System.out.println(c.getCname());
}
session.close();
}
執(zhí)行看看日志:
這段代碼只涉及到了圖書類型秀菱,并未涉及到圖書部分振诬,所以只執(zhí)行了select * from category從類型表中查詢出類型信息。
再來測試這段代碼:
@Test
public void testDo1(){
SqlSession session = sqlSessionFactory.openSession();
UserDao u = session.getMapper(UserDao.class);//用動態(tài)代理的方式自動生成接口的實(shí)現(xiàn)類
List<Category> lst = u.findCategoryWithLazingload();
for (Category c : lst) {
System.out.println(c.getCname());
}
List<Book> lstBook = lst.get(0).getBooks();
session.close();
}
執(zhí)行看看日志:
這里執(zhí)行了兩個sql語句:
Select * from category
Select * from book where cid = ?
對比這兩段代碼衍菱,可以看到赶么, 只有當(dāng)執(zhí)行List<Book> lstBook = lst.get(0).getBooks();這行代碼的時候才會去執(zhí)行sql語句Select * from book where cid = ?。
這就是延遲加載里的按需執(zhí)行sql語句脊串,只有在需要的時候才會去執(zhí)行辫呻。
回顧一下第三個步驟中的配置:
此時 lazyLoadingEnabled設(shè)置為true, aggressiveLazyLoading設(shè)置為false清钥,表示延遲加載開啟,按需加載也開啟放闺。分析一下在這種配置下代碼的每一步都做了什么:
1.List<Category> lst = u.findCategoryWithLazingload();執(zhí)行到這行代碼的時候從數(shù)據(jù)庫中查詢圖書類型的信息祟昭。
2.System.out.println(c.getCname());執(zhí)行這行代碼的時候因?yàn)閳D書類型信息已經(jīng)被查詢出來,所以不需要再和數(shù)據(jù)庫交互怖侦。
3.List<Book> lstBook = lst.get(0).getBooks();執(zhí)行這行代碼的時候從數(shù)據(jù)庫中查詢該類型下圖書的信息篡悟。
如果將lazyLoadingEnabled設(shè)置為true, aggressiveLazyLoading設(shè)置為true,表示延遲加載開啟,按需加載關(guān)閉匾寝,代碼每一步都做了什么呢搬葬?
1.List<Category> lst = u.findCategoryWithLazingload();執(zhí)行到這行代碼的時候從數(shù)據(jù)庫中查詢圖書類型的信息。
2.System.out.println(c.getCname());執(zhí)行這行代碼的時候艳悔,需要加載圖書類型的屬性“類型名”急凰,因?yàn)閷葱杓虞d關(guān)閉,所以此時會把Category的所有屬性都加載進(jìn)來猜年,包括List<Book> books,會去數(shù)據(jù)庫中查詢圖書的信息抡锈。
3.List<Bosok> lstBook = lst.get(0).getBooks();執(zhí)行這行代碼的時候因?yàn)閳D書的信息已經(jīng)被加載進(jìn)來,不需要查詢數(shù)據(jù)庫乔外。
如果將lazyLoadingEnabled設(shè)置為false企孩,相當(dāng)于關(guān)閉了延遲加載,此時無論aggressiveLazyLoading是true還是false都會在執(zhí)行
List<Category> lst = u.findCategoryWithLazingload();
的時候?qū)㈩愋秃蛨D書的信息都查詢出來袁稽。
總結(jié)一下:
一:延遲加載就是按需加載,在需要查詢的時候再去查詢擒抛,使用延遲加載可以避免表連接查詢推汽,表連接查詢比單表查詢的效率低,但是它需要多次與數(shù)據(jù)庫進(jìn)行交互歧沪,所以延遲加載并不是銀彈歹撒,使用需謹(jǐn)慎。
二:關(guān)于延遲加載有兩個重要的設(shè)置:lazyLoadingEnabled表示延遲加載是否開啟诊胞,如果設(shè)置為true表示開啟暖夭,此時還需要設(shè)置aggressiveLazyLoading為false,才能做到按需加載,如果aggressiveLazyLoading設(shè)置為true則按需加載關(guān)閉撵孤,此時只要加載了某個屬性就會將所有屬性都加載迈着。
lazyLoadingEnabled的默認(rèn)值為false
aggressiveLazyLoading的默認(rèn)值為true