MyBatis 延遲加載
引言
前面一篇文章,介紹了多表查詢,在實際使用中,我們會經常性的涉及到多表聯合查詢,但是有時候,並不會立即用到所有的查詢結果,我來舉兩個例子:
- 例如,查詢一批筆記本電腦的進貨明細,而不直接展示每列明細對應電腦配置或者價格等的詳細信息,等到用戶需要取出某筆記本相關的詳細信息的時候,再進行單表查詢
- 再例如 ,銀行中,某個用戶擁有50個賬戶(打比方),再我們查詢這個而用戶的信息,這個用戶下所有賬戶的詳細信息很顯然,在使用的時候再查詢纔是比較合理的
針對這樣一種情況,延遲加載這一種機制就出現了,延遲加載(懶加載)顧名思義,就是對某種信息推遲加載,這樣的技術也就幫助我們實現了 “按需查詢” 的機制,在一對多,或者多對多的情況下
既然提到了延遲加載,當然順便提一句立即加載,它的含義就是不管是否用戶需要,一調用,則馬上查詢,這種方式,適合與多對一,或者一對一的情況下
(一) 必要準備
首先,配置基本的環境,然後我們首先在數據庫準備兩張表
User表
CREATE TABLE USER (
`id` INT(11)NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL COMMENT '用戶名',
`telephone` VARCHAR(11) NOT NULL COMMENT '手機',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`gender` CHAR(1) DEFAULT NULL COMMENT '性別',
`address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Account表
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '編號',
`UID` int(11) default NULL COMMENT '用戶編號',
`MONEY` double default NULL COMMENT '金額',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然後分別創建出其對應的實體類
User類
public class User implements Serializable {
private Integer id;
private String username;
private String telephone;
private Date birthday;
private String gender;
private String address;
//一對多關係映射,主表實體應該包含從表實體的集合引用
private List<Account> accounts;
...... 請補充 get set 和 toString 方法
}
Account類
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//從表實體應該包含一個主表實體的對象引用
private User user;
...... 請補充 get set 和 toString 方法
}
UserMapper.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="cn.ideal.mapper.UserMapper">
<!-- 定義User的resultMap-->
<resultMap id="userAccountMap" type="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="telephone" column="telephone"></result>
<result property="birthday" column="birthday"></result>
<result property="gender" column="gender"></result>
<result property="address" column="address"></result>
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!-- 查詢所有用戶 並且顯示對應賬戶信息 -->
<select id="findAll" resultMap="userAccountMap">
SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT OUTER JOIN account a on u.id = a.uid;
</select>
<!-- 根據id查詢用戶 -->
<select id="findById" parameterType="INT" resultType="User">
select * from user where id = #{uid}
</select>
</mapper>
兩個接口中創建對應方法
public interface AccountMapper {
/**
* 查詢所有賬戶
* @return
*/
List<Account> findAll();
}
public interface UserMapper {
/**
* 查詢所有用戶信息,同時顯示出該用戶下的所有賬戶
*
* @return
*/
List<User> findAll();
/**
* 根據id查詢用戶信息
* @param userId
* @return
*/
User findById(Integer userId);
}
(一) 延遲加載代碼實現
首先,給大家演示一下,我們之前一對一查詢用戶的方式,同時會將用戶對應所有的賬戶信息,也查詢出來
/**
* 測試查詢所有
*/
@Test
public void testFindAll() {
List<User> users= userMapper.findAll();
for (User user : users) {
System.out.println("---------------------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
效果:
這種方式是通過 SQL 語句,以及resultMap將 用戶和賬戶的信息同時查詢出來
那麼如何實現我們上面所說的延遲加載呢?
這次我們選擇 查詢賬戶,然後延遲加載用戶的信息
(1) 修改AccountMapper.xml
首先需要修改的就是賬戶的映射配置文件,可以看到我們在查詢時,依舊定義了一個 resultMap 先封裝了 Account ,然後通過association 進行關聯 User,其中使用的就是 select 和 column 實現了延遲加載用戶信息
- select 用來指定延遲加載所需要執行的 SQL 語句,也就是指定 某個SQL映射文件中的某個select標籤對的 id,在這裏我們指定了用戶中通過id查詢信息的方法
- column 是指關聯的用戶信息查詢的列,在這裏也就是關聯的用戶的主鍵即,id
<mapper namespace="cn.ideal.mapper.AccountMapper">
<!-- 定義封裝 Account和User 的resultMap -->
<resultMap id="userAccountMap" type="Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 配置封裝 User 的內容
select:查詢用戶的唯一標識
column:用戶根據id查詢的時候,需要的參數值
-->
<association property="user" column="uid" javaType="User" select="cn.ideal.mapper.UserMapper.findById"></association>
</resultMap>
<!-- 根據查詢所有賬戶 -->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM account
</select>
</mapper>
(2) 第一次測試代碼
我們只執行一下賬戶的查詢所有方法,看一下,是否能夠實現我們的效果
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
(3) 執行效果
可以看到,三條 SQL 語句都執行了,這是爲什麼呢?
這是因爲,我們在測試方法之前,需要開啓延遲加載功能
(4) 延遲加載功能
我們可以去官網,如何配置開啓這樣一個功能
經過查閱文檔,我們知道了,如果想要開始延遲加載功能,就需要在總配置文件 SqlMapConfig.xml 中配置 setting 屬性,也就是將延遲加載 lazyLoadingEnable
的開關設置成 teue ,由於是按需加載,所以還需要將積極加載修改爲消極加載,也就是將 aggressiveLazyLoading
改爲 false
當然,由於我這裏導入的 MyBatis 版本爲 3.4.5 所以這個值默認就是 false 實際上不用設置也可以,不過我們還是寫出來
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
注意:如果有使用typeAliases配置別名的話一定要將 typeAliases 標籤放在後面
(5) 再次測試
仍然只執行查詢方法
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
執行效果
這一次果然只執行了一條查詢 account 的命令
那麼當用戶想要查看到,每個賬戶對應下的用戶的時候呢?這也就是按需查詢,只需要在測試時,加入對應獲取方法就可以了
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
for (Account account : accounts){
System.out.println("----------------------------");
System.out.println(account);
System.out.println(account.getUser());
}
}
執行一下
可以看到,我們延遲加載的目的達到了
總結
上面的測試,我們已經實現了延遲加載,簡單的總結一下步驟:
①:執行對應的 mapper 方法,也就是上例中執行 Mapper 中 id 值爲 findAll 的對應 SQL配置,只查詢到賬戶的信息
-
②:在程序中,遍歷查詢到的 accounts ,調用 getUser() 方法時,開始進行延遲加載
List<Account> accounts = accountMapper.findAll();
③:進行延遲加載,調用映射文件中 id 值爲 findById 的對應 SQL配置,獲取到對應用戶的信息
可以看到,我們之前通過使用 左外連接等的 SQL書寫方式,直接就可以查詢到多張表
SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT OUTER JOIN account a on u.id = a.uid;
但是我們可以通過延遲加載,實現我們按需查詢的需求,綜上所述,在使用的時候,先執行簡單的 SQL,然後再按照需求加載查詢其他信息
結尾
如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支持!
如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公衆號
在這裏的我們素不相識,卻都在爲了自己的夢而努力 ❤
一個堅持推送原創開發技術文章的公衆號:理想二旬不止