HQL (Hibernate Query Language) 是面向對象的查詢語言,編譯為 SQL 執行。掌握 HQL 能避免原生 SQL 的表結構耦合,享受實體映射、緩存、攔截器等 ORM 紅利。本文系統講解 HQL 核心語法、投影查詢、關聯抓取、批量操作、命名查詢、分頁與參數綁定最佳實踐。
| 場景 | HQL | 說明 |
|---|---|---|
| 查詢所有實體 | from User | 類名而非表名,大小寫敏感 |
| 條件查詢 | from User u where u.age > 20 and u.age < 60 | 別名 u 簡化屬性訪問 |
| 投影查詢 (標量) | select u.name, u.age from User u where u.age > 20 | 返回 List<Object[]> |
| 投影查詢 (實體構造) | select new User(u.name, u.age) from User u where u.age > 20 | 需實體有對應構造器,返回 List<User> |
| 聚合函數 | select avg(u.age) from User u | count, sum, min, max, avg |
| 子查詢 | from User u where u.age > (select avg(u2.age) from User u2) | WHERE/SELECT/HAVING 均可 |
// 實體關聯:User 1:N Address
// 1. 內連接 (僅有地址的用戶)
from User u inner join fetch u.addresses
// 2. 左外連接 (所有用戶,無地址者 address 為 null)
from User u left join fetch u.addresses
// 3. 右外連接 (所有地址,無用戶者 user 為 null)
from User u right join fetch u.addresses
// 4. 完全連接
from User u full join fetch u.addresses
List<User>),關聯集合已初始化,避免 N+1。List<Object[]>,每行包含 [User, Address],需手動組裝。// 批量更新
int updated = session.createQuery("update User set name = 'NewName' where name = 'OldName'")
.executeUpdate();
// 批量刪除
int deleted = session.createQuery("delete User where lastLogin < :date")
.setParameter("date", LocalDate.now().minusYears(1))
.executeUpdate();
注意:executeUpdate() 直接發 SQL,**不觸發**實體生命週期回調、不更新一級緩存、不級聯。使用後建議 session.clear() 或重新查詢。
| 方式 | 語法 | API | 適用場景 |
|---|---|---|---|
| 位置參數 | where u.name = ? | query.setString(0, "val") | 簡單 1-2 參數,**索引從 0 開始** |
| 命名參數 | where u.name = :name | query.setParameter("name", "val") | 多參數、可讀性、重用 |
| 實體屬性注入 | where u.name = :name | query.setProperties(userQuery) | 查詢條件封裝為 POJO (UserQuery) |
<hibernate-mapping>
<query name="User.byName"><![CDATA[
from User u where u.name = :name
]]></query>
</hibernate-mapping>
@NamedQuery(name = "User.byName", query = "from User u where u.name = :name")
Query q = session.getNamedQuery("User.byName");
q.setParameter("name", "Alice");
List<User> list = q.list();
Query q = session.createQuery("from User u order by u.id");
q.setFirstResult((pageNo - 1) * pageSize); // offset
q.setMaxResults(pageSize); // limit
List<User> page = q.list();
// 總數查詢 (可優化為子查詢或緩存)
Long total = (Long) session.createQuery("select count(*) from User").uniqueResult();
| 特性 | HQL | SQL | Criteria (Deprecated) | JPA QL |
|---|---|---|---|---|
| 面向對象 | ✅ | ❌ | ✅ (程式化) | ✅ (標準) |
| 類型安全 | ❌ | ❌ | ✅ | ❌ |
| 動態查詢構建 | 字串拼接 | 字串拼接 | ✅ API | 字串拼接 |
| 原生函數/方言 | 受限 | ✅ 完全 | 受限 | 受限 |
| 推薦度 | ⭐⭐⭐⭐ | ⭐⭐ (特殊場景) | ⭐ (已棄用) | ⭐⭐⭐⭐⭐ (新專案首選) |
新專案建議:優先使用 JPA QL / Spring Data JPA Repository,複雜報表/原生函數才落回 @Query(nativeQuery=true) 或 Session.createNativeQuery()。
| 陷阱 | 後果 | 解決 |
|---|---|---|
| 投影查詢無 ID 導致 saveOrUpdate 變 INSERT | 重複數據 | 投影包含 ID:select new User(u.id, u.name, u.age)... |
| 大量數據未分頁 | OOM | 必加 setFirstResult/setMaxResults |
| 批量更新未清緩存 | 緩存髒讀 | session.clear() 或 evict() |
| 關聯未 fetch 觸發 N+1 | 效能災難 | join fetch、@BatchSize、EntityGraph |
HQL 以實體類與屬性為查詢對象,編譯為優化 SQL,提供投影構造、關聯抓取、批量操作、命名查詢、參數綁定等豐富特性。掌握 HQL 核心語法與陷阱,是 Hibernate 高效開發的必修課。