首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 關聯查詢全解析:嵌套查詢 vs 嵌套結果,N+1 問題與優化策略

     編輯:Java 相關知識

場景:User (一) ↔ Order (多)

User { id, name, age, List<Order> orders }
Order { id, name, user_id }

方案一:嵌套查詢(Nested Select)—— 簡單但隱藏 N+1 陷阱

映射配置

<resultMap id="userMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <result property="age" column="age"/>
  <collection property="orders" ofType="Order" column="id" select="selectOrdersByUserId"/>
</resultMap>

<select id="selectOrdersByUserId" parameterType="int" resultType="Order">
  SELECT id, name FROM `order` WHERE user_id = #{id}
</select>

<select id="findUserById" resultMap="userMap" parameterType="int">
  SELECT * FROM `user` WHERE id = #{id}
</select>

執行流程

  1. 執行 findUserById 查用戶
  2. 對結果集每行用戶,執行 selectOrdersByUserId 查訂單

優點:SQL 簡單、可重用、映射清晰。

致命缺點:N+1 問題 —— 查 1 個用戶需 2 次查詢;查 100 個用戶需 101 次查詢。數據庫訪問次數 = 結果集行數 + 1,高併發下極易拖垮資料庫。

方案二:嵌套結果(Nested Result / Join Query)—— 一次查詢,內存組裝

映射配置

<resultMap id="userAutoMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <result property="age" column="age"/>
  <collection property="orders" ofType="Order">
    <id property="id" column="order_id"/>
    <result property="name" column="order_name"/>
  </collection>
</resultMap>

<select id="findAllUsersWithOrders" resultMap="userAutoMap">
  SELECT u.id, u.name, u.age, o.id AS order_id, o.name AS order_name
  FROM `user` u
  LEFT JOIN `order` o ON o.user_id = u.id
</select>

執行流程

  1. 執行 LEFT JOIN,一次性獲取扁平化結果集
  2. MyBatis 根據 <id> 去重,將同一用戶的多行訂單聚合到 orders 集合

優點:只查 1 次庫,無 N+1 問題,大數據量下效能碾壓嵌套查詢。

缺點:SQL 複雜、結果集冗餘(用戶字段重複)、內存組裝開銷大、分頁困難。

多對一(Order → User)同理

<resultMap id="orderMap" type="Order">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <association property="user" column="user_id" javaType="User" select="selectUserById"/> <!-- 嵌套查詢 -->
  <!-- 或 -->
  <association property="user"> <id property="id" column="user_id"/> ... </association> <!-- 嵌套結果 -->
</resultMap>
  • association:一對一/多對一(單個對象)
  • collection:一對多/多對多(集合)

選型決策樹

  1. 結果集行數小(< 100)、關聯表較小、需重用子查詢 → 嵌套查詢可接受
  2. 結果集大、高併發、關聯表大 → 必須嵌套結果(JOIN)
  3. 需分頁 → 嵌套查詢易分頁(先分頁主表再關聯);嵌套結果分頁極其麻煩(需子查詢/窗口函數)
  4. 極高效能要求 → 考慮應用層二級緩存緩存關聯數據,或引入 Redis 緩存聚合對象

最佳實踐建議

  • 默認優先嵌套結果(JOIN),避免 N+1。
  • 使用 <id> 標識唯一鍵,確保去重正確(無論嵌套查詢還是結果)。
  • 大量據場景:分步查詢——先查主表 ID 列表,再 WHERE id IN (...) 批量查關聯表,應用層組裝(手動實現嵌套結果邏輯,但可控分頁、可緩存)。
  • 開啟 MyBatis 日誌(log4j.logger.org.apache.ibatis=DEBUG)觀察實際 SQL 條數,及時發現 N+1。

總結

MyBatis 關聯映射只有 association 和 collection 兩把刀,但嵌套查詢 vs 嵌套結果的選擇直接決定系統吞吐量上限。理解 N+1 本質,根據數據量、併發、分頁需求決策,才是架構師級思維。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved