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