Hibernate(五)HQL語句
HQL語句是hibernate中的一個重要知識,今天我們來看看HQL語句吧,下面有三個類圖,我們就以這三個類為例講解HQL語句吧。
既然我們現在已經學習了兩種配置hibernate的方式(XML和annotation),下面我們就使用這兩種方式來配置出兩個專案來講解HQL(hibernate中的XML和annotation配置後的執行結果不一樣的,這個我們前面就說過了)。我們使用雙向關聯來實現:
HQL(hibernate query language)的基本使用
Hibernate中,我們前面實現了一個基本的CURD操作的,當時我們發現一些基本的增刪改查hibernate已經幫助我們完成了,那麼如果我們遇到一些比較複雜的查詢時應該如果使用呢(如多表關聯查詢)?這個我們就需要使用hibernate的HQL語句來完成查詢了。
基本的HQL查詢
下面我們來做一個簡單的列表查詢吧:
@Test publicvoid test01() { Session session = null; try { session = HibernateUtil.openSession(); //注意:是基於物件的查詢,不是基於資料庫表的查詢--> Special Query query = session.createQuery("from Special"); List<Special> specials = query.list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
執行結果如下:
我們發現hibernate的hql語句是基於物件的查詢,注意:from後面的是類名,不是表名。那麼我們還能使用select * from 表名這種查詢嗎?
@Test publicvoid test02() { Session session = null; try { session = HibernateUtil.openSession Query query = session.createQuery("select * from Special"); List<Special> specials = query.list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我們發現,HQL語句無法使用select *這種sql語句,那麼如果我們想要實現類似的效果怎麼辦呢?
@Test publicvoid test02() { Session session = null; try { session = HibernateUtil.openSession(); //HQL語句不能使用select * 這種查詢 //Query query = session.createQuery("select * from Special"); //我們可以使用基於別名的查詢,使用別名來實現sql類似於 //select *的這種查詢 //也可以使用這種鏈式結構來查詢 List<Special> specials = session. createQuery("select s from Special as s").list(); for (Special special : specials) { System.out.println(special.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
在真正的專案中,我們肯定會遇到帶條件的查詢的,那麼HQL如何使用條件查詢呢?
@Test publicvoid test03() { Session session = null; try { session = HibernateUtil.openSession(); List<Student> students = session. createQuery("from Student where name like '%劉%' ").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這種查詢我們在JDBC的時候就說過,當sql語句需要拼接的話,會存在sql注入漏洞問題,所以我們一般都是用?來為引數賦值。
@Test publicvoid test04() { Session session = null; try { session = HibernateUtil.openSession(); //也可以使用?問號的方式來為條件引數賦值 //hibernate的setParameter位置賦值是從0開始的 //JDBC是從1開始的 List<Student> students = session. createQuery("from Student where name like ? ") .setParameter(0, "%劉%").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
兩種方式執行結果一致:
在setParemeter的設定值的方式有基於?問號佔位符的數字(位置)來查詢,也可以基於別名來查詢,下面我們來看看:
@Test publicvoid test05() { Session session = null; try { session = HibernateUtil.openSession(); //當我們不想使用位置來完成賦值的時候 //還可以使用別名來完成賦值,就是在屬性欄位後加:別名 //在賦值的時候使用別名來完成賦值 List<Student> students = session. createQuery("from Student where name like :name and sex=:sex "). setParameter("name", "%林%").setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
可以看出這種方式也能夠完成查詢。
常用的HQL查詢
如果我們查詢資料的數量呢?以前我們使用的是select count(*)來完成查詢,hibernate中如果使用呢?
@Test publicvoid test06() { Session session = null; try { session = HibernateUtil.openSession(); //查詢數量時可以使用count(*)來完成查詢 //注意:使用了查詢數量的返回值是一個Long型別 List<Long> count = session. createQuery("select count(*) from Student where sex=:sex "). setParameter("sex", "男").list(); System.out.println(count.get(0)); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
執行結果如下:
我們發現這次的結果是一個唯一的值,只有一個數字,使用List接受感覺不太方便,那麼當以後我們再遇到這種查詢結果只有一個值的時候,我們可以不使用list方法,使用uniqueResult方法表示查詢結果只有一個結果集。
@Test publicvoid test06() { Session session = null; try { session = HibernateUtil.openSession(); //uniqueResult表示查詢結果為一個結果集時使用 Long count = (Long) session. createQuery("select count(*) from Student where sex=:sex "). setParameter("sex", "男").uniqueResult(); System.out.println(count); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
注意:uniqueResult返回一個物件,需要強制型別轉換。
如我們查詢一個結果:
@Test publicvoid test07() { Session session = null; try { session = HibernateUtil.openSession(); //uniqueResult表示查詢結果為一個結果集時使用 Student stu1 = (Student) session .createQuery("from Student where id=?") .setParameter(0, 10).uniqueResult(); System.out.println(stu1.getName()); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
如果我們要查詢的不是物件的屬性欄位呢?如計數統計。這樣的話,我們就沒辦法使用具體的物件來接受查詢結果了,所以hibernate提供了通過物件陣列的方式讓我們來接受一些不能使用物件接受的結果集,如下面統計男女各多少人的時候:
@Test publicvoid test08() { Session session = null; try { session = HibernateUtil.openSession(); //投影查詢時(就是無法使用具體的物件來封裝資料的查詢) //需要使用Object的陣列來接受資料 List<Object[]> list = session.createQuery("select s.sex,count(s.sex) from Student s group by s.sex").list(); for (Object[] o : list) { System.out.println(o[0]+":"+o[1]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
使用物件陣列的確可以接受,但是這種方式我們感覺不太習慣,所以hibernate還提供了另一種方式,就是我們寫一個vo物件(vo針對的是po物件,po是我們的實體類物件,vo就是一個標準的Java類,但是不存在資料庫的關係,僅僅作為封裝和傳遞資料的物件存在)。如下面所寫:
@Test publicvoid test09() { Session session = null; try { session = HibernateUtil.openSession(); /** * 我們可以使用vo物件來完成對於對映的查詢,當然有時候 * 我們也會叫它為Dto(資料傳輸物件) * 注意:使用的是new關鍵字,呼叫的使用vo的構造方法 * 所以一定要有這個構造方法才行 */ List<StuCount> list = session.createQuery( "select new com.lzcc.hibernate.entity.StuCount(s.sex as sex ,count(s.sex) as count) from Student s group by s.sex").list(); for (StuCount o : list) { System.out.println(o.getSex()+","+o.getCount()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
需要一個vo(Dto)物件類:
package com.lzcc.hibernate.entity; /** * 注意:這個類沒有其他任何含義 * 僅僅是作為資料的傳輸存在的 */ publicclass StuCount { private String sex; privatelongcount; public String getSex() { returnsex; } publicvoid setSex(String sex) { this.sex = sex; } publiclong getCount() { returncount; } publicvoid setCount(long count) { this.count = count; } public StuCount() { } public StuCount(String sex, long count) { this.sex = sex; this.count = count; } } |
執行效果一致:
我們的查詢還可以基於導航的查詢,如在Student中存在Classroom的屬性欄位,那麼我們就可以通過Classroom的id來完成導航查詢,如查詢id為1的班級的所以學生:
@Test publicvoid test10() { Session session = null; try { session = HibernateUtil.openSession(); //基於導航的查詢 List<Student> students = session. createQuery("from Student where classroom.id = ?"). setParameter(0,1).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這樣使用起來就方便多了。但是上面的查詢本身在一個表中,所以查詢是隻會發送一條sql,那麼如果我們關聯查詢多張表呢?
@Test publicvoid test11() { Session session = null; try { session = HibernateUtil.openSession(); //基於導航的查詢 List<Student> students = session. createQuery("select s from Student s where s.classroom.name = ? and s.sex = :sex"). setParameter(0,"好漢班").setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這種查詢我們發現也是一條sql就完成了兩個表的關聯查詢,但是我們注意到,hibernate使用了cross join來完成關聯的,注意這是一種基於笛卡爾積的查詢,效率不高,如果考慮效率問題的話,不建議使用,當然要使用問題也不大,很方便。
下面我們來查詢一班和二班的所有男學生呢?
@Test publicvoid test12() { Session session = null; try { session = HibernateUtil.openSession(); //基於導航的查詢 List<Student> students = session. createQuery("select s from Student s where s.sex=:sex and (s.classroom.id = ? or s.classroom.id = :id)") .setParameter(0,1).setParameter("sex", "男").setParameter("id", 2).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
如果我們這樣寫的話,我們發現程式碼報錯了,這是因為位置的引數賦值必須在別名的引數前面。
@Test publicvoid test12() { Session session = null; try { session = HibernateUtil.openSession(); //基於導航的查詢 List<Student> students = session. createQuery("select s from Student s where (s.classroom.id = ? or s.classroom.id = :id) and s.sex=:sex ") .setParameter(0,1).setParameter("sex", "男").setParameter("id", 2).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這樣寫就沒有問題了
當然我們也可以使用sql中的in這個關鍵字:
@Test publicvoid test13() { Session session = null; try { session = HibernateUtil.openSession(); //也可以使用sql中in這個關鍵字 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (?,?) and s.sex=:sex ") .setParameter(0,1).setParameter(1, 2).setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
同樣我們發現這兩個id引數是一個列表,這樣的話,hibernate也提供了一個方便與列表賦值的方式:
@Test publicvoid test14() { Session session = null; try { session = HibernateUtil.openSession(); //我們可以使用setParameterList完成列表的賦值,注意這個只能使用別名 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (:clas) and s.sex=:sex ") .setParameterList("clas", new Integer[]{1,2}) .setParameter("sex", "男").list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這兩次的程式碼執行效果如下:
下面我們來看看分頁查詢吧:
@Test publicvoid test15() { Session session = null; try { session = HibernateUtil.openSession(); //使用setFirstResult設定分頁的開始點((當前頁-1)*每頁條數) //使用setMaxResult設定每頁顯示的條數 List<Student> students = session. createQuery("select s from Student s where s.classroom.id in (:clas)") .setParameterList("clas", new Integer[]{1,2}) .setFirstResult(0).setMaxResults(5).list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
下面再看看其他的一些查詢,如為空查詢等:
@Test publicvoid test16() { Session session = null; try { session = HibernateUtil.openSession(); //可以使用 is null 或者 =null來設定為空的條件 /* List<Student> students = session. createQuery("select s from Student s where s.classroom.id = null") .list();*/ List<Student> students = session. createQuery("select s from Student s where s.classroom.id is null") .list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
HQL的關聯查詢
有時候我們可能存在多表的關聯查詢,這樣的話,我們前面的查詢可以使用前面的導航查詢,但是導航查詢有時候效率不高,所以下面給大家介紹另一種查詢,就是連線查詢(join)。
如果我們要查詢id為1的班級的所有學生:
@Test publicvoid test17() { Session session = null; try { session = HibernateUtil.openSession(); /** * 使用join完成關聯查詢, * 注意:必須是存在關聯關係的表之間才可以查詢 * 因為我們本質上使用了一個物件,另一個表是引 * 用這個物件的屬性對映過去的,同時也注意一個問題 * 就是必須使用select 別名這個,不能省略,因為 * 當兩張表都存在後,如果我們不指定需要查詢的欄位或者表 * 則hibernate不知道我們要查詢的欄位,就會報錯 */ List<Student> students = session. createQuery("select from Student s join s.classroom c where c.id=1") .list(); for (Student student : students) { System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我們發現程式碼這樣也可以完成查詢的,使用的是inner join,這樣就比導航的cross join的效率高了,這是內連線查詢。
同樣我們的資料庫連線除了內連線外,還有外連線,包括左外連線和右外連線,左外連線如果右面的沒有關聯左面的,則不顯示右面沒有關聯的部分。右外連線是如果左面的沒有關聯,則不顯示左面沒有關聯的部分。如下面我們查詢所有班級的人數:
@Test publicvoid test18() { Session session = null; try { session = HibernateUtil.openSession(); List<Object[]> students = session. createQuery("select c.name,count(s.classroom.id) from Student s right join s.classroom c group by c.id ") .list(); for (Object[] student : students) { System.out.println(student[0]+":"+student[1]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
這樣我們就完成了右外連線的查詢,左外連線於此相同。
有時候我們會使用多個表中的欄位完成查詢,如我們在報表時,我們一般要求有學生的專業名稱,班級名稱,學習一些其他屬性(如名字,性別等),那麼下面我們來完成這個查詢吧。
@Test publicvoid test19() { Session session = null; try { session = HibernateUtil.openSession(); List<Object[]> students = session. createQuery("select s.name,s.sex,c.name,spe.name from Student s left join s.classroom c left join c.special spe ") .list(); for (Object[] student : students) { System.out.println(student[0]+"--"+student[1]+"--"+student[2]+"--"+student[3]); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
程式碼執行效果如下:
這裡我們也可以使用我們前面講過的Dto(vo)來實現效果,因為在真實的專案開發中,我們在dao層完成資料的封裝,在頁面顯示,如果我們使用了物件陣列,這樣我們不便於知道到底有哪些欄位需要顯示,而使用了物件的話,就很清楚了
我們先寫個Dto物件:
package com.lzcc.hibernate.dto; publicclass StudentDto{ privateintid; private String name; private String sex; private String className; private String specialName; publicint getId() { returnid; } publicvoid setId(int id) { this.id = id; } public String getName() { returnname; } publicvoid setName(String name) { this.name = name; } public String getSex() { returnsex; } publicvoid setSex(String sex) { this.sex = sex; } public String getClassName() { returnclassName; } publicvoid setClassName(String className) { this.className = className; } public String getSpecialName() { returnspecialName; } publicvoid setSpecialName(String specialName) { this.specialName = specialName; } public StudentDto() { } public StudentDto(int id, String name, String sex, String className, String specialName) { super(); this.id = id; this.name = name; this.sex = sex; this.className = className; this.specialName = specialName; } } |
測試程式碼如下,注意寫法,必須是去路徑(包路徑+類名),這個建構函式必須有,別名也必須和屬性名稱保持一致。
@Test publicvoid test20() { Session session = null; try { session = HibernateUtil.openSession(); /** *
使用 |