HQL(Hibernate Query Language) 是面向?qū)ο蟮牟樵冋Z言, 它和 SQL 查詢語言有些相似. 在 Hibernate 提供的各種檢索方式中, HQL 是使用最廣的一種檢索方式. 它有如下功能:
HQL 查詢包括以下步驟:
Qurey 接口支持方法鏈編程風(fēng)格, 它的 setXxx() 方法返回自身實例, 而不是 void 類型,因此可以寫類似于.setXxx().setXxx().setXxx()...
樣式的語句。
HQL 查詢語句是面向?qū)ο蟮? Hibernate 負責(zé)解析 HQL 查詢語句, 然后根據(jù)對象-關(guān)系映射文件中的映射信息, 把 HQL 查詢語句翻譯成相應(yīng)的 SQL 語句。HQL 查詢語句中的主體是域模型中的類及類的屬性。
SQL 查詢語句是與關(guān)系數(shù)據(jù)庫綁定在一起的。SQL 查詢語句中的主體是數(shù)據(jù)庫表及表的字段。
最簡單實體查詢例子:
String hql = "from User";
Query query = session.createQuery(hql);
List<User> list = query.list();
上面的HQL語句將取出User的所有對應(yīng)記錄為:select user0_.U_ID as U_ID1_0_,user0_.U_NAME as U_NAME2_0_,user0_.U_AGE as U_AGE3_0_ from USERS user0_
在HQL語句中,本身大小寫無關(guān),但是其中出現(xiàn)的類名和屬性名必須注意大小寫區(qū)分。同時,在Hibernate中,查詢的目標實體存在繼承關(guān)系的判定,如果from User
將返回所有User以及User子類的記錄。假設(shè)系統(tǒng)中存在User的兩個子類:SysAdmin和SysOperator,那么該hql語句返回的記錄將包含這兩個子類的所有數(shù)據(jù),即使SysAdmin和SysOperator分別對應(yīng)了不同的庫表。
Where子句: 如果我們想取出名為“Erica”的用戶記錄,可以通過Where子句加以限定(其中AS可以省略):
FROM User AS user WHERE user.name='Erica'
where子句中,我們可以通過比較操作符指定甄選條件,如: =, , <, >, <=, >=, between, not between, in ,not in, is, like等。同時,在where子句中可以使用算術(shù)表達式。 幾個簡單實例:
FROM User user WHERE user.age<20
FROM User user WHERE user.name IS null
FROM User user WHERE user.name LIKE 'Er%'
FROM User user WHERE (user.age % 2 = 1)
FROM User user WHERE (user.age<20) AND (user.name LIKE '%Er')
有時,我們需要的數(shù)據(jù)可能僅僅是實體對象的某個屬性(庫表記錄中的某個字段信息)。通過HQL可以簡單的做到這一點。
String hql = "SELECT user.name FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
上例中,我們指定了只需要獲取User的name屬性。此時返回的list數(shù)據(jù)結(jié)構(gòu)中,每個條目都是一個String類型的name數(shù)據(jù)。 我們也可以通過一條HQL獲取多個屬性:
public void test() {
String hql = "SELECT user.name,user.age FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
Object[] results = (Object[]) it.next();
System.out.println(results[0]+","+results[1]);
}
}
而此時,返回的list數(shù)據(jù)結(jié)構(gòu)中,每個條目都是一個對象數(shù)組(Object[]),其中依次包含了我們所獲取的屬性數(shù)據(jù)。
除此之外,我們也可以通過在HQL中動態(tài)的構(gòu)造對象實例的方法對這些平面化的數(shù)據(jù)進行封裝。
String hql = "SELECT new User(user.uName,user.uAge) FROM User user";
List list = session.createQuery(hql).list();
Iterator it = list.iterator();
while(it.hasNext()){
User user = (User) it.next();
System.out.println(user);
}
通過在HQL中動態(tài)的構(gòu)造對象實例,我們實現(xiàn)了對查詢結(jié)果的對象化封裝。此時在查詢結(jié)果中的User對象僅僅是一個普通的Java對象,僅用于對查詢結(jié)果的封裝,除了在構(gòu)造時賦予的屬性值外,其他屬性均為未賦值狀態(tài)。同時,在實體類中,要提供包含構(gòu)造屬性的構(gòu)造方法,并且順序要相同。
與此同時,我們也可以在HQL的select子句中使用統(tǒng)計函數(shù)或者利用DSITINCT關(guān)鍵字,剔除重復(fù)記錄。
SELECT COUNT(*),MIN(user.age) FROM User user
SELECT DISTINCT user.name FROM User user
String hql = "UPDATE User SET age = 18 WHERE id = 1";
int result = session.createQuery(hql).executeUpdate();
上述代碼利用HQL語句實現(xiàn)了更新操作。對于單個對象的更新也許代碼量并沒有減少太多,但如果對于批量更新操作,其便捷性以及性能的提高就相當可觀。 例如,以下代碼將所有用戶的年齡屬性更改為10
UPDATE User SET age = 18
HQL的delete子句使用同樣很簡單,例如以下代碼刪除了所有年齡大于18的用戶記錄:
DELETE User WHERE age > 18
不過,需要注意的是,在HQL delete/update子句的時候,必須特別注意它們對緩存策略的影響,極有可能導(dǎo)致緩存同步上的障礙。
舉例說明:
FROM User user ORDER BY user.name
默認情況下是按照升序排序,當然我們可以指定排序策略:
FROM User user ORDER BY user.name DESC
order by子句可以指定多個排序條件:
FROM User user ORDER BY user.name, user.age DESC
通過Group by可進行分組統(tǒng)計,如果下例中,我們通過Group by子句實現(xiàn)了同齡用戶的統(tǒng)計:
SELECT COUNT(user),user.age FROM User user GROUP BY user.age
通過該語句,我們獲得了一系列的統(tǒng)計數(shù)據(jù)。對于Group by子句獲得的結(jié)果集而言,我們可以通過Having子句進行篩選。例如,在上例中,我們對同齡用戶進行了統(tǒng)計,獲得了每個年齡層次中的用戶數(shù)量,假設(shè)我們只對超過10人的年齡組感興趣,可用以下語句實現(xiàn):
SELECT COUNT(user),user.age FROM User user GROUP BY user.age HAVING COUNT(user) > 10
在解釋參數(shù)綁定的使用時,我們先來解釋一下什么是SQL注入。
SQL Injection是常見的系統(tǒng)攻擊手短,這種攻擊方式的目標是針對由SQL字符串拼接造成的漏洞。如,為了實現(xiàn)用戶登錄功能,我們編寫了以下代碼:
FROM User user WHERE user.name='"+username+"' AND user.password='"+password+"'
從邏輯上講,該HQL并沒有錯誤,我們根據(jù)用戶名和密碼從數(shù)據(jù)庫中讀取相應(yīng)的記錄,如果找到記錄,則認為用戶身份合法。
假設(shè)這里的變量username和password是來自于網(wǎng)頁上輸入框的數(shù)據(jù)。現(xiàn)在我們來做個嘗試,在登錄網(wǎng)頁上輸入用戶名:"'Erica' or 'x'='x'",密碼隨意,也可以登錄成功。
此時的HQL語句為:
FROM User user WHERE user.name='Erica' OR 'x'='x' AND user.password='fasfas'
此時,用戶名中的OR 'x'='x'
被添加到了HQL并作為子句執(zhí)行,where邏輯為真,而密碼是否正確就無關(guān)緊要。
這就是SQL Injection攻擊的基本原理,而字符串拼接而成的HQL是安全漏洞的源頭。參數(shù)的動態(tài)綁定機制可以妥善處理好以上問題。
Hibernate提供順序占位符以及引用占位符,將分別舉例說明:
順序占位符:
String hql = "from User user WHERE user.name = ? AND user.age = ?";
List<User> list = session.createQuery(hql).setString(0, "Erica")
.setInteger(1, 10).list();
引用占位符:
String hql = "from User user WHERE user.uName = :name AND user.uAge = :age";
List<User> list = session.createQuery(hql).setString("name", "Erica")
.setInteger("age", 10).list();
我們甚至還可以用一個JavaBean來封裝查詢參數(shù)。
參數(shù)綁定機制可以使得查詢語法與具體參數(shù)數(shù)值相互獨立。這樣,對于參數(shù)不同,查詢語法相同的查詢操作,數(shù)據(jù)庫即可實施性能優(yōu)化策略。同時,參數(shù)綁定機制也杜絕了參數(shù)值對查詢語法本身的影響,這也就是避免了SQL Injection的可能。
我們可能遇到過如下編碼規(guī)范:“代碼中不允許出現(xiàn)SQL語句”。
SQL語句混雜在代碼之間將破壞代碼的可讀性,并似的系統(tǒng)的可維護性降低。為了避免這樣的情況,我們通常采取將SQL配置化的方式,也就是說,將SQL保存在配置文件中。Hibernate提供了HQL可配置化的內(nèi)置支持。
我們可以在實體映射文件中,通過query節(jié)點定義查詢語句(與class節(jié)點同級):
<query name="queryTest"><![CDATA[FROM User user where user.uAge < 20]]></query>
需要注意的是,我們是將HQL語句寫入到了xml文件中,所以可能會造成沖突。例如HQL語句的的“<”(小于)與xml的語法有沖突。所以我們會用CDATA將其包裹。
之后我們可以通過session的getNamedQuery方法從配置文件中調(diào)用對應(yīng)的HQL,如:
Query query = session.getNamedQuery("queryTest");
List<User> list = query.list();
for(User user : list){
System.out.println(user);
}
關(guān)于這部分的內(nèi)容,參考了很多書上的資料,但都感覺講的不夠清晰,也就是說沒有結(jié)合到實際的情況中去。下面將按照一個視頻教程上的順序來介紹關(guān)聯(lián)查詢。
關(guān)于這部分的知識點,是鑒于已經(jīng)對關(guān)聯(lián)連接查詢有所了解的基礎(chǔ)上,比如懂得什么是左外連接、內(nèi)連接等。 下面就開始總結(jié):
LEFT JOIN FETCH
關(guān)鍵字表示使用迫切左外連接策略。
首先看一下例子中的實體類,這是一個雙向1-N的映射(關(guān)于1-N的映射在之前的博客中有介紹Hibernate關(guān)系映射2:雙向1-N關(guān)聯(lián)).
實體類:
public class Department {
private Integer id;
private String name;
private Set<Employee> emps = new HashSet<>();
//省卻get和set方法
}
public class Employee {
private Integer id;
private String name;
private float salary;
private String email;
private Department dept;
//省去get和set方法
}
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
System.out.println(dept.getName() + "-" + dept.getEmps().size());
}
上面的例子中是想得到所有的部門以及其中的員工。我們通過DISTINCT進行去重。 注意的點:
List<Department> depts = query.list();
depts = new ArrayList<>(new LinkedHashSet(depts));
String hql = "FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Object[]> result = query.list();
System.out.println(result);
for (Object[] objs : result) {
System.out.println(Arrays.asList(objs));
}
注意的是:通過左外連接返回的list是一個包含Department和與之連接的Employee的object數(shù)組。所以我們還需要對數(shù)組進行處理,并且有重復(fù)。鑒于此,我們可以通過DISTINCT進行處理。
String hql = "SELECT DISTINCT d FROM Department d LEFT JOIN d.emps";
Query query = session.createQuery(hql);
List<Department> depts = query.list();
System.out.println(depts.size());
for (Department dept : depts) {
System.out.println(dept.getName() + ", " + dept.getEmps().size());
}
注意:
我們真正進行的過程中,一般都會使用迫切左外連接。因為迫切左外連接只發(fā)送了一條SQL語句將所有信息都查出來,而左外連接就算不使用集合的對象,也會進行查詢,而當真正使用集合的時候,又會再去查詢。所以性能上迫切左外連接要好。
子查詢可以在HQL中利用另外一條HQL的查詢結(jié)果。 例如:
FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 1
HQL中,子查詢必須出現(xiàn)在where子句中,且必須以一對圓括號包圍。
更多建議: