1. 程式人生 > >Hibernate查詢效能優化技巧

Hibernate查詢效能優化技巧

4.3 使用HQL查詢

Hibernate提供了異常強大的查詢體系,使用Hibernate有多種查詢方式。可以選擇使用Hibernate的HQL查詢,或者使用條件查詢,甚至可以使用原生的SQL查詢語句,此外還提供了一種資料過濾功能,這些都可用於篩選目標資料。

下面分別介紹Hibernate的4種資料篩選方法:

4.3.1 HQL查詢

HQL是Hibernate Query Language的縮寫,HQL的語法很像SQL的語法,但HQL是一種面向物件的查詢語言。因此,SQL的操作物件是資料表和列等資料物件,而HQL的操作物件是類、例項、屬性等。

HQL是完全面向物件的查詢語言,因此可以支援繼承和多型等特徵。

HQL查詢依賴於Query類,每個Query例項對應一個查詢物件。使用HQL查詢可按如下步驟進行:

(1)獲取Hibernate Session物件;

(2)編寫HQL語句;

(3)以HQL語句作為引數,呼叫Session的createQuery方法建立查詢物件;

(4)如果HQL語句包含引數,呼叫Query的setXxx方法為引數賦值;

(5)呼叫Query物件的list等方法遍歷查詢結果。

看下面的查詢示例:

public class HqlQuery

{

    public static void main(String[] args)throws Exception

    {

        HqlQuery mgr = new HqlQuery();

        //呼叫查詢方法

        mgr.findPersons();

        //呼叫第二個查詢方法

        mgr.findPersonsByHappenDate();

        HibernateUtil.sessionFactory.close();

    }

    //第一個查詢方法

    private void findPersons()

    {

        //獲得Hibernate Session

        Session sess = HibernateUtil.currentSession();

        //開始事務

        Transaction tx = sess.beginTransaction();

        //以HQL語句建立Query物件.

        //執行setString方法為HQL語句的引數賦值

        //Query呼叫list方法訪問查詢的全部例項

        List pl = sess.createQuery("from Person p where p.myEvents.title

        = :eventTitle")

                        .setString("eventTitle","很普通事情")

                        .list();

        //遍歷查詢的全部結果

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        //提交事務

        tx.commit();

        HibernateUtil.closeSession();

    }

    //第二個查詢方法

    private void findPersonsByHappenDate()throws Exception

    {

        //獲得Hibernate Session物件

        Session sess = HibernateUtil.currentSession();

        Transaction tx = sess.beginTransaction();

        //解析出Date物件

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Date start = sdf.parse("2005-01-01");

        System.out.println("系統開始通過日期查詢人" + start);

        //通過Session的createQuery方法建立Query物件

        //設定引數

        //返回結果集

        List pl = sess.createQuery(

            "from Person p where p.myEvents.happenDate between :firstDate

            and :endDate")

                        .setDate("firstDate",start)

                        .setDate("endDate",new Date())

                        .list();

        //遍歷結果集

        for (Iterator pit = pl.iterator() ; pit.hasNext(); )

        {

            Person p = ( Person )pit.next();

            System.out.println(p.getName());

        }

        tx.commit();

        HibernateUtil.closeSession();

    }

}

通過上面的示例程式,可看出查詢步驟基本相似。Query物件可以連續多次設定引數,這得益於Hibernate Query的設計。

通常,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query本身。因此,程式通過Session建立Query後,直接多次呼叫setXxx方法為HQL語句的引數賦值,再直接呼叫list方法返回查詢到的全部結果即可。

Query還包含兩個方法:

   ● setFirstResult(int firstResult),設定返回的結果集從第幾條記錄開始。

   ● setMaxResults(int maxResults),設定本次查詢返回的結果數。

這兩個方法用於實現Hibernate分頁。

下面簡單介紹HQL語句的語法。

HQL語句本身是不區分大小寫的。也就是說,HQL語句的關鍵字和函式都是不區分大小寫的。但HQL語句中所使用的包名、類名、例項名和屬性名都區分大小寫。

4.3.2 HQL查詢的from子句

from子句是最簡單的HQL語句,也是最基本的HQL語句。from關鍵字後緊跟持久化類的類名。例如:

from Person

表明從Person持久化類中選出全部的例項。

大部分時候,推薦為該Person的每個例項起別名。例如:

from Person as p

在上面的HQL語句中,Person持久化類中的例項的別名為p,既然 p是例項名,因此也應該遵守Java的命名規則:第一個單詞的首字母小寫,後面每個單詞的首字母大寫。

命名別名時,as關鍵字是可選的,但為了增加可讀性,建議保留。

from後還可同時出現多個持久化類,此時將產生一個笛卡兒積或跨表的連線。

4.3.3 HQL查詢的select子句

select子句用於確定選擇出的屬性,當然select選擇的屬性必須是from後持久化類包含的屬性。例如:

select p.name from Person as p

select可以選擇任意屬性,不僅可以選擇持久化類的直接屬性,還可以選擇元件屬性包含的屬性,例如:

select p.name.firstName from Person as p

select也支援將選擇出的屬性存入一個List物件中,例如:

select new list(p.name , p.address) from Person as p

甚至可以將選擇出的屬性直接封裝成物件,例如:

select new ClassTest(p.name , p.address) from Person as p

前提是ClassTest支援p.name和p.address的構造器,假如p.name的資料型別是           String,p.address的資料型別是String,則ClassTest必須有如下的構造器:

ClassTest(String s1, String s2)

select還支援給選中的表示式命名別名,例如:

select p.name as personName from Person as p

這種用法與new map結合使用更普遍。如:

select new map(p.name as personName) from Person as p

在這種情形下,選擇出的是Map結構,以personName為key,實際選出的值作為value。

4.3.4 HQL查詢的聚集函式

HQL也支援在選出的屬性上,使用聚集函式。HQL支援的聚集函式與SQL完全相同,有如下5個:

   ● avg,計算屬性平均值。

   ● count,統計選擇物件的數量。

   ● max,統計屬性值的最大值

   ● min,統計屬性值的最小值。

   ● sum,計算屬性值的總和。

例如:

select count(*) from Person

select max(p.age) from Person as p

select子句還支援字串連線符、算術運算子以及SQL函式。如:

select p.name || "" || p.address from Person as p

select子句也支援使用distinct和all關鍵字,此時的效果與SQL中的效果完全相同。

4.3.5 多型查詢

HQL語句被設計成能理解多型查詢,from後跟的持久化類名,不僅會查詢出該持久化類的全部例項,還會查詢出該類的子類的全部例項。

如下面的查詢語句:

from Person as p

該查詢語句不僅會查詢出Person的全部例項,還會查詢出Person的子類,如Teacher的全部例項,前提是Person和Teacher完成了正確的繼承對映。

HQL支援在from子句中指定任何Java類或介面,查詢會返回繼承了該類的持久化子類的例項或返回實現該介面的持久化類的例項。下面的查詢語句返回所有被持久化的物件:

from java.lang.Object o

如果Named介面有多個持久化類,下面的語句將返回這些持久化類的全部例項:

from Named as n

注意:後面的兩個查詢將需要多個SQL SELECT語句,因此無法使用order by子句對結果集進行排序,從而,不允許對這些查詢結果使用Query.scroll()方法。

4.3.6 HQL查詢的where子句

where子句用於篩選選中的結果,縮小選擇的範圍。如果沒有為持久化例項命名別名,可以直接使用屬性名引用屬性。

如下面的HQL語句:

from Person where name like 'tom%'

上面HQL語句與下面的語句效果相同:

from Person as p where p.name like "tom%"

在後面的HQL語句中,如果為持久化例項命名了別名,則應該使用完整的屬性名。兩個HQL語句都可返回name屬性以tom開頭的例項。

複合屬性表示式加強了where子句的功能,例如如下HQL語句:

from Cat cat where cat.mate.name like "kit%"

該查詢將被翻譯成為一個含有內連線的SQL查詢,翻譯後的SQL語句如下:

select * from cat_table as table1 cat_table as table2 where table1.mate =

table2.id and table1.name like "kit%"

再看下面的HQL查詢語句:

from Foo foo where foo.bar.baz.customer.address.city like"guangzhou%"

翻譯成SQL查詢語句,將變成一個四表連線的查詢。

=運算子不僅可以被用來比較屬性的值,也可以用來比較例項:

from Cat cat, Cat rival where cat.mate = rival.mate

select cat, mate

from Cat cat, Cat mate

where cat.mate = mate

特殊屬性(小寫)id可以用來表示一個物件的識別符號。(也可以使用該物件的屬性名。)

from Cat as cat where cat.id = 123

from Cat as cat where cat.mate.id = 69

第二個查詢是一個內連線查詢,但在HQL查詢語句下,無須體會多表連線,而完全使用面向物件方式的查詢。

id也可代表引用識別符號。例如,Person類有一個引用識別符號,它由country屬性 與medicareNumber兩個屬性組成。

下面的HQL語句有效:

from Person as person

where person.id.country = 'AU'

    and person.id.medicareNumber = 123456

from Account as account

where account.owner.id.country = 'AU'

    and account.owner.id.medicareNumber = 123456

第二個查詢跨越兩個表Person和Account。是一個多表連線查詢,但此處感受不到多表連線查詢的效果。

在進行多型持久化的情況下,class關鍵字用來存取一個例項的鑑別值(discriminator value)。嵌入where子句中的Java類名,將被作為該類的鑑別值。例如:

from Cat cat where cat.class = DomesticCat

where子句中的屬性表示式必須以基本型別或java.lang.String結尾,不要使用元件型別屬性結尾,例如Account有Person屬性,而Person有Name屬性,Name有firstName屬性。

看下面的情形:

from Account as a where a.person.name.firstName like "dd%" //正確

from Account as a where a.person.name like "dd%" //錯誤

4.3.7 表示式

HQL的功能非常豐富,where子句後支援的運算子異常豐富,不僅包括SQL的運算子,還包括EJB-QL的運算子等。

where子句中允許使用大部分SQL支援的表示式:

   ● 數學運算子+、–、*、/ 等。

   ● 二進位制比較運算子=、>=、<=、<>、!=、like等。

   ● 邏輯運算子and、or、not等。

   ● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。

   ● 簡單的case、case ... when ... then ... else ... end和case、case when ... then ... else ...       end等。

   ● 字串連線符value1 || value2或使用字串連線函式concat(value1 , value2)。

   ● 時間操作函式current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。

   ● HQL還支援EJB-QL 3.0所支援的函式或操作substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。

   ● 還支援資料庫的型別轉換函式,如cast(... as ...),第二個引數是Hibernate的型別名,或者extract(... from ...),前提是底層資料庫支援ANSI cast() 和extract()。

   ● 如果底層資料庫支援如下單行函式sign()、trunc()、rtrim()、sin()。則HQL語句也完全可以支援。

   ● HQL語句支援使用?作為引數佔位符,這與JDBC的引數佔位符一致,也可使用命名引數佔位符號,方法是在引數名前加冒號 :,例如 :start_date和:x1等。

   ● 當然,也可在where子句中使用SQL常量,例如'foo'、69、'1970-01-01 10:00:         01.0'等。

   ● 還可以在HQL語句中使用Java public static final 型別的常量,例如eg.Color.TABBY。

除此之外,where子句還支援如下的特殊關鍵字用法。

   ● in與between...and可按如下方法使用:

from DomesticCat cat where cat.name between 'A' and 'B'

from DomesticCat cat where cat.name in ( 'Foo','Bar','Baz')

   ● 當然,也支援not in和not between...and的使用,例如:

from DomesticCat cat where cat.name not between 'A' and 'B'

from DomesticCat cat where cat.name not in ( 'Foo','Bar','Baz' )

   ● 子句is null與is not null可以被用來測試空值,例如:

from DomesticCat cat where cat.name is null;

from Person as p where p.address is not null;

如果在Hibernate配置檔案中進行如下宣告:

<property name="hibernate.query.substitutions">true 1, false 0</property>

上面的宣告表明,HQL轉換SQL語句時,將使用字元1和0來取代關鍵字true和false。然後將可以在表示式中使用布林表示式,例如:

from Cat cat where cat.alive = true

   ● size關鍵字用於返回一個集合的大小,例如:

from Cat cat where cat.kittens.size > 0

from Cat cat where size(cat.kittens) > 0

   ● 對於有序集合,還可使用minindex與maxindex函式代表最小與最大的索引序數。同理,可以使用minelement與maxelement函式代表集合中最小與最大的元素。         例如:

from Calendar cal where maxelement(cal.holidays) > current date

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

   ● 可以使用SQL函式any、some、all、exists、in操作集合裡的元素,例如:

//操作集合元素

select mother from Cat as mother, Cat as kit

where kit in elements(foo.kittens)

//p的name屬性等於集合中某個元素的name屬性

select p from NameList list, Person p

where p.name = some elements(list.names)

//操作集合元素

from Cat cat where exists elements(cat.kittens)

from Player p where 3 > all elements(p.scores)

from Show show where 'fizard' in indices(show.acts)

注意這些結構變數size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。

   ● where子句中,有序集合的元素(arrays, lists, maps)可以通過[ ]運算子訪問。例如:

//items是有序集合屬性,items[0]代表第一個元素

from Order order where order.items[0].id = 1234

//holidays是map集合屬性,holidays[national day]代表其中一個元素

select person from Person person, Calendar calendar

where calendar.holidays['national day'] = person.birthDay

and person.nationality.calendar = calendar

//下面同時使用list 集合和map集合屬性

select item from Item item, Order order

where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11

select item from Item item, Order order

where order.items[ maxindex(order.items) ] = item and order.id = 11

在[]中的表示式甚至可以是一個算術表示式,例如:

select item from Item item, Order order

where order.items[ size(order.items) - 1 ] = item

藉助於HQL,可以大大簡化選擇語句的書寫,提高查詢語句的可讀性,看下面的HQL語句:

select cust

from Product prod,

    Store store

    inner join store.customers cust

where prod.name = 'widget'

    and store.location.name in ( 'Melbourne', 'Sydney' )

    and prod = all elements(cust.currentOrder.lineItems)

如果翻譯成SQL語句,將變成如下形式:

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order

FROM customers cust,

    stores store,

    locations loc,

    store_customers sc,

    product prod

WHERE prod.name = 'widget'

    AND store.loc_id = loc.id

    AND loc.name IN ( 'Melbourne', 'Sydney' )

    AND sc.store_id = store.id

    AND sc.cust_id = cust.id

    AND prod.id = ALL(

        SELECT item.prod_id

        FROM line_items item, orders o

        WHERE item.order_id = o.id

            AND cust.current_order = o.id

    )

4.3.8 order by子句

查詢返回的列表(list)可以根據類或元件屬性的任何屬性進行排序,例如:

from Person as p

order by p.name, p.age

還可使用asc或desc關鍵字指定升序或降序的排序規則,例如:

from Person as p

order by p.name asc , p.age desc

如果沒有指定排序規則,預設採用升序規則。即是否使用asc關鍵字是沒有區別的,加asc是升序排序,不加asc也是升序排序。

4.3.9 group by子句

返回聚集值的查詢可以對持久化類或元件屬性的屬性進行分組,分組所使用的group by子句。看下面的HQL查詢語句:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

類似於SQL的規則,出現在select後的屬性,要麼出現在聚集函式中,要麼出現在group by的屬性列表中。看下面示例:

//select後出現的id出現在group by之後,而name屬性則出現在聚集函式中

select foo.id, avg(name), max(name)

from Foo foo join foo.names name

group by foo.id

having子句用於對分組進行過濾,如下:

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

注意:having子句用於對分組進行過濾,因此having子句只能在有group by子句時才可以使用,沒有group by子句,不能使用having子句。

Hibernate的HQL語句會直接翻譯成資料庫SQL語句。因此,如果底層資料庫支援的having子句和group by子句中出現一般函式或聚集函式,HQL語句的having子句和order by 子句中也可以出現一般函式和聚集函式。

例如:

select cat

from Cat cat

join cat.kittens kitten

group by cat

having avg(kitten.weight) > 100

order by count(kitten) asc, sum(kitten.weight) desc

注意:group by子句與 order by子句中都不能包含算術表示式。

4.3.10 子查詢

如果底層資料庫支援子查詢,則可以在HQL語句中使用子查詢。與SQL中子查詢相似的是,HQL中的子查詢也需要使用()括起來。如:

from Cat as fatcat

where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )

如果select中包含多個屬性,則應該使用元組構造符:

from Cat as cat

where not ( cat.name, cat.color ) in (

    select cat.name, cat.color from DomesticCat cat

)

4.3.11 fetch關鍵字

對於集合屬性,Hibernate預設採用延遲載入策略。例如,對於持久化類Person,有集合屬性scores。載入Person例項時,預設不載入scores屬性。如果Session被關閉,Person例項將無法訪問關聯的scores屬性。

為了解決該問題,可以在Hibernate對映檔案中取消延遲載入或使用fetch join,例如:

from Person as p join p.scores

上面的fetch語句將會初始化person的scores集合屬性。

如果使用了屬性級別的延遲獲取,可以使用fetch all properties來強制Hibernate立即抓取那些原本需要延遲載入的屬性,例如:

from Document fetch all properties order by name

from Document doc fetch all properties where lower(doc.name) like '%cats%'

4.3.12 命名查詢

HQL查詢還支援將查詢所用的HQL語句放入配置檔案中,而不是程式碼中。通過這種方式,可以大大提供程式的解耦。

使用query元素定義命名查詢,下面是定義命名查詢的配置檔案片段:

<!-- 定義命名查詢 -->

<query name="myNamedQuery">

    <!-- 此處確定命名查詢的HQL語句 -->

    from Person as p where p.age > ?

</query>

該命名的HQL查詢可以直接通過Session訪問,呼叫命名查詢的示例程式碼如下:

private void findByNamedQuery()throws Exception

{

    //獲得Hibernate Session物件

    Session sess = HibernateUtil.currentSession();

    //開始事務

    Transaction tx = sess.beginTransaction();

    System.out.println("執行命名查詢");

    //呼叫命名查詢

    List pl = sess.getNamedQuery("myNamedQuery")

                        //為引數賦值

                       .setInteger(0 , 20)

                        //返回全部結果

                       .list();

    //遍歷結果集

    for (Iterator pit = pl.iterator() ; pit.hasNext(); )

    {

        Person p = ( Person )pit.next();

        System.out.println(p.getName());

    }

    //提交事務

    tx.commit();

    HibernateUtil.closeSession();

}

posted @ 2009-07-19 08:48 jadmin 閱讀(4) 評論(0) 編輯