MyBatis 如何實現延遲加載

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,然後再按照需求加載查詢其他信息

結尾

如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支持!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公衆號

在這裏的我們素不相識,卻都在爲了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章