1. 程式人生 > >Hibernate學習筆記(六)—— 查詢優化

Hibernate學習筆記(六)—— 查詢優化

一、Hibernate的抓取策略

1.1 什麼是抓取策略

  抓取策略是當應用程式需要在(Hibernate實體物件圖的)關聯關係間進行導航的時候,Hibernate如何獲取關聯物件的策略。

  HIbernate的抓取策略是Hibernate提升效能的一種手段,可以在獲取關聯物件的時候,對傳送的語句進行優化,但是往往抓取策略需要和延遲載入一起使用來提升效能。我們首先來學習一下延遲載入的內容。

1.2 延遲載入的分類

  延遲載入(lazy load)是(也稱為懶載入)Hibernate關聯關係物件預設的載入方式,延遲載入機制是為了避免一些無謂的效能開銷而提出來的,所謂延遲載入就是當在真正需要資料的時候,才真正執行資料載入操作

。可以簡單理解為,只有在使用的時候,才會發出sql語句進行查詢。

【類級別的延遲載入】

  使用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提升自身效能的手段。