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