Hibernate 查詢語言(HQL)
1. 前言
本節課程和大家一起學習 Hibernate 中的 HQL ( Hibernate 查詢語言)。通過本節課程的學習,你將了解到:
- HQL 基礎語法;
- HQL 查詢的具體實現。-
2. HQL
查詢?前面不是講過?用過嗎?
但是,前面的查詢都是簡單查詢,真實項目中的查詢需求要遠比這個復雜。僅僅依靠 get()、load() 是遠遠達不到要求。
Hibernate 提供了靈活多樣的查詢機制,幾乎能做到無死角查詢。
- 標準化對象查詢 (Criteria Query): 以對象的方式進行查詢,將查詢語句封裝為對象操作。
- 優點: 可讀性好,符合 Java 程序員的編碼習慣。
- 缺點: 不夠成熟,不支持投影(projection)或統計函數(aggregation)
-
Hibernate 語言查詢(Hibernate Query Language,HQL): 它是完全面向對象的查詢語句,查詢功能非常強大,具有繼承、多態和關聯等特性 。Hibernate 官方推薦使用 HQL 進行查詢。
-
Native SQL Queries(原生 SQL 查詢): 直接使用數據庫提供的 SQL 語句進行查詢。
原生 SQL 的查詢能力是最強的,當其它查詢不能達到完成任務要求時,可使用它。
本次課程主要是介紹 HQL 查詢。
2.1 HQL 基礎語法
還是從查詢需求入手吧。
查詢需求:查詢所有學生。
前提:確定數據庫中有測試數據。
查詢流程:
首先編寫 HQL 語句,如:
select stuName,stuId from Student
HQL 整體結構上類似于 SQL,可以認為 HQL 語句由兩部分組成,一部分直接借用 SQL 中的關鍵字,如 select、from、where 等,和 SQL 中要求是一樣的,編寫時可不區分大小寫。
另一部分就是純 JAVA 概念。
在 HQL 語句中,原生 SQL 語法中的表名被類名替換,字段名被屬性名替換,類和屬性是 Java 概念,所以要區分大小寫。
下面的 HQL 語句用來查詢所有學生信息,在 HQL 中同樣有別名一說。
from Student s
HQL 是面向對象的、類似于 SQL 的查詢語言,本質上是不能直接交給數據庫的,在提交之前,需要把 HQL 轉譯成 SQL,在轉譯時把類名換成表名、把屬性名換成字段名。
雖然替換工作由 Hibernate 自己完成,但是,你需要有所了解。
從中可得出一個結論,HQL 查詢是沒有原生 SQL 查詢快的。
Hibernate 提供了 Query 組件執行 HQL 語句。
Query query=session.createQuery(hql);
Query 與原生 JDBC 中的 Statement 組件很相似,但其功能更高級、強大。
調用 Query 對象中 list() 查詢方法,就能查詢出開發者所需要的數據。
List<Student> stus= query.list();
最后,享受數據的時刻:
for (Student student : stus) {
System.out.println(student);
}
完整的實例:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
String hql="from Student s";
Query query=session.createQuery(hql);
List<Student> stus= query.list();
for (Student student : stus) {
System.out.println(student);
}
return null;
}
});
執行實例,在控制臺可看到輸出了所有學生信息。簡直是簡單得不要不要。
如果只想查詢某幾個屬性的值,又該如何查詢呢?
很簡單,重構一下 HQL 語句:
String hql="select s.stuId,s.stuName from Student s";
此時查詢出來的數據不是一個完整的對象。list() 方法查詢出來的結果不能直接封裝到 Student 類型中。
Hibernate 會把查詢出來的每一行數據封裝到一個數組中,所以 List 應該 是一個數組的集合,完整實例代碼如下:
HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
hibernateTemplate.template(new Notify<Student>() {
@Override
public Student action(Session session) {
String hql="select s.stuId,s.stuName from Student s";
Query query=session.createQuery(hql);
List<Object[]> stus= query.list();
for (Object[] student : stus) {
System.out.println(student[0]+":"+student[1]);
}
return null;
}
});
控制臺輸出結果:
Hibernate:
select
student0_.stuId as col_0_0_,
student0_.stuName as col_1_0_
from
Student student0_
1:Hibernate
2:Session
3:SessionFactory
24:Configuration
如果一定要使用 Student 類型接收查詢數據則必須保證 Student 類中存在如下的構造方法:
public Student(Integer stuId, String stuName) {
this.stuId = stuId;
this.stuName = stuName;
}
然后修改 HQL 語句:
String hql="select new Student(s.stuId,s.stuName) from Student s";
Query query=session.createQuery(hql);
List<Student> stus= query.list();
for (Student student : stus) {
System.out.println(student);
}
建議大家使用上面的方案,畢竟很 OOP 嘛。其實也可以使用 Map 封裝查詢出來的數據。
String hql="select new Map(s.stuId as sid,s.stuName as sname) from Student s";
Query query=session.createQuery(hql);
List<Map<String,Object>> stus= query.list();
for (Map<String,Object> map : stus) {
System.out.println(map.get("sid")+":"+map.get("sname"));
}
道路千萬條,選擇在你手上。
2.2 HQL 高級查詢
強參數查詢
使用 SQL 查詢時,可以指定查詢條件,這個地球人都知道。HQL 中同樣能使用條件查詢:
from Student s where s.stuId> 2
在 HQL 中,如果查詢條件中的數據需要通過參數傳遞,則會有兩種方案:
- 匿名方案,已經司空見慣,對不對;
from Student s where s.stuId> ?
- 命名參數方案。
from Student s where s.stuId> :id
參數名前面一定要有一個冒號 :id。
完整實例獻上:
String hql="from Student s where s.stuId> :id";
Query query=session.createQuery(hql);
query.setInteger("id", 2);
List<Student> stus= query.list();
for (Student student : stus) {
ystem.out.println(student);
}
return null;
可自行查看控制臺上的輸出結果。強命名參數和 ? 占位符作用是一樣的,但是,強命名參數可減少指定實參時的出錯率。
分頁查詢
分頁查詢是很實用的查詢機制。
使用原生 SQL 分頁查詢時,需要自己構建查詢 SQL 語句,不同的數據庫中的分頁查詢語句編寫也有差異性。Hibernate 通過其提供的分頁查詢功能很好地避開了這些問題。
分頁查詢之前,先搞清楚幾個與查詢有關的參數:
- pageSize: 每一頁大??;
- pageNum: 頁碼。
假如數據庫中有 20 行數據,分頁查詢時指定 pageSize 為 5,則每 5 條數據為一個邏輯頁,總共有 4 頁。
如果要查詢第 3 頁數據,即 pageNum=3。
則需要跳過去的記錄數為:(pageNum-1)*pageSize=(3-1)*5=10 ,也就是從第 11 條數據開始查詢。
現在直接上實例代碼:
String hql = "from Student s order by stuId" ;
Query query = session.createQuery(hql);
int pageNum=3;
int pageSize=5;
int passNum=(pageNum-1)*pageSize;
query.setFirstResult(passNum);
query.setMaxResults(pageSize);
List<Student> stus = query.list();
for (Student student : stus) {
System.out.println(student.getStuName());
}
return null;
HIbernate 會從第 11 條記錄開始,查詢出 5 條記錄。針對不同的數據庫系統,Hibernate 會給出最佳的 SQL 分頁方案。
聯合查詢
程序中所需要的數據可不一定在同一張表中,往往都是在多張表中。原生 SQL 通過多表連接或子查詢方式解決這個問題。
使用 HQL 一樣能表達出多表連接的意圖。
可能你會問:
前面的一對一、一對多、多對多映射關聯關系后,不就已經能夠查詢出多張表中的數據嗎。
如下面表數據:
在學生類中采用立即查詢策略:
@ManyToOne(targetEntity = ClassRoom.class, cascade = CascadeType.REMOVE,fetch=FetchType.EAGER)
@JoinColumn(name = "classRoomId")
public ClassRoom getClassRoom() {
return classRoom;
}
查詢所有學生:
String hql = "from Student s";
Query query = session.createQuery(hql);
List<Student> stus = query.list();
System.out.println("-----------------------------");
for (Student student : stus) {
System.out.println("學生姓名:"+student.getStuName());
System.out.println("班級名稱: "+student.getClassRoom().getClassRoomName());
}
return null;
不要懷疑,結果一定是會出現的。但是,可以看到控制臺輸出了很多 SQL 語句。
那是因為,Hibernate 會先查詢出所有學生,然后根據班級 ID 再進入班級表進行查詢,這就是 Hibernate 查詢過程的 1+N 問題。
可改成下面的關聯查詢方式:
String hql = "select s.stuName,c.classRoomName from Student s,ClassRoom c where s.classRoom=c";
Query query = session.createQuery(hql);
List<Object[]> stus = query.list();
System.out.println("-----------------------------");
for (Object[] student : stus) {
System.out.println("學生姓名:"+student[0]);
System.out.println("班級名稱: "+student[1]);
}
return null;
控制臺輸入結果:
Hibernate:
select
student0_.stuName as col_0_0_,
classroom1_.classRoomName as col_1_0_
from
Student student0_ cross
join
ClassRoom classroom1_
where
student0_.classRoomId=classroom1_.classRoomId
Hibernate 僅構建了一條 SQL 語句,直接查詢出來了所有數據,看得出來,其性能要大于 1+N 方案。
HQL 比想象中要簡單,比你預期的功能要強大。有了它,再也不怕查詢不到我們需要的數據。
2.3 HQL 與函數
先把上一節課程中遺留的內容向大家介紹一下。
使用原生 SQL 查詢時,可以在查詢語句中嵌入聚合函數,HQL 查詢中也可以使用聚合函數。
數據的用途之一是用于邏輯,產生新的數據。另一個用途是可產生報表,用于決策。
聚合函數的作用在于數據統計和分析,其現實意義很大。
HQL 中能使用哪些聚合函數?
答案是:SQL 中的聚合函數全部可照搬過來。
實例:統計學生相關信息。
String hql = "select count(*),sum(s.stuId),avg(s.stuId),max(s.stuId),min(s.stuId) from Student s";
Query query = session.createQuery(hql);
Object[] stus = (Object[]) query.uniqueResult();
System.out.println("學生總人數:" + stus[0]);
System.out.println("學生編號求和:" + stus[1]);
System.out.println("學生編號平均值:" + stus[2]);
System.out.println("學生編號最大值:" + stus[3]);
System.out.println("學生編號最小值:" + stus[4]);
使用過程很簡單,上面實例中用到了 Query 對象的 uniqueResult() 方法,當確定查詢結果只有一行記錄時,可以使用此方法。
好了,有句話說得好,師傅引進門,學藝在個人。因為 HQL 是對 SQL 的 OOP 封裝,其內涵是一樣。對于很熟悉 SQL 語法的你們來講,全完掌握 HQL 也只時間的問題,不會存在技術上的難點。
3. 小結
又到了要總結的時候,本課程給大家介紹了 HQL 查詢語法,還有更多細節留待后面慢慢研究。
HQL 語法是一種類似于 SQL 語法,形式上與 SQL 一樣,但本質有很大區別。HQL 是面向對象的查詢語法結構。注意語句中哪些地方不區分大小,哪些地方 區分大小寫。
本節課也聊到了 HQL 的關聯查詢語法,很好解決了 1+N 問題。