一、實體對象查詢
實體對象查詢是hql查詢的基礎,作為一種對象查詢語言,在查詢操作時和sql不同,查詢字符串中的內容要使用類名和類的屬性名來代替。這種查詢方法相對簡單,只要有SQL功底,使用hql是很簡單的,但是有一些問題需要注意,就是查詢獲取數據不是目的,需要考慮的是如何編寫出高效的查詢語句,這才是討論的重點。
1.N+1問題
(1)什么是N+1問題
在剛聽到這個名詞時疑惑可能是有的,以前根本就沒有聽過N+1問題,那么它是指什么呢?N+1指的是一張表中有N條數據,那么在獲取這N條數據時會產生N+1條sql命令,這種操作被稱為N+1。這里的1指的是發出一條查詢id列表的語句,N則指根據id發出N條sql語句,加載相關的對象。這種查詢操作效率很低,往往產生在迭代器中,也就是說如果我們將查詢結果直接轉化為迭代器,這時候就會出現這種問題,如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public void testQuery(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); /** * 會出現N+1問題,所謂的N+1值得是發出了N+1條sql語句 * * 1:發出一條查詢id列表的語句 * * N:根據id發出N條sql語句,加載相關的對象 */ Iterator iter=session.createQuery( "from Student" ).iterate(); while (iter.hasNext()){ Student student=(Student)iter.next(); System.out.println(student.getName()); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
上面的這段查詢代碼就會產生N+1問題,因為查詢時返回的是一個迭代器,這樣沒產生一次就會發出一條sql語句,這主要取決于Iterator的這種查詢機制,它是從緩存中查詢數據,如果緩存中不存在該數據那么首先會將數據轉換到內存中,所以這時候就會發出一條sql查詢語句,所以在每次迭代時就會產生一條sql語句。這種寫法其實是一種錯誤,可以使用其它方法優化解決。
(2)避免N+1問題
出現了N+1的問題是因為Iterate使用不當的原因,當然可以使用其它的方法來避免這種N+1的問題,這里介紹一種List的方法。List和Iterate最大的區別是List將數據放到緩存中,但是不利用緩存,默認情況下list每次都會發出sql語句。可以在使用Iterate之前,使用list將數據保存到數據庫中,這樣Iterate訪問數據時就可以從緩存中讀取,避免了N+1問題的出現,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public void testQuery(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); List students=session.createQuery( "from Student" ).list(); System.out.println( "---------------------------------" ); /** * 避免了N+1問題 * * 因為執行list操作后會將數據放到session的緩存中(一級緩存),所以采用iterate的時候 * 首先會發出一條查詢id列表的語句,再根據id到緩存中加載相應的數據,如果緩存中存在與之匹配的數據 * 則不再發出根據id查詢的sql語句,直接使用緩存中的數據 * * Iterate方法如果緩存中存在數據,它可以提高性能,否則出現N+1問題 */ //可以使用as別名 Iterator iter=session.createQuery( "from Student" ).iterate(); while (iter.hasNext()){ Student student=(Student)iter.next(); System.out.println(student.getName()); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
上例 避免了N+1問題,因為執行list操作后會將數據放到session的緩存中(一級緩存),所以采用iterate的時候首先會發出一條查詢id列表的語句,再根據id到緩存中加載相應的數據,如果緩存中存在與之匹配的數據, 則不再發出根據id查詢的sql語句,直接使用緩存中的數據。 Iterate方法如果緩存中存在數據,它可以提高性能,否則出現N+1問題。
2.對象導航查詢
對象導航是指在一個對象中按照對象的屬性導航獲取到另一個對象的數據,這樣做可以簡化查詢語句,優化查詢方法。如果按照我們平常的想法可能會再重寫編寫另一個類的對象,來獲取另一個對象的操作,和對象導航對比語句比較累贅。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@SuppressWarnings ({ "unchecked" , "rawtypes" }) public void testQuery1(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); //返回結果集屬性列表,元素類型和實體類中的屬性類型一致 List students=session.createQuery( "from Student s where s.classes.name like '%2%'" ).list(); for (Iterator ite=students.iterator();ite.hasNext();){ Student obj=(Student)ite.next(); System.out.println(obj.getName()); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
上例中的查詢語句就使用了對象導航的方法,查詢語句是從Student對象中查詢的信息,但是要比對的對象屬性卻是來自于Classes對象的name屬性,這時候使用對象導航的查詢方法就會明顯提高查詢效率,優化查詢語句,如果換做普通的查詢方法就可能會產生大量的連接語句,很復雜。
二、sql原生查詢
原生查詢值的是使用SQL語句來查詢獲取數據,而不是采用hql語句,它的使用方法其實很簡單,同hql類似,只需要使用createSQLQuery方法查詢即可,它其實類似于hql的createQuery方法,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public void testQeury(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); List list=session.createSQLQuery( "select * from t_student" ).list(); for (Iterator ite=list.iterator();ite.hasNext();){ Object[] obj=(Object[])ite.next(); System.out.println(obj[ 0 ]+ "," +obj[ 1 ]); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
上面的代碼使用了createSQLQuery方法,方法內的查詢字符串就是SQL語句,它實現了底層的字符串查詢方法,不同的是HQL又做了一層包裝,在Hibernate.cfg.xml中配置相應的方言選項即可完成映射。
三、連接查詢
在sql中經常使用連接查詢來獲取多個對象的合集,其中經常用到的有inner join、left join、right join等,分別指代內連接查詢、左外連接查詢、右外連接查詢,它們在查詢時返回的內容分別是實體之間的笛卡爾積,查詢的內容及左表的一些內容,查詢內容及右表的一些內容,查詢的功能強大。hql的連接查詢方法和sql的連接查詢在查詢結果上是相同的,但是在查詢語句上稍有區別。
1.內連接
hql的內連接查詢可使用inner join語句或者join語句查詢,獲取的結果集是笛卡爾積。同sql的內連接查詢類似,hql的join查詢又分為顯式與隱式兩種,顯示的查詢是指查詢字符串中有join關鍵字,隱式的查詢在字符串中不需要添加join。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//內連接 @SuppressWarnings ({ "unchecked" , "rawtypes" }) public void testQuery(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); //返回結果集屬性列表,元素類型和實體類中的屬性類型一致 List students=session.createQuery( "select s.name,c.name from Student s join s.classes c" ).list(); for (Iterator ite=students.iterator();ite.hasNext();){ Object[] obj=(Object[])ite.next(); System.out.println(obj[ 0 ]); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
2.外連接
外連接又分為左外連接和右外連接查詢,查詢方法類似,但是查詢出的結果集不同,它們在查詢結果上和SQL的外連接相同,不同的是寫法,具體使用代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@SuppressWarnings ({ "unchecked" , "rawtypes" }) public void testQuery(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); //返回結果集屬性列表,元素類型和實體類中的屬性類型一致 List students=session.createQuery( "select s.name,c.name from Student s left join s.classes c" ).list(); for (Iterator ite=students.iterator();ite.hasNext();){ Object[] obj=(Object[])ite.next(); System.out.println(obj[ 0 ]); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |
上面的代碼使用的是左外連接查詢語句,相應的右外連接查詢的方法和左外連接類似,將left轉換為right即可。查詢到的數據被保存到list對象中,可通過list來獲取查詢內容。
四、外置命名查詢
外置命名查詢是指將查詢語句寫到映射文件中,在映射文件中使用<query>標簽來定義hql語句,這樣定義的hql語句就能夠實現功能配置功能,如果出現問題只需要修改配置即可。如果想用使用該sql語句,可在程序中使用session.getNamedQuery()方法得到hql查詢串,如下示例。
1.外置查詢語句
下面示例中演示了外置查詢語句的應用,在映射文件中添加<query>標簽,并為該標簽添加name屬性,將字符串添加到<![CDATA[]]>中,這樣既可在程序中按照query的name屬性獲取對應的查詢字符串了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<? xml version = "1.0" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> < hibernate-mapping > < class name = "com.src.hibernate.Student" table = "t_student" > < id name = "id" > < generator class = "native" /> </ id > < property name = "name" /> < property name = "createTime" ></ property > <!-- 在多的一端Student中添加一行新的Classes列 ,并且列的名稱要和Classes.hbm.xml的列明相同--> < many-to-one name = "classes" column = "classesid" ></ many-to-one > </ class > < query name = "queryStudent" > <![CDATA[ select s from Student s where s.id<? ]]> </ query > </ hibernate-mapping > |
外置的命名查詢將查詢語句放到了映射文件中,所以它可以被認為是公共的查詢字符串,在程序文件中都可以查詢使用該字符串,這是它的優點,這樣其它的程序文件就都可以獲取使用了,另外作為公共的字符串增加了修改的便利性。
2.程序應用
定義了外置的查詢語句后要在程序中使用,hql提供了getNameQuery方法來獲取外置的查詢語句串,該方法中需要添加一個外置的游標名稱,hql會根據游標名稱查詢獲取相對應的sql語句塊,如下的程序代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//外置命名查詢 @SuppressWarnings ({ "unchecked" , "rawtypes" }) public void testQuery(){ Session session= null ; try { session=HibernateUtils.getSession(); session.beginTransaction(); List students=session.getNamedQuery( "queryStudent" ).setParameter( 0 , 10 ).list(); for (Iterator ite=students.iterator();ite.hasNext();){ Student obj=(Student)ite.next(); System.out.println(obj.getName()); } session.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); session.getTransaction().rollback(); } finally { HibernateUtils.closeSession(session); } } |