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

Hibernate 中使用 UNION 查詢:繞過 HQL 限制的 Native SQL 實戰方案

     編輯:Java 相關知識

HQL 不支援 UNION 操作,複雜的「或」邏輯查詢若強行用 HQL 會導致多表連接、Distinct 去重、索引失效等效能災難。本文實戰演示如何透過 Native SQL + Hibernate SQLQuery 實現 UNION 查詢,並正確映射實體物件與關聯關係。

業務場景:查詢用戶評論過或收藏過的專案

原 HQL(效能差):


String hql = "SELECT DISTINCT p FROM Project p, CommentSimple c, ProjectBookmark b " +
             "WHERE (c.owner.id=? AND p.id=c.targetId AND c.targetType=500) " +
             "OR (b.user.id=? AND p.id=b.project.id)";

問題:三表連接產生笛卡爾積、大量無效中間結果、Distinct 压力大、索引無法有效使用。

優化方案:UNION 分而治之


// 1. 統計總數(分頁必需)
String countSql = "SELECT COUNT(*) FROM ( " +
    "SELECT p.id FROM project p, comment_simple c " +
    "WHERE p.id=c.target_id AND c.target_type=500 AND c.uid=" + userId +
    " UNION " +
    "SELECT pr.id FROM project pr, project_bookmark b " +
    "WHERE pr.id=b.project_id AND b.uid=" + userId +
    ") AS temp";

SQLQuery countQuery = session.createSQLQuery(countSql);
Integer total = Integer.valueOf(((BigInteger) countQuery.uniqueResult()).toString());

// 2. 分頁查詢實體
String dataSql = 
    "(SELECT {p.*} FROM project p, comment_simple c " +
    "WHERE p.id=c.target_id AND c.target_type=500 AND c.uid=" + userId + ")" +
    "UNION " +
    "(SELECT {p.*} FROM project p, project_bookmark b " +
    "WHERE p.id=b.project_id AND b.uid=" + userId + ")" +
    "LIMIT " + (pageIndex - 1) * pageSize + ", " + pageSize;

SQLQuery dataQuery = session.createSQLQuery(dataSql);
dataQuery.addEntity("p", Project.class);  // 關鍵:別名 p 對應 {p.*}
List list = dataQuery.list();

關鍵技術點解析

1. {p.*} 別名映射機制

  • addEntity("p", Project.class) 中的 "p" 必須與 SQL 中 {p.*} 的別名完全一致。
  • Hibernate 會將 {p.*} 展開為 Project 表所有欄位,並按實體映射規則填充物件。
  • 兩個 UNION 子查詢的 {p.*} 別名必須相同,否則映射失敗。

2. 需要 ORDER BY 時的特殊處理


String dataSql = 
    "(SELECT {p.*}, p.created FROM project p, comment_simple c " +
    "WHERE ... )" +
    "UNION " +
    "(SELECT {p.*}, p.created FROM project p, project_bookmark b " +
    "WHERE ... )" +
    "ORDER BY created LIMIT ?, ?";

原因:Hibernate 將 p.created 映射為 created AS y0_,導致 ORDER BY created 失效。解法:額外 SELECT 排序欄位並保持原名,資料庫直接按原名排序。

3. 參數綁定最佳實踐(防注入)


String dataSql = 
    "(SELECT {p.*} FROM project p, comment_simple c " +
    "WHERE p.id=c.target_id AND c.target_type=500 AND c.uid=?) " +
    "UNION " +
    "(SELECT {p.*} FROM project p, project_bookmark b " +
    "WHERE p.id=b.project_id AND b.uid=?) " +
    "LIMIT ?, ?";

SQLQuery query = session.createSQLQuery(dataSql);
query.addEntity("p", Project.class);
query.setParameter(0, userId);
query.setParameter(1, userId);
query.setParameter(2, (pageIndex - 1) * pageSize);
query.setParameter(3, pageSize);
List list = query.list();

表結構參考

表關鍵欄位
projectid, owner_id, created, ...
project_bookmarkuid(用戶), project_id(收藏專案), owner_id(收藏者)
comment_simpletarget_type(500=專案), target_id, project_id, owner_id(評論者)

效能對比

指標原 HQL (三表連接+Distinct)UNION Native SQL
執行計畫全表掃描/臨時表各子查詢獨立走索引
中間結果集極大 (笛卡爾積)極小 (各自過濾後合併)
Distinct 開銷高無 (UNION 自動去重)
分頁效能差優 (LIMIT 直接下推)

避坑指南

  • 確保兩側 SELECT 欄位數量、順序、型別完全一致。
  • 實體映射別名 addEntity("alias", Entity.class) 與 {alias.*} 嚴格對應。
  • 複雜關聯(如一對多集合)Native SQL 無法自動載入,需額外查詢或改用 HQL 子查詢。
  • 跨資料庫分頁語法差異:MySQL LIMIT、Oracle ROWNUM、SQL Server OFFSET/FETCH、PostgreSQL LIMIT/OFFSET。

結語

HQL 表達力有限時,Native SQL + addEntity 映射是突破效能瓶頸的利器。UNION 將複雜「或」邏輯拆解為獨立索引友好查詢,配合 Hibernate 實體映射,兼顧效能與領域模型便利性。

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