Mybatis中的N+1問題與延遲加載


0.什么是N+1問題?

在查詢中一下子取出所有屬性,就會使數據庫多執行幾條毫無意義的SQL 。
實際中不需要把所有信息都加載進來,因為有些信息並不常用,加載它們會多執行幾條毫無用處的 SQL,導致數據庫資源的損耗和系統性能的下降。
假設現在有 N 個關聯關系完成了級聯,那么只要再加入一個關聯關系,就變成了 N+l個級聯,所有的級聯 SQL 都會被執行,顯然會有很多並不是我們關心的數據被取出,這樣會造成很大的資源浪費,這就是 N+l 問題,尤其是在那些需要高性能的互聯網系統中,這往往是不被允許的。
為了應對 N+l 問題, MyBatis 提供了延遲加載功能。

1.什么是延遲加載

MyBatis 支持延遲加載,把常用的級聯數據通過 SQL 直接查詢出來,而對於那些不常用的級聯數據不要取出,而是等待要用時才取出,這些不常用的級聯數據可以采用了延遲加載的功能

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

需求:

如果查詢訂單並且關聯查詢用戶信息。如果先查詢訂單信息即可滿足要求,當我們需要查詢用戶信息時再查下用戶信息。把對用戶信息的按需去查詢就是延遲加載。

延遲加載:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高數據庫性能,因為查詢單表要比關聯查詢多張表速度要快。

2.使用association實現延遲加載

2.1需求

查詢訂單並且關聯查詢用戶信息

2.2mapper.xml

需要定義兩個mapper的方法對應的statement。

(1)只查詢訂單信息

SELECT * FROM orders

在查詢訂單的statement中使用association去延遲加載(執行)下邊的statement(關聯查詢用戶信息)。

    <!-- 查詢訂單關聯查詢用戶 --> <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap"> SELECT * FROM orders </select>

(2)關聯查詢用戶信息

通過上邊查詢到的訂單信息中user_id去關聯查詢用戶信息

使用UserMapper.xml中的findUserById

    <select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{value} </select>

上邊先去執行findOrdersUserLazyLoading,當需要去查詢用戶的時候再去執行fingUserById,通過resultMap的定義將延遲加載執行配置起來。

2.3 延遲加載resultMap

使用association中的select指定延遲加載去執行的statement的id。

復制代碼
    <!-- 延遲加載的resultMap --> <resultMap type="joanna.yan.mybatis.entity.Orders" id="OrdersUserLazyLoadingResultMap"> <!-- 1.對訂單信息進行映射配置 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 2.實現對用戶信息進行延遲加載 --> <!-- select:指定延遲加載需要執行的statement的id(是根據user_id查詢用戶信息的statement) 要使用UserMapper.xml中findUserById完成根據用戶id(user_id)用戶信息的查詢, 如果findUserById不在本mapper中需要前邊加namespace。 column:訂單信息中關聯用戶信息查詢的列,是user_id 關聯查詢的sql理解為: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id)username, (SELECT sex FROM USER WHERE orders.user_id = user.id)sex FROM orders --> <association property="user" javaType="joanna.yan.mybatis.entity.User" select="joanna.yan.mybatis.mapper.UserMapper.findUserById" column="user_id"> </association> </resultMap>
復制代碼

2.4mapper.java

    //查詢訂單關聯查詢用戶,用戶信息時延遲加載 public List<Orders> findOrdersUserLazyLoading() throws Exception;

2.5測試

2.5.1測試思路

(1)執行上邊mapper方法(findOrdersUserLazyLoading),內部去調用joanna.yan.mybatis.mapper.OrdersCustomMapper中findOrdersUserLazyLoading只查詢orders信息(單表)。

(2)在程序中去遍歷上一步驟查詢出的List<Orders>,當我們調用Orders中的getUser()時,開始進行延遲加載。

(3)延遲加載,去調用UserMapper.xml中findUserById這個方法獲取用戶信息。

2.5.2延遲加載配置

mybatis默認沒有開啟延遲加載,需要在SqlMapConfig.xml中setting配置。

在mybatis核心配置文件中配置:

lazyLoadingEnabled、aggressiveLazyLoading

設置項

描述

允許值

默認值

lazyLoadingEnabled

全局性設置懶加載。如果設為‘false’,則所有相關聯的都會被初始化加載。

true | false

false

aggressiveLazyLoading

當設置為‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。

true | false

true

在SqlMapConfig.xml中配置:

復制代碼
     <!-- 全局配置參數,需要時再設置 --> <settings> <!-- 打開延遲加載的開關 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 將積極加載改為消極加載即按需要加載 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
復制代碼

2.5.3測試代碼

復制代碼
    @Test
    public void findOrdersUserLazyLoadingTest() throws Exception{ SqlSession sqlSession=sqlSessionFactory.openSession(); OrdersCustomMapper ordersCustomMapper=sqlSession.getMapper(OrdersCustomMapper.class); List<Orders> list=ordersCustomMapper.findOrdersUserLazyLoading(); for (Orders orders : list) { //執行getUser()去查詢用戶信息,這里實現按需加載 User user=orders.getUser(); System.out.println(user); } sqlSession.close(); }
復制代碼

2.6延遲加載思考

不使用mybatis提供的association及collection中的延遲加載功能,如何實現延遲加載?

實現方法如下:

定義兩個mapper方法:

(1)查詢訂單列表

(2)根據用戶id查詢用戶信息

    實現思路:先去查詢第一個mapper方法,獲取訂單信息列表

       在測試程序中,按需去調用第二個mapper方法去查詢用戶信息。

總之,使用延遲加載方法,先去查詢簡單的sql(最好單表,也可以關聯查詢),再去按需要加載關聯查詢的其它信息。

 

原文地址:http://www.cnblogs.com/Joanna-Yan/p/6953005.html


免責聲明!

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



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