HQL(Hibernate Query Language)是面向對象的查詢語言,它和SQL查詢語言有些相似。在Hibernate提供的各種檢索方式中,HQL是使用最廣的一種檢索方式。它具有以下功能:
- 在查詢語句中設定各種查詢條件。
- 支持投影查詢,即僅檢索出對象的部分屬性。
- 支持分頁查詢。
- 支持分組查詢,允許使用group by和having關鍵字。
- 提供內置聚集函數,如sum()、min()和max()。
- 能夠調用用戶定義的SQL函數。
- 支持子查詢,即嵌套查詢。
- 支持動態綁定參數。
Session 類的 Qurey 接口支持HQL檢索方式,它提供了以上列出的各種查詢功能。
注:Qurey 接口支持方法鏈編程風格,它的set方法都返回自身實例,而不是返回void類型。方法鏈編程風格能使程序代碼更加簡潔。
一、HQL 查詢的步驟
寫 HQL 語句注意: 表名與列名 對應 持久化類的類名和字段名,嚴格區分大小寫,HQL不支持 * 號,一般使用別名 ,
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 寫HQL
String hql = "from Activity"; //等價於: String hql = "select a from Activity a";
//2. 獲取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 給動態綁定參數賦值
//4. 獲取list(查詢) 增刪改 使用executeUpdate()
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
二. HQL 動態綁定參數查詢
Activity 與 Member 多對多
1). 使用 ? 做佔位符: Hibernate setParameter() 佔位符參數從 0 開始
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 寫HQL
String hql = "select a from Activity a where a.id<? and a.aname like ?";
//2. 獲取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 給動態綁定參數賦值 Hibernate 從0開始
query.setParameter(0, 5);
query.setParameter(1, "建%");
//4. 獲取list
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
--------
建軍節--2
Hibernate:
select
member0_.activity_id as activity1_1_0_,
member0_.member_id as member_i2_1_0_,
member1_.id as id1_2_1_,
member1_.m_name as m_name2_2_1_
from
t_activity_member member0_
inner join
t_member member1_
on member0_.member_id=member1_.id
where
member0_.activity_id=?
建黨節--1
2). 使用 :自定義名 做佔位符: Hibernate setParameter() 佔位符參數 用字符串表示, 推薦使用 可讀性強
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 寫HQL
String hql = "select a from Activity a where a.id<:aid and a.aname like :an";
//2. 獲取Query<>
Query<Activity> query = session.createQuery(hql);
//3. 給動態綁定參數賦值
query.setParameter("aid", 5);
query.setParameter("an", "建%");
//4. 獲取list
List<Activity> list = query.list();
for (Activity activity : list) {
System.out.println(activity.getAname() +"--"+ activity.getMember().size() );
}
}
3. where 篩選參數可以是基本數據類型外,還可以是一個對象(必須有id,否則報錯)
Teacher 與 Student 一對多
@SuppressWarnings("unchecked")
@Test
public void test() {
//1. 寫HQL
String hql = "select s from Student s where s.teacher = ?";
//2. 獲取Query<>
Query<Student> query = session.createQuery(hql);
//3. 給動態綁定對象參數賦值
Teacher teacher = new Teacher();
teacher.setId(3);
query.setParameter(0, teacher);
//4. 獲取list
List<Student> list = query.list();
for (Student Student : list) {
System.out.println(Student.getSname() +"--"+ Student.getTeacher().getTname());
}
}
--------
學生3--老師3
學生4--老師3
三、HQL 分頁查詢: mysql 和 orcale 都適合。
分頁查詢主要靠兩個方法來實現:
setFirstResult(index) : 從index這個索引值開始查詢數據庫的記錄, index 從 0 開始
setMaxResults(數量) : 表示從起始的索引對應的記錄開始, 最多查詢出的記錄數目
@SuppressWarnings("unchecked")
@Test
public void test() {
int pageNo = 4;
int pageSize = 5;
String hql = "from Student where id>?"; //可以加條件
Query<Student> query = session.createQuery(hql);
query.setParameter(0, 5);
//使用下 鏈式語法
query.setFirstResult((pageNo-1)*pageSize).setMaxResults(pageSize);
List<Student> list = query.list();
//System.out.println(Arrays.toString(list.toArray()));
for (Student Student : list) {
System.out.println(Student.getSname());
}
}
四、只查詢出表中的部分字段
只查詢出表中的部分字段, 在 Hibernate 中叫做投影查詢:
1. 方法一:多字段(大於1)查詢
@Test
public void test() {
String hql = "select s.sname,s.teacher from Student s";
//返回結果爲 Object[]的List集合
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
}
--------
[學生1, cn.jq.hibernate5.model.Teacher@5b6e8f77]
[學生2, cn.jq.hibernate5.model.Teacher@5b6e8f77]
[學生3, cn.jq.hibernate5.model.Teacher@6b7d1df8]
[學生4, cn.jq.hibernate5.model.Teacher@6b7d1df8]
[學生5, cn.jq.hibernate5.model.Teacher@3044e9c7]
[學生6, cn.jq.hibernate5.model.Teacher@41d7b27f]
。。。
方法一投影查詢返回結果是一個 List<Object[]> 類型,這是因爲查詢的字段是多個(大於1),所以是 Object[] 數組,如果我們業務層很多方法都是以對象來傳遞的,Object 類型要強制類型轉換,不好!
2.方法二: 對象查詢
@Test
public void test() {
String hql = "select new Student(s.sname) from Student s";
// 返回結果是 對用POJO類的List集合, 注意:必須要有對應參數的構造方法
List<Student> list = session.createQuery(hql).list();
for (Student student : list) {
System.out.println(student);
}
}
----其他值爲空或默認基本類型值--
Student [id=0, sname=學生1, teacher=null]
Student [id=0, sname=學生2, teacher=null]
Student [id=0, sname=學生3, teacher=null]
Student [id=0, sname=學生4, teacher=null]
。。。
方法二返回結果是一個 List<POJO類> 對象集合類型,(被泛型限定的POJO類),這種方式解決了傳遞 Object[] 方式,只傳遞對象了,且也實現了,只查詢部分屬性。
注意: POJO 類映射對象中必須要有對應參數查詢語句的構造方法
五. HQL語句中使用聚合函數
重點內容
函數名 | 說明 |
count() | 統計選擇對象的記錄條數 |
sum() | 求和 |
max() | 求最大值 |
min() | 求最小值 |
avg() | 計算選擇屬性的平均值 |
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select sum(s.id),avg(s.id),max(s.id),min(s.id),count(s.id),s.teacher from Student s group by s.teacher.id having s.teacher.id < ?";
//返回結果爲 Object[]的List集合
List<Object[]> list = session.createQuery(hql).setParameter(0, 6).list();
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
}
--------
[3, 1.5, 2, 1, 2, Teacher [id=1, tname=老師1]]
[7, 3.5, 4, 3, 2, Teacher [id=3, tname=老師3]]
[18, 6.0, 7, 5, 3, Teacher [id=4, tname=老師4]]
結論: HQL 語言和 SQL 語言基本上是相通的,只是 將字段,表對應成了屬性和類名字 即可。
六. HQL連接查詢
HQL提供的連接方式:
迫切連接是指在指定連接方式時不僅指定了連接查詢方式,而且顯式地指定了關聯級別的查詢策略。
Hibernate 使用 fetch 關鍵字實現,fetch 關鍵字表明“左邊”對象用於與“右邊”對象關聯的屬性會立即被初始化。
平時項目中都建議使用 fetch !
多對一關聯,無迫切連接和迫切連接比較(其他連接類同):
sql 中 表名 inner join 表名
hql 中 POJO類名 inner join 關聯 屬性類
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "from Student s inner join s.teacher t where s.id<? and t.id<?";
//返回結果爲 Object[]的List集合
List<Object[]> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
}
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "from Student s inner join fetch s.teacher t where s.id<? and t.id<?";
//返回結果爲 POJO類的List集合
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
--------
Student [id=1, sname=學生1, teacher=Teacher [id=1, tname=老師1]]
Student [id=2, sname=學生2, teacher=Teacher [id=1, tname=老師1]]
Student [id=3, sname=學生3, teacher=Teacher [id=3, tname=老師3]]
Student [id=4, sname=學生4, teacher=Teacher [id=3, tname=老師3]]
Student [id=5, sname=學生5, teacher=Teacher [id=4, tname=老師4]]
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select s from Student s inner join s.teacher t where s.id<? and t.id<?";
//返回結果爲 POJO類的List集合, 多條sql語句
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "select s from Student s inner join fetch s.teacher t where s.id<? and t.id<?";
//返回結果爲 POJO類的List集合, 一條sql語句
List<Student> list = session.createQuery(hql).setParameter(0, 6).setParameter(1, 5).list();
for (Student student : list) {
System.out.println(student);
}
}
--------
Hibernate:
select
student0_.id as id1_3_0_,
teacher1_.id as id1_4_1_,
student0_.sname as sname2_3_0_,
student0_.teacher_id as teacher_3_3_0_,
teacher1_.tname as tname2_4_1_
from
t_student student0_
inner join
t_teacher teacher1_
on student0_.teacher_id=teacher1_.id
where
student0_.id<?
and teacher1_.id<?
使用結論:
1. 使用 from.... 沒加 fetch 和加了,得到的結果類型是不一樣的,只發起一條 sql .
2. 使用 select ... 指定查詢對象時, 加 fetch, 只發起一條 sql , 反之發起多條 sql , 結果類型是一樣。
根據發起的 sql 條數越來效率越高的原則,項目開發中, 推薦都加fetch
fetch關鍵字只對 inner join 和 left join 有效。right join 基本也不用.
七. HQL批量增刪改
每個 Hibernate Session 中都維持了一個必選的數據緩存,這個內部緩存正常情況下是由 Hibernate 自動維護的,並且沒有容量限制。在批量插入與更新時,由於每次保存的實體都會保存在Session緩存中,當數據量大的時候,就可能出現OutOfMemoryException(內存溢出異常)。所以批量增加或更新操作中, 應該考慮到控制內部緩存的過度增長而出現OutOfMemeoryError 錯誤。
這裏有兩個方面進行控制:
一. 是在數據保存過程中週期性的對 Session 調用 flush 和 clear 方法,確保 Session 的容量不至於太大。
二. 是設置 hibernate.jdbc.batch_size 參數來指定每次提交SQL的數量。
setParameter() setParameterList() executeUpdate()
1. 批量插入(不推薦):推薦使用 save()
@Test
public void test() {
List<Student> list = new ArrayList<Student>();
for(int i=50; i < 85; i++) {
Student student = new Student();
student.setSname("學生" + i);
list.add(student);
}
int row = 10; //
for(int i =0; i<list.size(); i++) {
session.save(list.get(i));
if(i % row == 0) {
session.flush();
session.clear();
}
}
}
Session.flush():1.刷新所有數據;2.執行數據庫SQL完成持久化動作;必須在操作結束且在提交事務和關閉連接之前被調用。
Session.clear(): 則是清除 session 中的緩存數據。這樣就達到控制session的一級緩存的大小
2. 批量修改
@SuppressWarnings("unchecked")
@Test
public void test() {
String hql = "update Student s set s.sname = ? where s.id > ?";
int n = session.createQuery(hql).setParameter(0, "李斯").setParameter(1, 15).executeUpdate();
System.out.println(n);
}
3. 批量刪除
@SuppressWarnings("unchecked")
@Test
public void test() {
Integer[] ids = {16,19,10}; //注意Object[]類型
String hql = "delete from Student s where s.id in (:ids)";
int n = session.createQuery(hql).setParameterList("ids", ids).executeUpdate();
System.out.println(n); }
-------
Hibernate:
delete
from
t_student
where
id in (
? , ? , ?
)
3
批量操作總結:
1)優化 HIbernate API , 程序上採取分段插入或更新時 及時調用清除緩存方法。
2)通過 session.doWork()直接操作原生的 JDBC API 來做批量操作,性能更好。它具有以下優點:
(1) 不會消耗大量內存。因爲它不再將數據庫中的大批量數據先加載到內存中,然後再逐個更新或修改。
(2) 可以在一條SQL語句中更新或刪除大批量的數據。