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