MyBatis 強大的結果映射機制,能將複雜的多表關聯查詢結果自動裝配為豐富的 Java 對象圖。本文通過「博客系統」實戰案例,深度對比嵌套查詢與嵌套結果兩大核心方案,解析 N+1 問題、性能權衡與最佳實踐。
// 表結構對應
Author (作者) 1 ——< N Blog (博客)
Blog (博客) 1 ——< N Post (文章)
Post (文章) 1 ——< N Comment (評論)
Post (文章) N ——> M Tag (標簽) // 中間表 Post_Tag
public class BlogInfo {
private Long blogId;
private String title;
private Author author; // 多對一
private List posts; // 一對多
// 每個 Post 內含 List、List
}
BlogInfo blogInfo = sqlSession.selectOne("BlogMapper.queryBlogInfoById", blogId);
// 一行代碼獲得完整對象圖
queryBlogInfoById,獲取 Blog 基礎 ResultSet<association>,執行關聯的 select 查詢作者<collection>,執行關聯的 select 查詢文章列表AuthorMapper.selectByPrimaryKey、PostMapper.selectByBlogId 可獨立被其他業務調用
// 假設查詢 1 個 Blog,關聯 1 個 Author、10 個 Post
// 實際執行 SQL 數 = 1 (主查詢) + 1 (Author) + 10 (Post) = 12 條
// 若查詢 100 個 Blog,每個 10 篇文章 → 1 + 100*1 + 100*10 = 1101 條 SQL!
N+1 公式:總 SQL 數 = 1 (主查詢) + N × (關聯查詢數)。N 為主查詢結果集行數。
BlogInfo blogInfo = sqlSession.selectOne("BlogMapper.queryAllBlogInfo");
// 僅 1 條 SQL,內存中完成對象圖組裝
<id> 分組:相同 blog_id 歸為同一 BlogInfo<association>):按 column 值分組,構建單一 Author<collection>):按 column 值分組,構建 List| 維度 | 嵌套查詢 | 嵌套結果 |
|---|---|---|
| SQL 數量 | 1 + N × 關聯數 | 1 |
| DB 壓力 | 高(多次往返) | 低(單次) |
| 網絡傳輸 | 精確(按需) | 冗餘(重複列) |
| SQL 複雜度 | 低(單表) | 高(JOIN) |
| 映射配置 | 簡潔(引用 select) | 冗長(全列映射) |
| SQL 可複用性 | 高 | 低 |
| 延遲加載支援 | 支援 | 不支援 |
| 分頁友好度 | 高 | 低(需子查詢/窗口函數) |
| 適用數據量 | 小(N < 20) | 大(N > 100) |
public class Module {
private int id;
private String name;
private Module parentModule; // 多對一:父模塊
private List childrenModules; // 一對多:子模塊
}
注意:遞歸關聯極易產生指數級 SQL,生產環境必須配合 fetchType="lazy" 延遲加載,或改用嵌套結果單次查詢構建樹(CTE 遞歸查詢)。
MyBatis 關聯映射功能強大,但「大權在手,大責在身」。理解 N+1 本質、權衡 SQL 複雜度與對象圖豐富度、根據業務訪問模式選型,才能寫出高性能、可維護的持久層代碼。