淺析mybatis里的懶加載 - 通過懶加載來提高mybatis的查詢效率


一、需求背景

  需求:比如查詢訂單信息,需要查詢出是誰下單的,就是需要關聯查詢出用戶信息。

  第一種方法:我們直接關聯查詢出所有訂單和用戶的信息

  第二種方法:分步查詢,首先查詢出所有的訂單信息,然后如果需要用戶的信息,我們在根據查詢的訂單信息去關聯用戶信息

  對應分析:

  如果使用第一種方法:這里我們一次查詢出所有的信息,需要什么信息的時候直接從查詢的結果中篩選。但是如果訂單和用戶表都比較大的時候,這種關聯查詢肯定比較耗時。

  我們的需求是有時候需要關聯查詢用戶信息,這里不是一定需要用戶信息的。即有時候不需要查詢用戶信息,我們也查了,程序進行了多余的耗時操作。

  而第二種方法:這里兩步都是單表查詢,執行效率比關聯查詢要高很多。分為兩步,如果我們不需要關聯用戶信息,那么我們就不必執行第二步,程序沒有進行多余的操作。

  這第二種方法就是mybatis的懶加載。

二、什么是mybatis懶加載

1、懶加載是什么

  通俗的講就是按需加載,我們需要什么的時候再去進行什么操作。

  而且先從單表查詢,需要時再從關聯表去關聯查詢,能大大提高數據庫性能,因為查詢單表要比關聯查詢多張表速度要快。

  在mybatis中,resultMap可以實現高級映射(使用association、collection實現一對一及一對多映射),association、collection具備延遲加載功能。

  對象模型就是一個訂單中持有一個對用戶的引用。

  當查詢訂單信息時,暫時不加載用戶信息,就延遲加載(懶加載)。

2、什么情況下才能使用懶加載呢?

  上面的情況是無法實現懶加載的。因為是連接查詢,所以在查詢時只是執行了一次sql語句,就查詢所有的數據。

  這種情況可能出現延遲加載,第一次查詢結束之后,不在執行第二次查詢。

三、開啟懶加載

1、如何開啟懶加載

  查看文檔

<!-- 開啟懶加載配置 -->
<settings>
    <!-- 全局性設置懶加載。如果設為‘false',則所有相關聯的都會被初始化加載。 -->
    <setting name="lazyLoadingEnabled" value="true"/>

    <!-- 當設置為‘true'的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

2、測試查看具體查詢流程

  測試一下看具體流程是怎么樣的

@Test public void testLazy(){ String statement = "com.ys.lazyload.OrdersMapper.getOrderByOrderId"; //創建OrdersMapper對象,mybatis自動生成mapepr代理對象
    OrdersMapper orderMapper = session.getMapper(OrdersMapper.class); List<Orders> orders = orderMapper.getOrderByOrderId();//第一步
    for(Orders order : orders){ System.out.println(order.getUser());//第二步
 } session.close(); }

  當我們運行到第一步時,發出了第一次查詢所有訂單信息sql語句:select * from orders。注意只是查詢訂單信息,還沒有進行關聯查詢。

  當我們運行到第二步時,已經執行了一次for循環,因為我們需要用戶信息,故發出了根據用戶id查詢用戶信息的sql語句。

  注意:如果用戶信息有多條,這里並不會發出多條sql語句,這是由於mybatis的一級緩存的原因。

3、總結

(1)啟動懶加載,mybatis初始化返回類型的時候,會返回一個cglib代理對象,該對象的關聯對象(例如一對多,多對一)相關信息就會在loadpair里邊,並且添加到loadmap中,cglib對象會過濾get,set ,is,"equals", "clone", "hashCode", "toString"觸發方法,然后才會調用loadpair來加載關聯對象的值。所以我們必須在進行懶加載的時候必須要導入相應的jar包,不然會報錯。

  但是注意,新版的MyBatis已經不需要引入這2個包了。所以現在的一般都不需要引入了。

(2)其實通過上面的例子,我們很好理解懶加載的原理,就是按需加載。我們需要什么信息的時候再去查。而不是一次性查詢所有的。將復雜的關聯查詢分解成單表查詢,然后通過單表查詢的結果去關聯查詢。

(3)那么不用mybatis的懶加載我們也可是實現上面的例子:

  一、定義兩個mapper方法:1、查詢訂單列表;2、根據用戶 id 查詢用戶信息

  二、先去查詢第一個mapper方法,獲取訂單信息列表,然后放入到一個集合中

  三、如果需要用戶信息,那么在程序中,我們可以遍歷訂單信息,得到用戶id,然后通過id去查詢用戶信息。

  這與mybatis懶加載的區別就是,mybatis是在mapper.xml文件中配置好關聯關系了,我們直接調用就好了。而自己實現的原理就是手動去建立關聯關系。

四、懶加載具體分析研究

  在實際使用中,我們會經常性的涉及到多表聯合查詢,但是有時候,並不會立即用到所有的查詢結果,我來舉兩個例子:

  • 例如,查詢一批筆記本電腦的進貨明細,而不直接展示每列明細對應電腦配置或者價格等的詳細信息,等到用戶需要取出某筆記本相關的詳細信息的時候,再進行單表查詢
  • 再例如 ,銀行中,某個用戶擁有50個賬戶(打比方),再我們查詢這個而用戶的信息,這個用戶下所有賬戶的詳細信息很顯然,在使用的時候再查詢才是比較合理的

  針對這樣一種情況,延遲加載這一種機制就出現了,延遲加載(懶加載)顧名思義,就是對某種信息推遲加載,這樣的技術也就幫助我們實現了 “按需查詢” 的機制,在一對多,或者多對多的情況下

  既然提到了延遲加載,當然順便提一句立即加載,它的含義就是不管是否用戶需要,一調用,則馬上查詢,這種方式,適合與多對一,或者一對一的情況下。

1、如何實現延遲加載

  我們選擇 查詢賬戶,然后延遲加載用戶的信息

(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(); }

  可以看到,三條 SQL 語句都執行了,這是為什么呢?

  這是因為,我們在測試方法之前,需要開啟延遲加載功能

(3)延遲加載功能

  我們可以去官網,如何配置開啟這樣一個功能

  經過查閱文檔,我們知道了,如果想要開始延遲加載功能,就需要在總配置文件 SqlMapConfig.xml 中配置 setting 屬性,也就是將延遲加載 lazyLoadingEnable 的開關設置成 true ,由於是按需加載,所以還需要將積極加載修改為消極加載,也就是將 aggressiveLazyLoading 改為 false

  當然,由於我這里導入的 MyBatis 版本為 3.4.5 所以這個值默認就是 false 實際上不用設置也可以,不過我們還是寫出來

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="aggressiveLazyLoading" value="false"></setting>
</settings>

(4)再次測試,仍然只執行查詢方法

  這一次果然只執行了一條查詢 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()); } }

  可以看到,我們延遲加載的目的達到了。

2、總結

  上面的測試,我們已經實現了延遲加載,簡單的總結一下步驟:

①:執行對應的 mapper 方法,也就是上例中執行 Mapper 中 id 值為 findAll 的對應 SQL配置,只查詢到賬戶的信息

②:在程序中,遍歷查詢到的 accounts ,調用 getUser() 方法時,開始進行延遲加載

③:進行延遲加載,調用映射文件中 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,然后再按照需求加載查詢其他信息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM