注:程式碼已託管在GitHub
上,地址是:https://github.com/Damaer/Mybatis-Learning
,專案是mybatis-16-lazyload
,需要自取,需要配置maven環境以及mysql
環境(sql
語句在resource
下的test.sql
中),覺得有用可以點個小星星。
docsify
文件地址在:https://damaer.github.io/Mybatis-Learning/#/
mybatis
的懶載入,也稱為延遲載入,是指在進行關聯查詢的時候,按照設定延遲規則推遲對關聯物件的select
查詢,延遲載入可以有效的減少資料庫壓力。延遲載入只對關聯物件有延遲設定,主載入物件都是直接執行查詢語句的
關聯物件載入型別
mybatis的關聯物件的查詢select語句的執行時機,可以分為3類,直接載入,侵入式載入與深度延遲載入。
1.直接載入
執行完主載入物件的select語句,馬上就會執行關聯物件的select語句。
2.侵入式延遲載入
執行對主載入物件的查詢時,不會執行關聯物件的查詢,但是當訪問主載入物件的詳情時,就會馬上執行關聯物件的select查詢,也就是說關聯物件的查詢執行,侵入到了主載入物件的詳情訪問中,可以理解為,將關聯物件的詳情侵入到主載入物件的詳情中,作為它的一部分出現了。
3.深度延遲載入
執行對主載入物件的查詢的時候,不會執行對關聯物件的查詢,訪問主載入物件的詳情的時候,也不會執行關聯物件的select查詢,只有當真正的訪問關聯物件的詳情的時候,才會執行對關聯物件的select查詢。
注意:延遲載入的最基本要求,關聯物件的查詢與主載入物件的查詢必須是分別放在兩個語句中的,不能使用多表連線查詢,因為多表連線查詢相當於把多張表連線成一張表的查詢,無法做到分開查詢,會一次性將表的內容查詢出來。
延遲載入,可以應用到一對多,一對一,多對一,多對多的關聯查詢中。
舉個例子:我們使用上一個demo
,查詢minister
與country
之間的關係,資料庫如下:
#建立資料庫
CREATE DATABASE `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
#建立資料表
CREATE TABLE `test`.`student` ( `sid` INT(10) NOT NULL AUTO_INCREMENT ,`sname` VARCHAR(20) NOT NULL ,PRIMARY KEY(`sid`)) ENGINE = MyISAM;
CREATE TABLE `test`.`course` ( `cid` INT(10) NOT NULL AUTO_INCREMENT ,`cname` VARCHAR(20) NOT NULL ,PRIMARY KEY(`cid`)) ENGINE = MyISAM;
CREATE TABLE `test`.`middle` (
`id` INT(10) NOT NULL AUTO_INCREMENT ,`studentId` INT(10) NOT NULL ,`courseId` INT(10) NOT NULL ,PRIMARY KEY(`id`)) ENGINE = MyISAM;
#初始化資料表
INSERT INTO `course` (`cid`, `cname`) VALUES ('1', 'JAVA') ;
INSERT INTO `course` (`cid`, `cname`) VALUES ('2', 'C++') ;
INSERT INTO `course` (`cid`, `cname`) VALUES ('3', 'JS') ;
INSERT INTO `student` (`sid`, `sname`) VALUES ('1', 'Jam') ;
INSERT INTO `student` (`sid`, `sname`) VALUES ('2', 'Lina') ;
INSERT INTO `middle` (`id`, `studentId`, `courseId`) VALUES ('1', '1', '1');
INSERT INTO `middle` (`id`, `studentId`, `courseId`) VALUES ('2', '1', '2');
INSERT INTO `middle` (`id`, `studentId`, `courseId`) VALUES ('3', '2', '1');
INSERT INTO `middle` (`id`, `studentId`, `courseId`) VALUES ('4', '2', '3');
與之對應的實體類:Country.class
public class Country {
private Integer cid;
private String cname;
private Set<Minister> ministers;
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getName() {
return cname;
}
public void setName(String cname) {
this.cname = cname;
}
public Set<Minister> getMinisters() {
return ministers;
}
public void setMinisters(Set<Minister> ministers) {
this.ministers = ministers;
}
@Override
public String toString() {
return "Country [cid=" + cid + ", cname=" + cname + ", ministers="
+ ministers + "]";
}
}
Minister.class
:領導人實體類
public class Minister {
private Integer mid;
private String mname;
@Override
public String toString() {
return "Minister [mid=" + mid + ", mname=" + mname + "]";
}
public Integer getMid() {
return mid;
}
public void setMid(Integer mid) {
this.mid = mid;
}
public String getMname() {
return mname;
}
public void setMname(String mname) {
this.mname = mname;
}
}
主配置檔案mybatis.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置資料庫檔案 -->
<properties resource="jdbc_mysql.properties">
</properties>
<settings>
<setting name="lazyLoadingEnabled" value="false"/>
<!--<setting name="aggressiveLazyLoading" value="false"/>-->
</settings>
<!-- 別名,對資料物件操作全名太長,需要使用別名 -->
<typeAliases>
<!--<typeAlias type="bean.Student" alias="Student"/>-->
<!--直接使用類名即可,對於整個包的路徑配置(別名),簡單快捷 -->
<package name="beans"/>
</typeAliases>
<!-- 配置執行環境 -->
<!-- default 表示預設使用哪一個環境,可以配置多個,比如開發時的測試環境,上線後的正式環境等 -->
<environments default="mysqlEM">
<environment id="mysqlEM">
<transactionManager type="JDBC">
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 註冊對映檔案 -->
<mappers>
<mapper resource="mapper/mapper.xml"/>
</mappers>
</configuration>
mapper.xml
檔案:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.ICountryDao">
<!-- resultMap 能解決欄位和屬性不一樣的問題 -->
<!-- 以後用得比較多 ,是因為可以使用延遲載入-->
<!-- 巢狀查詢 -->
<select id="selectMinisterByCountry" resultType="Minister">
select mid,mname from minister where countryId=#{ooo}
</select>
<resultMap type="Country" id="countryMapper">
<id column="cid" property="cid"/>
<result column="cname" property="cname"/>
<!-- country中有一個成員變數是ministers,它的泛型是Minister -->
<collection property="ministers"
ofType="Minister"
select="selectMinisterByCountry"
column="cid">
</collection>
</resultMap>
<select id="selectCountryById" resultMap="countryMapper">
select cid,cname
from country
where
cid=#{cid}
</select>
</mapper>
與之對應的sql
介面:
public interface ICountryDao {
Country selectCountryById(int cid);
}
使用到的工具類:
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
public static SqlSession getSqlSession() {
InputStream is;
try {
is = Resources.getResourceAsStream("mybatis.xml");
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
}
return sqlSessionFactory.openSession();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
直接載入查詢
關於懶載入的配置,我們只需要在mybatis.xml
檔案裡面使用<settings></settings>
就可以了,懶載入有一個總開關,lazyloadingEnabled
,只要置為false
就可以將延遲載入關掉,那就是直接載入查詢了。配置在<properties></properties>
與<typeAliases></typeAliases>
之間。
<properties resource="jdbc_mysql.properties">
</properties>
<settings>
<setting name="lazyLoadingEnabled" value="flase"/>
</settings>
<!-- 別名,對資料物件操作全名太長,需要使用別名 -->
<typeAliases>
<!--<typeAlias type="bean.Student" alias="Student"/>-->
<!--直接使用類名即可,對於整個包的路徑配置(別名),簡單快捷 -->
<package name="bean"/>
</typeAliases>
當單元測試是直接査country
物件的時候:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
}
結果是,我們可以看到兩條sql
,除了查詢country
之外,連同minister
關聯物件也一起查詢了,這就是直接載入,簡單粗暴,不管是否使用,都會先將關聯查詢載入:
[service] 2018-07-17 09:59:00,796 - dao.ICountryDao.selectCountryById -491 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 09:59:00,823 - dao.ICountryDao.selectCountryById -518 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 09:59:00,838 - dao.ICountryDao.selectMinisterByCountry -533 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ====> Preparing: select mid,mname from minister where countryId=?
[service] 2018-07-17 09:59:00,838 - dao.ICountryDao.selectMinisterByCountry -533 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ====> Parameters: 1(Integer)
[service] 2018-07-17 09:59:00,849 - dao.ICountryDao.selectMinisterByCountry -544 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - <==== Total: 2
[service] 2018-07-17 09:59:00,850 - dao.ICountryDao.selectCountryById -545 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
侵入式延遲載入
需要將延遲載入開關開啟(true
),同時也需要將侵入式載入開關開啟(true
)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressivelazyLoading" value="true"/>
</settings>
1.當我們只查詢country
的時候,只會執行country
的查詢,不會執行關聯查詢minister
:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
}
結果如下,只有一條sql:
[service] 2018-07-17 14:30:55,471 - dao.ICountryDao.selectCountryById -902 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 14:30:55,494 - dao.ICountryDao.selectCountryById -925 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:30:55,590 - dao.ICountryDao.selectCountryById -1021 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
當我們查詢country
的屬性,但是不查詢minister
屬性的時候:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
System.out.println(country.getCid());
}
結果如下,會載入到關聯物件minister
,這就是侵入式延遲載入:
[service] 2018-07-17 14:32:37,959 - dao.ICountryDao.selectCountryById -724 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 14:32:37,979 - dao.ICountryDao.selectCountryById -744 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:32:38,170 - dao.ICountryDao.selectCountryById -935 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
[service] 2018-07-17 14:32:38,171 - dao.ICountryDao.selectMinisterByCountry -936 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ==> Preparing: select mid,mname from minister where countryId=?
[service] 2018-07-17 14:32:38,171 - dao.ICountryDao.selectMinisterByCountry -936 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:32:38,173 - dao.ICountryDao.selectMinisterByCountry -938 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - <== Total: 2
1
深度延遲載入
需要將延遲載入開關lazyLoadingEnabled
開啟(true
),同時需要將侵入式載入開關aggressivelazyLoading
關閉(false
)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressivelazyLoading" value="false"/>
</settings>
1.當我們只查詢出country
的時候,只會查詢country
,而不會查詢minister
:
單元測試程式碼:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
}
[service] 2018-07-17 14:20:38,608 - dao.ICountryDao.selectCountryById -1271 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 14:20:38,631 - dao.ICountryDao.selectCountryById -1294 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:20:38,980 - dao.ICountryDao.selectCountryById -1643 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
2.當我們訪問country
的屬性的時候,也不會載入關聯查詢的minister
:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
System.out.println(country.getCid());
}
結果同樣是:
[service] 2018-07-17 14:24:03,004 - org.apache.ibatis.transaction.jdbc.JdbcTransaction -686 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@cb51256]
[service] 2018-07-17 14:24:03,030 - dao.ICountryDao.selectCountryById -712 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 14:24:03,078 - dao.ICountryDao.selectCountryById -760 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:24:03,160 - dao.ICountryDao.selectCountryById -842 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
3.當我們查詢country
屬性minister的時候:
@Test
public void TestselectCountryById(){
Country country=dao.selectCountryById(1);
System.out.println(country.getMinisters());
}
我們可以看到結果,執行了minister
的查詢,這個時候才是真正的載入minister
查詢
[service] 2018-07-17 14:26:55,913 - dao.ICountryDao.selectCountryById -1540 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Preparing: select cid,cname from country where cid=?
[service] 2018-07-17 14:26:55,943 - dao.ICountryDao.selectCountryById -1570 [main] DEBUG dao.ICountryDao.selectCountryById - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:26:56,161 - dao.ICountryDao.selectCountryById -1788 [main] DEBUG dao.ICountryDao.selectCountryById - <== Total: 1
[service] 2018-07-17 14:26:56,162 - dao.ICountryDao.selectMinisterByCountry -1789 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ==> Preparing: select mid,mname from minister where countryId=?
[service] 2018-07-17 14:26:56,163 - dao.ICountryDao.selectMinisterByCountry -1790 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - ==> Parameters: 1(Integer)
[service] 2018-07-17 14:26:56,168 - dao.ICountryDao.selectMinisterByCountry -1795 [main] DEBUG dao.ICountryDao.selectMinisterByCountry - <== Total: 2
[Minister [mid=2, mname=bbb], Minister [mid=1, mname=aaa]]
載入方式 | lazyLoadingEnabled | aggressiveLazyLoading |
---|---|---|
直接載入 | 必須是false,預設是false | 不管是什麼,只要lazyLoadingEnabled是false就是直接載入 |
侵入式延遲載入 | 必須是true | 必須是true |
深度延遲載入 | 必須是true | 必須是false,預設是false |
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java原始碼解析,JDBC,Mybatis,Spring,redis,分散式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。
平日時間寶貴,只能使用晚上以及週末時間學習寫作,關注我,我們一起成長吧~