Hibernate:深入HQL學(xué)習(xí)

2018-08-12 21:21 更新

導(dǎo)讀


HQL(Hibernate Query Language) 是面向?qū)ο蟮牟樵冋Z言, 它和 SQL 查詢語言有些相似. 在 Hibernate 提供的各種檢索方式中, HQL 是使用最廣的一種檢索方式. 它有如下功能:

  1. 在查詢語句中設(shè)定各種查詢條件;
  2. 支持投影查詢, 即僅檢索出對象的部分屬性;
  3. 支持分頁查詢;
  4. 支持連接查詢;
  5. 支持分組查詢, 允許使用 HAVING 和 GROUP BY 關(guān)鍵字;
  6. 提供內(nèi)置聚集函數(shù), 如 sum(), min() 和 max();
  7. 支持子查詢;
  8. 支持動態(tài)綁定參數(shù);
  9. 能夠調(diào)用 用戶定義的 SQL 函數(shù)或標準的 SQL 函數(shù)。

HQL 查詢包括以下步驟:

  1. 獲取Hibernate Session對象。
  2. 編寫HQL語句
  3. 以HQL語句作為參數(shù),調(diào)用Session的createQuery方法創(chuàng)建查詢對象。
  4. 如果HQL語句包含參數(shù),則調(diào)用Query的setXxx方法為參數(shù)賦值。
  5. 調(diào)用Query對象的list()或uniqueResult()方法返回查詢結(jié)果列表(持久化實體集)

Qurey 接口支持方法鏈編程風(fēng)格, 它的 setXxx() 方法返回自身實例, 而不是 void 類型,因此可以寫類似于.setXxx().setXxx().setXxx()...樣式的語句。

HQL vs SQL


HQL 查詢語句是面向?qū)ο蟮? Hibernate 負責(zé)解析 HQL 查詢語句, 然后根據(jù)對象-關(guān)系映射文件中的映射信息, 把 HQL 查詢語句翻譯成相應(yīng)的 SQL 語句。HQL 查詢語句中的主體是域模型中的類及類的屬性。

SQL 查詢語句是與關(guān)系數(shù)據(jù)庫綁定在一起的。SQL 查詢語句中的主體是數(shù)據(jù)庫表及表的字段。

HQL實用技術(shù)


實體查詢

最簡單實體查詢例子:

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)致緩存同步上的障礙。

分組與排序

Order by子句

舉例說明:

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子句

通過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注入

在解釋參數(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)聯(lián)查詢

關(guān)于這部分的內(nèi)容,參考了很多書上的資料,但都感覺講的不夠清晰,也就是說沒有結(jié)合到實際的情況中去。下面將按照一個視頻教程上的順序來介紹關(guān)聯(lián)查詢。

關(guān)于這部分的知識點,是鑒于已經(jīng)對關(guān)聯(lián)連接查詢有所了解的基礎(chǔ)上,比如懂得什么是左外連接、內(nèi)連接等。 下面就開始總結(jié):

HQL迫切左外連接

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對象關(guān)聯(lián)的Employee集合都將被初始化,存放所有關(guān)聯(lián)的Employee的實體對象
  • 查詢結(jié)果中可能會包含重復(fù)元素,可以通過DISTINCT關(guān)鍵字去重,同樣由于list中存放的實體對象的引用,所以可以通過HashSet來過濾重復(fù)對象。例如:
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());
        }

注意:

  • list方法返回的集合中存放的是對象數(shù)組類型
  • 根據(jù)配置文件來決定Employee集合的檢索策略,比如是fetch、lazy啊或者怎樣,還不是像迫切左外連接那樣。

我們真正進行的過程中,一般都會使用迫切左外連接。因為迫切左外連接只發(fā)送了一條SQL語句將所有信息都查出來,而左外連接就算不使用集合的對象,也會進行查詢,而當真正使用集合的時候,又會再去查詢。所以性能上迫切左外連接要好。

子查詢

子查詢可以在HQL中利用另外一條HQL的查詢結(jié)果。 例如:

FROM User user WHERE (SELECT COUNT(*) FROM user.address) > 1

HQL中,子查詢必須出現(xiàn)在where子句中,且必須以一對圓括號包圍。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號