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 压力大、索引無法有效使用。
// 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();
addEntity("p", Project.class) 中的 "p" 必須與 SQL 中 {p.*} 的別名完全一致。{p.*} 展開為 Project 表所有欄位,並按實體映射規則填充物件。{p.*} 別名必須相同,否則映射失敗。
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 排序欄位並保持原名,資料庫直接按原名排序。
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();
| 表 | 關鍵欄位 |
|---|---|
| project | id, owner_id, created, ... |
| project_bookmark | uid(用戶), project_id(收藏專案), owner_id(收藏者) |
| comment_simple | target_type(500=專案), target_id, project_id, owner_id(評論者) |
| 指標 | 原 HQL (三表連接+Distinct) | UNION Native SQL |
|---|---|---|
| 執行計畫 | 全表掃描/臨時表 | 各子查詢獨立走索引 |
| 中間結果集 | 極大 (笛卡爾積) | 極小 (各自過濾後合併) |
| Distinct 開銷 | 高 | 無 (UNION 自動去重) |
| 分頁效能 | 差 | 優 (LIMIT 直接下推) |
addEntity("alias", Entity.class) 與 {alias.*} 嚴格對應。LIMIT、Oracle ROWNUM、SQL Server OFFSET/FETCH、PostgreSQL LIMIT/OFFSET。HQL 表達力有限時,Native SQL + addEntity 映射是突破效能瓶頸的利器。UNION 將複雜「或」邏輯拆解為獨立索引友好查詢,配合 Hibernate 實體映射,兼顧效能與領域模型便利性。