Hibernate學習筆記(五)—— Hibernate查詢方式
一、物件圖導航查詢
物件圖導航查詢方式是根據已經載入的物件,導航到他的關聯物件。它利用類與類之間的關係來查詢物件。比如要查詢一個聯絡人對應的客戶,就可以由聯絡人物件自動導航找到聯絡人所屬的客戶物件。當然,前提是必須在物件關係對映檔案上配置了多對一的關係。其檢索方式如下所示:
LinkMan linkMan = session.get(LinkMan.class, 1l); Customer customer = linkMan.getCustomer();
二、OID檢索方式
OID檢索方式主要指用Session的get()和load()方法載入某條記錄對應的物件。如下面兩種載入客戶物件的方式,就是OID檢索方式,具體如下:
Customer customer = session.get(Customer.class, 1l); Customer customer1 = session.load(Customer.class, 1l);
三、HQL檢索
HQL(Hibernate Query Language)是面向物件的查詢語言,它和SQL查詢語言有些相似,但它使用的是類、物件和屬性的概念,而沒有表和欄位的概念。在Hibernate提供的各種檢索方式中,HQL是官方推薦的查詢語言,也是使用最廣泛的一種檢索方式,它具有如下功能。
- 在查詢語句中設定各種查詢條件;
- 支援投影查詢,即僅檢索出物件的部分屬性;
- 支援分頁查詢;
- 支援分組查詢,允許使用group by 和 having關鍵字;
- 提供內建聚集函式,如sum()、min()和max();
- 能夠呼叫使用者定義的SQL函式;
- 支援子查詢,即巢狀查詢;
- 支援動態繫結引數;
Hibernate提供的Query介面是專門的HQL查詢介面,它能夠執行各種複雜的HQL查詢語句,完整的HQL語句結構如下:
select...from...where...group by...having...order by ... asc/desc
可以HQL查詢非常類似於表中SQL查詢。通常情況下,當檢索資料表中的所有記錄時,查詢語句中可以省略select關鍵字,示例如下所示:
String hql = "from Customer";
如果執行該查詢語句,則會返回應用程式中的所有Customer物件。需要注意的是:Customer是類名,而不是表名,類名需要區分大小寫,而關鍵字from不區分大小寫。
3.1 基本查詢
/** * HQL:基本查詢 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 基本查詢 // Query query = session.createQuery("from Customer"); // 起別名 // Query query = session.createQuery("from Customer c"); Query query = session.createQuery("select c from Customer c"); List<Customer> list = query.list(); // query.uniqueResult(); //接收唯一的查詢結果 for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.2 排序查詢
/** * HQL:排序查詢 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 排序查詢 Query query = session.createQuery("from Customer order by cust_id desc"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.3 條件查詢
/** * HQL:條件查詢 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 按位置繫結引數——問號佔位符 // Query query = session.createQuery("from Customer where cust_id = ? "); // 設定引數 // query.setLong(0, 1l); // query.setParameter(0, 1l); // 根據查詢物件獲得查詢結果 // Customer customer = (Customer) query.uniqueResult(); // System.out.println(customer); // 按名稱繫結引數 Query query = session.createQuery("from Customer where cust_id = :cust_id"); // 設定引數 query.setParameter("cust_id", 1l); // 根據查詢物件獲得查詢結果 Customer customer = (Customer) query.uniqueResult(); System.out.println(customer); tx.commit(); session.close(); }
3.4 分頁查詢
/** * HQL:分頁查詢 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer"); // 設定分頁資訊 limit ?,? query.setFirstResult(1); query.setMaxResults(1); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.5 統計查詢
/** * HQL:統計查詢 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query1 = session.createQuery("select count(*) from Customer"); Query query2 = session.createQuery("select sum(cust_id) from Customer"); Query query3 = session.createQuery("select avg(cust_id) from Customer"); Query query4 = session.createQuery("select max(cust_id) from Customer"); Query query5 = session.createQuery("select min(cust_id) from Customer"); Number number = (Number) query1.uniqueResult(); System.out.println(number); tx.commit(); session.close(); }
3.6 投影查詢
/** * 投影查詢 */ @Test public void demo6() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 投影查詢一列 /*Query query = session.createQuery("select cust_name from Customer"); List<String> list = query.list(); for (String string : list) { System.out.println(string); }*/ // 投影查詢多列 /*Query query = session.createQuery("select cust_name,cust_id from Customer"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(Arrays.toString(objects)); }*/ // 投影的構造方式查詢——Customer實體需要生成相應的帶參構造 Query query = session.createQuery("select new Customer(cust_id,cust_name) from Customer"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
四、QBC查詢
QBC(Query By Criteria)是Hibernate提供的另一種檢索物件的方式,它主要有Criteria介面、Criterion介面和Expression類組成。Criteria介面是HibernateAPI中的一個查詢介面,它需要由session進行建立。
Criterion是Criteria的查詢條件,在Criteria中提供了add(Criterion criterion)方法新增查詢條件。使用QBC檢索物件的示例程式碼,如下所示:
// 建立criteria物件 Criteria criteria = session.createCriteria(Customer.class); // 設定查詢條件 criteria.add(Restrictions.eq("id", 1l)); // 執行查詢,返回查詢結果 List<Customer> list = criteria.list();
上述程式碼中查詢的是id為1的Customer物件。
QBC檢索是使用Restrictions物件編寫查詢條件的,在Restrictions類中提供了大量的靜態方法來建立查詢條件。其常用的方法如表所示:
方法名 | 說明 |
Restrictions.eq | 等於 |
Restrictions.allEq | 使用Map,使用key/value進行多個等於的比較 |
Restrictions.gt | 大於> |
Restrictions.ge | 大於等於>= |
Restrictions.lt | 小於 |
Restrictions.le | 小於等於<= |
Restrictions.between | 對應SQL的between子句 |
Restrictions.like | 對應SQL的like子句 |
Restrictions.in | 對應SQL的IN子句 |
Restrictions.and | and關係 |
Restrictions.or | or關係 |
Restrictions.sqlRestriction | SQL限定查詢 |
4.1 基本查詢
/** * 簡單的查詢 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 建立criteria物件 Criteria criteria = session.createCriteria(Customer.class); // 執行查詢,返回查詢結果--查詢所有的Customer物件 List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.2 條件查詢
/** * 條件查詢 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); // 新增查詢引數--查詢cust_id為1的Customer物件 // criteria.add(Restrictions.eq("cust_id", 1l)); criteria.add(Restrictions.like("cust_name", "%王%")); // 執行查詢獲得結果 Customer customer = (Customer) criteria.uniqueResult(); System.out.println(customer); tx.commit(); session.close(); }
4.3 分頁查詢
/** * 分頁查詢 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 建立criteria物件 Criteria criteria = session.createCriteria(Customer.class); // 設定分頁資訊 limit ?,? criteria.setFirstResult(1); criteria.setMaxResults(2); // 執行查詢 List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.4 排序查詢
/** * 排序查詢 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); criteria.addOrder(Order.asc("cust_id")); List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.5 統計查詢
/** * 統計查詢 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); // 設定查詢的聚合函式 => 總行數 criteria.setProjection(Projections.rowCount()); // 執行查詢 Long count = (Long) criteria.uniqueResult(); System.out.println(count); tx.commit(); session.close(); }
4.6 離線條件查詢
DetachedCriteria翻譯為離線條件查詢,因為它是可以脫離Session來使用的一種條件查詢物件,我們都知道Criteria物件必須由Session物件來建立。那麼也就是說必須先有Session才可以生成Criteria物件。而DetachedCriteria物件可以在其他層對條件進行封裝。
這個物件也是比較有用的,尤其在SSH整合以後這個物件經常會使用。它的主要優點是做一些特別複雜的條件查詢的時候,往往會在WEB層向業務層傳遞很多的引數,業務層又會將這些引數傳遞給DAO層。最後在DAO中拼接SQL完成查詢。有了離線條件查詢物件後,那麼這些工作都可以不用關心了,我們可以在WEB層將資料封裝好,傳遞到業務層,再由業務層傳遞給DAO完成查詢。
我們可以先簡單的測試一下離線條件查詢物件,然後具體的使用我們會在後期整合中使用,到那時會更加體會出它的優勢。
/** * 離線條件查詢:DetachedCriteria(SSH整合經常使用) * 可以脫離session設定引數 */ @Test public void demo6() throws Exception { // Web/Service層 // 獲得一個離線條件查詢的物件 DetachedCriteria dc = DetachedCriteria.forClass(Customer.class); // 拼接調劑(全部與普通Criteria一致) dc.add(Restrictions.idEq(1l)); //------------------------------------------------------------ Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); List<Customer> list = dc.getExecutableCriteria(session).list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
五、原生SQL查詢
採用HQL或QBC檢索方式時,Hibernate生成標準的SQL查詢語句,適用於所有的資料庫平臺,因此這兩種檢索方式都是跨平臺的。但有的應用程式可能需要根據底層資料庫的SQL方言,來生成一些特殊的查詢語句。在這種情況下,可以利用Hibernate提供的SQL檢索方式。使用SQL檢索方式檢索物件的示例程式碼,如下所示:
SQLQuery sqlQuery = session.createSQLQuery("select id,name,age,city from customer");
5.1 基本查詢
/** * 基本查詢 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 建立sql查詢物件 SQLQuery query = session.createSQLQuery("select * from cst_customer"); // 指定將結果集封裝到哪個物件中 query.addEntity(Customer.class); // 呼叫方法查詢結果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
5.2 條件查詢
/** * 條件查詢 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 建立sql查詢物件 SQLQuery query = session.createSQLQuery("select * from cst_customer where cust_id = ?"); query.setParameter(0, 1l); // 指定將結果集封裝到哪個物件中 query.addEntity(Customer.class); // 呼叫方法查詢結果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
5.3 分頁查詢
/** * 分頁查詢 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 建立sql查詢物件 SQLQuery query = session.createSQLQuery("select * from cst_customer limit ?,?"); query.setParameter(0, 0); query.setParameter(1, 1); // 指定將結果集 query.addEntity(Customer.class); // 呼叫方法查詢結果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
六、Hibernate的多表查詢
在做多表查詢之前,我們需要先來回顧下使用SQL是如何完成多表的查詢的。在學習SQL語句的時候進行多表聯合查詢一般都會採用連線查詢,那麼我們就來回顧一下SQL中的多表的聯合查詢。
6.1 SQL多表聯合查詢
6.1.1 交叉連線
交叉連線返回的結果是被連線的兩個表中所有資料行的笛卡爾積,也就是返回第一個表中符合查詢條件的資料行數乘以第二個表中符合查詢條件的資料行數,例如department表中有四個部門,employee表中有四個員工,那麼交叉連線的結果就是4*4=16條資料。需要注意的是,在實際開發中這種業務需求很少見的,一般不會使用交叉連線,而是使用具體的條件對資料進行有目的的查詢。
6.1.2 內連線
內連線(INNER JOIN)又稱簡單連線或自然連線,是一種常見的連線查詢。內連線使用比較運算子對兩個表中的資料進行比較,並列出與連線條件匹配的資料行,組合成新的記錄,也就是說在內連線查詢中,只有滿足條件的記錄才能出現在查詢結果中。內連線查詢的語法格式如下所示:
select 查詢欄位 from 表1 [INNER] JOIN 表2 on 表1.關係欄位=表2.關係欄位
在上述語法格式中,INNER JOIN用於連線兩個表,ON來指定連線條件,其中INNER可以省略。內連線其實還可以細分為如下兩類:
- 隱式內連線:顧名思義,隱式的就是我們看不到inner join關鍵字,而是用where關鍵字替代。
select * from 表1, 表2 where 表1.關係欄位 = 表2.關係欄位;
- 顯式內連線:顯示的就是在語句中明顯的呼叫了inner join的關鍵字。
select * from 表1 inner join 表2 on 表1.關係欄位 = 表2.關係欄位; select * from 表1 join 表2 on 表1.關係欄位 = 表2.關係欄位;
6.1.3 外連線
前面講解的內連線查詢中,返回的結果只包含符合查詢條件和連線條件的資料,然而有時還需要包含沒有關聯的資料,即返回查詢結果中不僅包含符合條件的資料,而且還包括左表(左連線或左外連線)、右表(右連線或右外連線)或兩個表(全外連線)中的所有資料,此時就需要使用外連線查詢,外連線分為左連線和右連線。外連線的語法格式如下:
select 所查欄位 from 表1 left | rigth [outer] JOIN 表2 on 表1.關係欄位 = 表2.關係欄位 where 條件
外連線的語法格式和內連線類似,只不過使用的是LEFT JOIN、RIGHT JOIN關鍵字,其中關鍵字左邊的表被稱為左表,關鍵字右邊的表被稱為右表。
在使用左連線和右連線查詢時,查詢結果是不一致的,具體如下:
- LEFT JOIN(左連線): 返回包括左表中的所有記錄和右表中符合連線條件的記錄。
select * from 表1 left outer join 表2 on 表1.關係欄位=表2.關係欄位; select * from A left join 表2 on 表1.關係欄位 = 表2.關係欄位;
- RIGHT JOIN(右連線):返回包括右表中的所有記錄和左表中符合連線條件的記錄。
select * from 表1 right outer join 表2 on 表1.關係欄位 = 表2.關係欄位; select * from A right join 表2 on 表1.關係欄位 = 表2.關係欄位;
SQL語句的連線查詢我們會寫了,那麼Hibernate中的HQL如何進行連線查詢呢?
6.2 HQL連線查詢
Hibernate進行多表查詢與SQL其實是很相似的,但是HQL會在原來SQL分類的基礎上又多出來一些操作。
HQL的多表連線查詢的分類如下:
-
- 交叉連線
- 內連線
- 顯式內連線
- 隱式內連線
- 迫切內連線
- 外連線
- 左外連線
- 迫切左外連線
- 右外連線
其實,這些連線查詢語法大致都是一致的,就是HQL查詢的是物件而SQL查詢的是表。
6.2.1 內連線
- 顯示內連線
SQL連線查詢:
select * from cst_customer c inner join cst_linkman l on c.cust_id = l.lkm_cust_id;
HQL連線查詢:
from Customer c inner join c.linkmans
在HQL中,我們不用寫關聯欄位了,因為客戶中的聯絡人集合其實對應的就是外來鍵,所以我們在inner join的後面直接可以寫c.linkMans。測試程式碼如下:
/** * HQL:內連線 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c inner join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }
在控制檯的輸出的語句如下:
我們發現如果這樣寫HQL語句的化,生成的底層SQL語句就是使用inner join進行連線的,而連線的條件就是customer0_.cust_id=linkmans1_.lkm_cust_id。就是兩個表關聯的欄位。所以HQL的連線不用寫具體的on條件,直接寫關聯的屬性即可。
- 迫切內連線
迫切內連線其實就是在內連線的inner join後新增一個fetch關鍵字。
/** * HQL的多表連線查詢:迫切內連線 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c inner join fetch c.linkMans"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
控制檯輸出的語句如下:
我們會發現無論是內連線或是迫切內連線,傳送的底層SQL都是一樣的,而且在生成的SQL語句中也沒有fetch關鍵字,當然fetch本身就不是SQL語句的關鍵字。所以一定要注意,fetch只能在HQL中使用,生成了SQL語句後,fetch就消失了。那麼fetch到底有什麼作用呢?
其實我們知道HQL內連線查詢的和SQL的內連線查詢到的結果集是一樣的,都是兩個表交集部分的資料。
然後在封裝資料的時候,普通內連線會將客戶的資料封裝到Customer物件中,會將屬於聯絡人的資料封裝到LinkMan物件中,所以每條記錄都會是裝有兩個物件的集合,所以封裝以後的資料是List<Object[]>,在Object[]中有兩個物件,一個是Customer,另一個是LinkMan。
那麼加了fetch以後,雖然我們查詢到的資料是一樣的,但是Hibernate發現HQL中有fetch,就會將資料封裝到一個物件中,把屬於客戶的資料封裝到Customer物件中,將屬於聯絡人的部分封裝到Customer中的聯絡人集合中,這樣最後封裝完成以後是一個List<Customer>中。
【內連線與迫切內連線的對比總結】
其實內連線和迫切內連線的主要區別就在於封裝資料,因為他們查詢的結果集都是一樣的,生成底層的SQL語句也是一樣的。
- 內連線:傳送就是內連線的語句,封裝的時候將屬於各自物件的資料封裝到各自的物件中,最後得到List<Object[]>.
- 迫切內連線:傳送的是內連線的語句,需要在編寫HQL的時候在join後新增一個fetch關鍵字,Hibernate會發送HQL中的fecth關鍵字,從而將每條資料封裝到物件中,最後得到一個List<Customer>。
但是迫切內連線封裝以後會出現重複的資料,因為我們查詢到目前有三條記錄,就會被封裝到三個物件中,其實我們真正的客戶物件只有兩個,所以往往自己在手動編寫迫切內連線的時候使用distinct去掉重複值。
/** * HQL的多表連線查詢:內連線 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("select distinct c from Customer c inner join fetch c.linkMans"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
6.2.2 外連線
- 左外連線
1、將連線的兩端物件分別返回,放到陣列中。
2、以左邊表中資料為參照,顯示左邊表中所有資料,右邊表只顯示與左邊表對應的資料,少了補null,多了刪除。
/** * HQL:左外連線 */ @Test public void demo6() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c left join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }
- 右外連線
1、將連線的兩端物件分別返回,放到陣列中。
2、以右邊為參照,左邊少了補null,多了刪除
/** * HQL:右外連線 */ @Test public void demo7() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c right join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }