Hibernate學習筆記(六)—— 查詢優化
一、Hibernate的抓取策略
1.1 什麼是抓取策略
抓取策略是當應用程式需要在(Hibernate實體物件圖的)關聯關係間進行導航的時候,Hibernate如何獲取關聯物件的策略。
HIbernate的抓取策略是Hibernate提升效能的一種手段,可以在獲取關聯物件的時候,對傳送的語句進行優化,但是往往抓取策略需要和延遲載入一起使用來提升效能。我們首先來學習一下延遲載入的內容。
1.2 延遲載入的分類
延遲載入(lazy load)是(也稱為懶載入)Hibernate關聯關係物件預設的載入方式,延遲載入機制是為了避免一些無謂的效能開銷而提出來的,所謂延遲載入就是當在真正需要資料的時候,才真正執行資料載入操作
【類級別的延遲載入】
使用load方法查詢某個物件的時候,這個類是否採用延遲載入的策略,就是類級別的延遲。類級別的延遲一般在<class>上配置lazy屬性,lazy的預設值是true。預設是延遲載入的,所以使用load方法去查詢的時候,不會馬上傳送SQL語句,當真正使用該物件的時候,才會傳送SQL語句。
@Test public void fun1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx= session.beginTransaction(); Customer customer = session.load(Customer.class, 1l); // 不會馬上傳送SQL語句 System.out.println(customer); //使用到該物件後,才會傳送SQL語句 tx.commit(); session.close(); }
其實如果不想使用延遲載入也有很多種方法,當然最簡單的就是在這個類的對映檔案上將<class>的lazy屬性設定為false(當lazy為false時,load方法和get方法沒有任何區別,都是呼叫時就載入資料);當然也可以將這個持久化類改為final修飾,這樣就無法生成代理類,就會使延遲載入失效。
這就是類級別的延遲載入,為了提高效率,類延遲載入我們一般不進行修改,採用預設值lazy="true"即可。
注意:使用懶載入時要確保,呼叫屬性載入資料時,session還是開啟的.不然會丟擲異常:
【關聯級別的延遲載入】
關聯級別的延遲載入指的是,在查詢到某個物件的時候,查詢其關聯的物件時,是否採用延遲載入。
Customer customer = session.load(Customer.class, 1l); Set<LinkMan> linkMans = customer.getLinkMans();
通過客戶查詢其關聯的聯絡人物件,在查詢聯絡人的時候是否採用延遲載入稱為是關聯級別的延遲。關聯級別的延遲通常是在<set>和<many-to-one>上進行配置。
- <set>標籤:
- <many-to-one>標籤:
1.3 抓取策略
抓取策略指的是查詢到某個物件的時候,如何抓取其關聯的物件。通常可以在<set>和<many-to-one>上配置fetch屬性(決定使用什麼型別的sql語句載入集合資料)
- <set>標籤
- <many-to-one>標籤
這樣來看在<set>上配置fetch有三個值,lazy有三個值,這樣就會產生很多種效果。其實不用擔心,因為fetch如果設定為join,lazy就會失效了。
下面我們通過在<set>標籤上設定fetch和lazy來檢視每種取值的效果。
1.4 測試程式碼
需求1:查詢1號客戶,同時查詢1號客戶的聯絡人數量
- 預設情況下
@Test public void fun1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號客戶 Customer customer = session.get(Customer.class, 1l);// 傳送一條select語句查詢1號客戶 // 獲得1號客戶聯絡人的數量 System.out.println(customer.getLinkMans().size()); // 傳送一條select語句查詢1號客戶的所有聯絡人 tx.commit(); session.close(); }
- 在<set>上配置fetch="select" lazy="true"
@Test public void fun2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號客戶 Customer customer = session.get(Customer.class, 1l);// 傳送一條select語句查詢1號客戶 // 獲得1號客戶聯絡人的數量 System.out.println(customer.getLinkMans().size()); // 傳送一條select語句查詢1號客戶的所有聯絡人 tx.commit(); session.close(); }
- 在<set>上配置fetch="select" lazy="false"
@Test public void fun3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號客戶 Customer customer = session.get(Customer.class, 1l);// 傳送兩條select語句,一條查詢1號客戶,一條查詢1號客戶的所有聯絡人 // 獲得1號客戶聯絡人的數量 System.out.println(customer.getLinkMans().size()); // 不傳送sql語句 tx.commit(); session.close(); }
- 在<set>上配置fetch="select" lazy="extra"
@Test public void fun4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號客戶 Customer customer = session.get(Customer.class, 1l);// 傳送一條select語句查詢1號客戶 // 獲得1號客戶聯絡人的數量 System.out.println(customer.getLinkMans().size()); // 傳送一條select count(*)語句統計個數 tx.commit(); session.close(); }
- 在<set>上配置fetch="join" lazy="..."(當fetch為join時,lazy會失效)
@Test public void fun5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號客戶 Customer customer = session.get(Customer.class, 1l);//傳送一條迫切左外連線直接將關聯的物件一起查詢了 // 獲得1號客戶聯絡人的數量 System.out.println(customer.getLinkMans().size()); //不傳送了 tx.commit(); session.close(); }
- 在<set>上配置fetch="subselect" lazy="true"
@Test public void fun6() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 子查詢不能查詢某個物件,查詢多個物件來測試 List<Customer> list = session.createQuery("from Customer").list(); //傳送查詢客戶的語句 for (Customer customer : list) { System.out.println(customer.getLinkMans().size()); //傳送一條子查詢,查詢客戶關聯的聯絡人 } tx.commit(); session.close(); }
-
在<set>上配置fetch="subselect" lazy="false"
@Test public void fun7() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 子查詢不能查詢某個物件,查詢多個物件來測試 List<Customer> list = session.createQuery("from Customer").list(); //傳送兩條sql:一條查詢所有客戶;一條子查詢,查詢客戶關聯的聯絡人 for (Customer customer : list) { System.out.println(customer.getLinkMans().size()); } tx.commit(); session.close(); }
在<set>標籤上的抓取策略已經明白了,那麼在<many-to-one>上的fetch和lazy又是什麼樣的效果呢?我們也可以通過程式來測試一下。
需求2:查詢1號聯絡人,同時查詢1號聯絡人所關聯客戶的資訊
- 預設情況
@Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號聯絡人 LinkMan linkMan = session.get(LinkMan.class, 1l);//傳送一條select語句查詢1號聯絡人 System.out.println(linkMan.getCustomer().getCust_name());//傳送一條select語句查詢1號聯絡人對應的客戶資訊 tx.commit(); session.close(); }
- 在<many-to-one>上配置fetch="select" lazy="proxy"
@Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號聯絡人 LinkMan linkMan = session.get(LinkMan.class, 1l);//傳送一條select語句查詢1號聯絡人 System.out.println(linkMan.getCustomer().getCust_name());//傳送一條select語句查詢1號聯絡人對應的客戶資訊 tx.commit(); session.close(); }
- 在<many-to-one>上配置fetch="select" lazy="false"
@Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號聯絡人 LinkMan linkMan = session.get(LinkMan.class, 1l);//傳送兩條select:一條傳送查詢1號聯絡人;一條查詢1號聯絡人對應的客戶資訊 System.out.println(linkMan.getCustomer().getCust_name());//不傳送 tx.commit(); session.close(); }
- 在<many-to-one>上配置fetch="join" lazy="失效"
@Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 查詢1號聯絡人 LinkMan linkMan = session.get(LinkMan.class, 1l);//傳送一條迫切左外連線查詢關聯物件 System.out.println(linkMan.getCustomer().getCust_name());//不傳送 tx.commit(); session.close(); }
1.5 <set>標籤的fetch和lazy總結
fetch:控制的是查詢其關聯物件的時候採用的SQL語句的格式。
* select:預設值。傳送一條select語句查詢其關聯物件
* join:傳送一條迫切左外連線查詢關聯物件
* subselect:傳送一條子查詢查詢其關聯物件
lazy:控制的是查詢其關聯物件的時候是否採用延遲載入的策略
* true:預設值。預設查詢物件的時候採用延遲載入
* false:查詢關聯物件的時候,不採用延遲載入
* extra:極其懶惰。查詢關聯物件的時候,採用比延遲載入更懶惰的方式進行查詢
1.6 批量抓取
當我們想查詢所有客戶,然後再查詢所有客戶關聯的聯絡人時,為了提高效率,就可以考慮使用批量抓取。
如果要實現批量的抓取效果,可以通過配置batch-size來完成。
【查詢客戶批量抓取聯絡人】
@Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); List<Customer> list = session.createQuery("from Customer").list(); for (Customer customer : list) { System.out.println("客戶的名稱:" + customer.getCust_name()); for (LinkMan linkMan : customer.getLinkMans()) { System.out.println("客戶對應的聯絡人名稱:"+linkMan.getLkm_name()); } } tx.commit(); session.close(); }
在沒有設定batch-size之前執行以上程式碼,會出現如下效果:
目前資料庫中有兩個客戶,會發現執行程式碼傳送了3條sql語句。我們能不能傳送一條sql語句直接將兩個客戶關聯的聯絡人一起查詢出來呢?我們可以在<set>上配置batch-size來實現優化效果。
配置完batch-size以後,再次執行以上程式碼會發現:
這時會發現SQL語句發生了變化,當資料量越大,效果越明細。
【查詢聯絡人批量抓取客戶】
@Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); List<LinkMan> list = session.createQuery("from LinkMan").list(); for (LinkMan linkMan : list) { System.out.println("聯絡人:"+linkMan.getLkm_name()); System.out.println("聯絡人對應的客戶:"+linkMan.getCustomer().getCust_name()); } tx.commit(); session.close(); }
如果沒有配置batch-size,執行上述程式碼會得到如下效果:
如果配置了batch-size,再次執行上述程式碼得到如下效果:
注意:這時候要在Customer.hbm.xml的<class>標籤上配置batch-size
可以看到語句和之前已經發生變化了。這些優化都是Hibernate提升自身效能的手段。