hibernate(八) Hibernate檢索策略(類級別,關聯級別,批量檢索)詳解

一杯涼茶發表於2016-12-12

      序言

         很多看起來很難的東西其實並不難,關鍵是看自己是否花費了時間和精力去看,如果一個東西你能看得懂,同樣的,別人也能看得懂,體現不出和別人的差距,所以當你覺得自己看了很多書或者學了很多東西的時候,你要想想,你花費的也就那麼一點時間,別人花你這麼多時間也能夠學到你所學到的東西,所以還是要繼續努力。既然不是天才,唯有靠勤奮來彌補。

                                            --WH

 

一、概述

    檢索策略分三大塊,類級別檢索策略和關聯級別檢測策略。

        類級別檢索策略:get、load、

        關聯級別檢索策略:order.getCustomer().getName()

        上面這兩種應該是看得懂的。很容易去理解,現在就具體來說說這兩種其中的細節。

    批量檢索解決n+1問題。

二、類級別檢索策略

    2.1、立即檢索  get

        直接傳送sql語句,到資料庫中去查詢資料。

        例如                

 1 Staff staff = (Staff)session.get(Staff.class, 3);//執行完這句,就會傳送sql語句,到資料庫表中查詢相應的資料加入一級快取中
 2 
 3 //結果
 4 Hibernate: 
 5     select
 6         staff0_.id as id1_0_,
 7         staff0_.name as name1_0_,
 8         staff0_.deptId as deptId1_0_ 
 9     from
10         staff staff0_ 
11     where
12         staff0_.id=?
View Code

        

    2.2、延遲檢索  load

        不會直接傳送sql語句,而是等到用的時候在傳送sql語句,如果一直沒用,就永遠不會傳送sql語句。

 1         Staff staff = (Staff)session.load(Staff.class, 3);//執行完這句,不會傳送sql語句
 2         System.out.println("load後還沒傳送sql語句,等用到的時候才會傳送。");
 3         System.out.println(staff.getName());//現在需要用staff。則會傳送sql語句。
 4 
 5 //結果
 6 
 7 load後還沒傳送sql語句,等用到的時候才會傳送。
 8 Hibernate: 
 9     select
10         staff0_.id as id1_0_,
11         staff0_.name as name1_0_,
12         staff0_.deptId as deptId1_0_ 
13     from
14         staff staff0_ 
15     where
16         staff0_.id=?
17 qqq2
View Code

 

    2.3、深入講解get和load

        上面兩個只是簡單講解一下立即載入和延遲載入兩個概念。現在來講點深入的東西。

      1、load檢索返回的代理物件,而不是一個pojo物件,get返回的是pojo物件,這個的前提是一級快取中沒有我們要查詢的物件。

            

      2、get和load都是先從一級快取中拿資料,而不是每次都從資料庫中拿,也就是說如果一級快取有我們需要的資料,就不會在傳送sql語句了。並且返回就是一級快取物件中物件的狀態,也就是說如果在一級快取中該物件的狀態是pojo物件,那麼就算是用load載入的,返回的也就是pojo物件,如果該物件是代理物件,那麼就算get載入的,返回的也就是代理物件,不過會將代理物件的資料初始化。也就是會向資料庫中傳送sql語句查詢資料。           解釋:代理物件資料初始化:代理物件中包含了我們想要的pojo物件的所有資訊。

        例子:一級快取中的是代理物件,使用get獲得

 1         Staff staff = (Staff)session.load(Staff.class, 3);//staff代理物件加入快取中了
 2         System.out.println("沒有傳送sql語句,get之後就會傳送。");
 3         Staff staff1 = (Staff)session.get(Staff.class, 3);//get獲取了快取中的代理物件,並且初始化
 4 
 5 //結果
 6 沒有傳送sql語句,get之後就會傳送。
 7 Hibernate: 
 8     select
 9         staff0_.id as id1_0_,
10         staff0_.name as name1_0_,
11         staff0_.deptId as deptId1_0_ 
12     from
13         staff staff0_ 
14     where
15         staff0_.id=?
View Code

              

         例子: 一級快取中是pojo物件,通過load獲得

 1         Staff staff = (Staff)session.get(Staff.class, 3);//staff pojo物件加入快取中了
 2         System.out.println("get後傳送sql語句,並且該pojo物件在一級快取中了。");
 3         Staff staff1 = (Staff)session.load(Staff.class, 3);//load獲取了快取中的pojo物件
 4 
 5 //結果
 6 Hibernate: 
 7     select
 8         staff0_.id as id1_0_,
 9         staff0_.name as name1_0_,
10         staff0_.deptId as deptId1_0_ 
11     from
12         staff staff0_ 
13     where
14         staff0_.id=?
15 get後傳送sql語句,並且該pojo物件在一級快取中了。
View Code

              

 

      3、只有在使用時,代理物件才會初始化,其實還有一種方式可以不使用代理物件而初始化資料,

               

            因為沒有初始化代理物件,在關閉session後,在使用staff1,就會報錯,報錯內容為不能夠初始化程式碼物件,沒有session,

                

            使用Hibernate.initialize(proxy);來對代理物件進行初始化,這個的效果和使用代理物件是一樣的,但是會使程式碼看起來更好,如果你在這裡system.out.println(代理物件),也有也可以,但是看起來總覺得乖乖的,所以hibernate就有了這個方法來對代理物件進行初始化。

              

      4、可以通過lazy屬性來設定讓load不延遲載入,而跟get一樣立即載入,因為是類級別檢索,所以在hbm對映檔案中的class位置進行屬性設定。

               

            在staff.hbm.xml中設定了lazy=false。意思是讓其延遲載入失效,所以在對staff進行查詢時,使用load也是立即檢索

               

 

      5、我們常說的,get如果查詢資料庫中沒有的記錄的話,返回是null,而load將會報錯。這句話是正確的,但是概念很模糊,來看下面的路子看會不會報錯。

          例子一:查詢資料庫中沒有的資料,id=100,load的時候會不會報錯?  報錯           

              

          例子二:查詢資料庫中沒有的資料,id=100,並且將其取出id  不抱錯

              

          例子三:查詢資料庫中沒有的資料,id=100,並且取出name  報錯

              

            總結load:load載入返回的是一個代理物件,並且我們說的用load查詢一個資料庫中沒有的資料,並不是load這條語句報異常,而是在使用時,代理物件在資料庫表中找不到資料而報的異常,所以單純只寫load語句,是不會報錯的,並且代理物件的id是我們手動輸入進去的,不用往資料庫中查也知道,所以在代理物件.getId()時也不會傳送sql語句,而是拿到我們一開始的id值。

 

       

 

 

    2.4、load和get的區別(面試題)

            1、get是立即載入、load是延遲載入

            2、get和load都是先從快取中查詢物件,如果有該物件的快取,則不向資料庫中查詢,並且返回的是快取中物件的狀態(是代理物件就返回代理物件,是pojo物件就返回pojo物件)

            3、在快取中沒有物件時,get返回的是pojo物件,load返回的是代理物件

 

三、關聯級別檢索策略

    在<set>標籤上或者在<many-to-one>上設定兩個屬性值來控制其檢索策略,  fetch、lazy

          fetch:代表檢索時的語句的方式,比如左外連線等。

              fetch:join、select 、subselect  

          lazy:是否延遲載入。

              lazy:true、false、extra

    分兩種情況

    3.1、一對多或多對多時

        <set fetch="",lazy="">

        fetch = join時,採取迫切左外連線查詢  

              lazy不管取什麼值都失效,就是取預設值為false。

        fetch=select時,生成多條簡單sql查詢語句

              lazy=false:立即檢索

              lazy=true:延遲檢索

              lazy=extra:加強延遲檢索,非常懶惰,比延遲檢索更加延遲

        fetch=subselect時,生成子查詢

              lazy=false:立即檢索

              lazy=true;延遲檢索

              lazy=extra:加強延遲檢索,非常懶惰,比延遲檢索更加延遲

 

       實驗一:fetch=join 傳送左外迫切連線

          一、hql的query查詢

                  

        使用hql的query查詢,會讓fetch=join失效,lazy重新啟用。如果生成結果是延遲檢索,那麼就說明我們說的這個是正確的。

 1         //使用hql查詢,fetch=join是無效的,並且lazy重新啟用,本來lazy失效的話,
 2         //則使用預設的,就是立即載入,現在因為重新啟用了,所以會是延遲載入
 3         Query query = session.createQuery("from Dept where id = 2");
 4         Dept dept = (Dept) query.uniqueResult();
 5         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 6         dept.getStaffSet().size();
 7 
 8 //結果
 9 
10 Hibernate: 
11     select
12         dept0_.id as id0_,
13         dept0_.name as name0_ 
14     from
15         dept dept0_ 
16     where
17         dept0_.id=2
18 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
19 Hibernate: 
20     select
21         staffset0_.deptId as deptId0_1_,
22         staffset0_.id as id1_,
23         staffset0_.id as id1_0_,
24         staffset0_.name as name1_0_,
25         staffset0_.deptId as deptId1_0_ 
26     from
27         staff staffset0_ 
28     where
29         staffset0_.deptId=?
View Code

          二、使用get查詢。fetch=join生效,lazy就會失效,並且會傳送左外迫切連線。這裡要注意,要看set中存放的東西是什麼,而不是看傳送的語句是不是含有fetch來判斷是不是左外迫切連線,因為hibernate中,左外迫切連線傳送的語句跟左外連線傳送的語句是一樣的,從這裡是區分不出來了。如果不信的話,自己可以去嘗試一下,手動寫一個左外迫切連線,然後看傳送的語句是什麼樣的,我試過了,跟我說的一樣

 1         Dept dept = (Dept) session.get(Dept.class, 2);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Set<Staff> set = dept.getStaffSet();
 4         System.out.println(set);
 5 
 6 //結果
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_1_,
10         dept0_.name as name0_1_,
11         staffset1_.deptId as deptId0_3_,
12         staffset1_.id as id3_,
13         staffset1_.id as id1_0_,
14         staffset1_.name as name1_0_,
15         staffset1_.deptId as deptId1_0_ 
16     from
17         dept dept0_ 
18     left outer join
19         staff staffset1_ 
20             on dept0_.id=staffset1_.deptId 
21     where
22         dept0_.id=?
23 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
24 [oneToMany.Staff@367e4144, oneToMany.Staff@20a709f3, oneToMany.Staff@1dc39acc, oneToMany.Staff@1aeef34f, oneToMany.Staff@1d82e71, oneToMany.Staff@55a7e5ae, oneToMany.Staff@3da7d559, oneToMany.Staff@782d5c85, oneToMany.Staff@6a155d66]
View Code

          總結第一種情況fetch=join。

              1、注意我們這裡討論的是關聯級別的檢索方式,所以重點是看關聯的時候傳送的sql語句,重心不在get和load上面了

              2、fetch=join讓lazy失效的前提是使用的不是hql的query查詢。

       實驗二:fetch=select時 傳送簡單sql語句

            lazy=false;也就是立即檢索,傳送簡單sql語句

                     

 1         Dept dept = (Dept) session.get(Dept.class, 2);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Set<Staff> set = dept.getStaffSet();
 4         System.out.println(set);
 5 
 6 //結果
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_0_,
10         dept0_.name as name0_0_ 
11     from
12         dept dept0_ 
13     where
14         dept0_.id=?
15 Hibernate: 
16     select
17         staffset0_.deptId as deptId0_1_,
18         staffset0_.id as id1_,
19         staffset0_.id as id1_0_,
20         staffset0_.name as name1_0_,
21         staffset0_.deptId as deptId1_0_ 
22     from
23         staff staffset0_ 
24     where
25         staffset0_.deptId=?
26 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
27 [oneToMany.Staff@5091be16, oneToMany.Staff@5c1a555, oneToMany.Staff@753f827a, oneToMany.Staff@6c4d7266, oneToMany.Staff@58e83637, oneToMany.Staff@15dbd461, oneToMany.Staff@1056bfad, oneToMany.Staff@2f41ff3c, oneToMany.Staff@1c8f53b9]
View Code    

            lazy=true;延遲檢索,傳送簡單sql語句

                   

  

 1         Dept dept = (Dept) session.get(Dept.class, 2);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Set<Staff> set = dept.getStaffSet();
 4         System.out.println(set);
 5 
 6 //結果
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_0_,
10         dept0_.name as name0_0_ 
11     from
12         dept dept0_ 
13     where
14         dept0_.id=?
15 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
16 Hibernate: 
17     select
18         staffset0_.deptId as deptId0_1_,
19         staffset0_.id as id1_,
20         staffset0_.id as id1_0_,
21         staffset0_.name as name1_0_,
22         staffset0_.deptId as deptId1_0_ 
23     from
24         staff staffset0_ 
25     where
26         staffset0_.deptId=?
27 [oneToMany.Staff@5091be16, oneToMany.Staff@5c1a555, oneToMany.Staff@753f827a, oneToMany.Staff@6c4d7266, oneToMany.Staff@58e83637, oneToMany.Staff@1056bfad, oneToMany.Staff@2f41ff3c, oneToMany.Staff@1c8f53b9, oneToMany.Staff@645c1312]
View Code

            lazy=extra;超級懶惰,能儘量少查就絕對不會多查,比如,size(),就會使用count()函式,而不會全部查表中的欄位,就是這個意思

                    

 1         Dept dept = (Dept) session.get(Dept.class, 2);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Set<Staff> set = dept.getStaffSet();
 4         System.out.println(set.size());
 5 
 6 //結果
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_0_,
10         dept0_.name as name0_0_ 
11     from
12         dept dept0_ 
13     where
14         dept0_.id=?
15 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
16 Hibernate: 
17     select
18         count(id) 
19     from
20         staff 
21     where
22         deptId =?
23 9
View Code

 

        實驗三、fetch=subselect 生成子查詢,注意使用get方式不生成子查詢,使用query().list().get(),並且資料庫表中還得不止一條記錄才會生成子查詢,如果只有一條記錄,hibernate也很聰明,就沒必要用子查詢了

            lazy=false:立即檢索

 1         Dept dept = (Dept) session.createQuery("from Dept").list().get(0);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Set<Staff> set = dept.getStaffSet();
 4         System.out.println(set);
 5 
 6 //結果
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_,
10         dept0_.name as name0_ 
11     from
12         dept dept0_
13 Hibernate: 
14     select
15         staffset0_.deptId as deptId0_1_,
16         staffset0_.id as id1_,
17         staffset0_.id as id1_0_,
18         staffset0_.name as name1_0_,
19         staffset0_.deptId as deptId1_0_ 
20     from
21         staff staffset0_ 
22     where
23         staffset0_.deptId in (
24             select
25                 dept0_.id 
26             from
27                 dept dept0_
28         )
29 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
30 [oneToMany.Staff@62c2bff3, oneToMany.Staff@583c72d7, oneToMany.Staff@6897ae82, oneToMany.Staff@19d5f3ea, oneToMany.Staff@3160c0bd, oneToMany.Staff@4af364d4, oneToMany.Staff@4afbc8ce, oneToMany.Staff@5fc81d2c, oneToMany.Staff@3e420e73]
View Code

            lazy=true:延遲檢索

            lazy=extra;超級懶惰,比延遲檢索還延遲

                這兩個其實也就差不多了,自己可以試試

           

    3.2、多對一或一對一時

        fetch可以取值為:join,select

        lazy:false,proxy,no-proxy

        當fetch=join,lazy會失效,生成的sql是迫切左外連線

              如果我們使用query時,hql是我們自己指定的,那麼fetch=join是無效的,不會生成迫切左外連線,這時lazy重新啟用

        當fetch=select,lazy不失效,生成簡單sql語句,

            lazy=false:立即檢索

            lazy=proxy:這時關聯物件採用什麼樣的檢索策略取決於關聯物件的類級別檢索策略.就是說參考<class>上的lazy的值

 

        其實跟上面一樣,我們是測試一個fetch=select,lazy=proxy的把。

        staff,也就是多方,

            

        dept,也就是一方

                

        按照我們所配置的,關聯級別檢索應該是延遲檢索,結果正如我們所想的。

 1         Staff staff = (Staff) session.createQuery("from Staff").list().get(0);
 2         System.out.println("如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入");
 3         Dept dept = staff.getDept();
 4         System.out.println(dept);
 5 //結果
 6 Hibernate: 
 7     select
 8         staff0_.id as id1_,
 9         staff0_.name as name1_,
10         staff0_.deptId as deptId1_ 
11     from
12         staff staff0_
13 如果sql語句在這句話之上說明是立即載入,如果在之下就是延遲載入
14 Hibernate: 
15     select
16         dept0_.id as id0_0_,
17         dept0_.name as name0_0_ 
18     from
19         dept dept0_ 
20     where
21         dept0_.id=?
22 Dept [id=2, name=1部門]
View Code

 

 

        總結:

          1、為什麼需要分(一對多,多對多)和(多對一,一對一)兩組情況呢?

              注意:這裡說的一對多,那麼就是單向一對多,也就是佔在一的角度去考慮東西,上面說的四種都是從左邊往右邊看。

              因為一對多和多對多,所拿到的關聯物件度是一個集合,查詢的記錄就有很多個,也就多了一個fetch=subselect這個特性,查詢方式的變化也就多一點,

              而多對一,一對一,所拿到的關聯物件就是一個物件,也就是一條記錄,查詢的方式比較單一和簡單

              因為上面的原因就把他們兩個給分開來以處理不同的情況。達到更高的效率。

          2、為什麼需要搞這樣的檢索方式,不很麻煩嗎?

              根據不同的業務需求,來讓開發者自己控制用什麼樣的檢索方式,這樣讓程式的效能更好

 

四、批量檢索

      什麼叫批量檢索,就是多條sql語句才能完成的查詢,現在一條sql語句就能解決多條sql語句才能完成的事情,看下面例子就明白了,

      例子:n+1問題,什麼叫n+1問題?

         就拿我們上面這個例子來說,Dept和Staff,現在有5個部門,每個部門中的人數可能一樣,也可能不一樣,要求,查詢每個部門中的員工。那麼我們寫的話就需要傳送6條sql語句,哪6條呢?第一條查詢部門表中所有的部門,剩下5條,拿到每一個部門的ID,去員工表中查詢每個部門中的員工,要查5次,因為有5個部門。本來只有5個部門,現在需要傳送6條sql語句,這就是n+1問題,看下面程式碼

         總之就傳送了6條sql語句,我已經數過了。

 1         List<Dept> list = session.createQuery("from Dept").list();
 2         
 3         for(Dept dept : list){
 4             System.out.println(dept.getStaffSet());
 5         }
 6 
 7 //結果
 8 Hibernate: 
 9     select
10         dept0_.id as id0_,
11         dept0_.name as name0_ 
12     from
13         dept dept0_
14 Hibernate: 
15     select
16         staffset0_.deptId as deptId0_1_,
17         staffset0_.id as id1_,
18         staffset0_.id as id1_0_,
19         staffset0_.name as name1_0_,
20         staffset0_.deptId as deptId1_0_ 
21     from
22         staff staffset0_ 
23     where
24         staffset0_.deptId=?
25 [oneToMany.Staff@ffdde88, oneToMany.Staff@1a33cda6, oneToMany.Staff@3160c0bd, oneToMany.Staff@48860e29, oneToMany.Staff@1538c1e3, oneToMany.Staff@339289a7, oneToMany.Staff@3f025aba, oneToMany.Staff@598b4d64, oneToMany.Staff@641cbaeb, oneToMany.Staff@590bcaf1]
26 Hibernate: 
27     select
28         staffset0_.deptId as deptId0_1_,
29         staffset0_.id as id1_,
30         staffset0_.id as id1_0_,
31         staffset0_.name as name1_0_,
32         staffset0_.deptId as deptId1_0_ 
33     from
34         staff staffset0_ 
35     where
36         staffset0_.deptId=?
37 [oneToMany.Staff@650e7ac9, oneToMany.Staff@456ffab9, oneToMany.Staff@3ab5ab4c, oneToMany.Staff@5456df43, oneToMany.Staff@718bc0c4, oneToMany.Staff@7fde1684, oneToMany.Staff@6d0128b0, oneToMany.Staff@157f068f, oneToMany.Staff@55cfffa2, oneToMany.Staff@46e1d5d9, oneToMany.Staff@4d9875b1, oneToMany.Staff@104628c, oneToMany.Staff@4687a14f, oneToMany.Staff@135bd2f7, oneToMany.Staff@149ebdea, oneToMany.Staff@726f6db5, oneToMany.Staff@4a9810b1, oneToMany.Staff@72c5c2e7, oneToMany.Staff@e1cbe19, oneToMany.Staff@671672b8]
38 Hibernate: 
39     select
40         staffset0_.deptId as deptId0_1_,
41         staffset0_.id as id1_,
42         staffset0_.id as id1_0_,
43         staffset0_.name as name1_0_,
44         staffset0_.deptId as deptId1_0_ 
45     from
46         staff staffset0_ 
47     where
48         staffset0_.deptId=?
49 [oneToMany.Staff@5ce4c86b, oneToMany.Staff@23081b81, oneToMany.Staff@132ff4b6, oneToMany.Staff@316ae291, oneToMany.Staff@480955df, oneToMany.Staff@30221872, oneToMany.Staff@6945c41e, oneToMany.Staff@1f43a98b, oneToMany.Staff@634ec390, oneToMany.Staff@e72fd0e]
50 Hibernate: 
51     select
52         staffset0_.deptId as deptId0_1_,
53         staffset0_.id as id1_,
54         staffset0_.id as id1_0_,
55         staffset0_.name as name1_0_,
56         staffset0_.deptId as deptId1_0_ 
57     from
58         staff staffset0_ 
59     where
60         staffset0_.deptId=?
61 [oneToMany.Staff@269c2a55, oneToMany.Staff@4d45222c, oneToMany.Staff@1a66421c, oneToMany.Staff@2f7e49ce, oneToMany.Staff@42c51adb, oneToMany.Staff@5103c049, oneToMany.Staff@c1f8bbe, oneToMany.Staff@75c69e55, oneToMany.Staff@41c7d5a8, oneToMany.Staff@6b0f6d29]
62 Hibernate: 
63     select
64         staffset0_.deptId as deptId0_1_,
65         staffset0_.id as id1_,
66         staffset0_.id as id1_0_,
67         staffset0_.name as name1_0_,
68         staffset0_.deptId as deptId1_0_ 
69     from
70         staff staffset0_ 
71     where
72         staffset0_.deptId=?
73 [oneToMany.Staff@1cf8338b, oneToMany.Staff@7beca583, oneToMany.Staff@5fb369e2, oneToMany.Staff@532cb84e, oneToMany.Staff@6afff988, oneToMany.Staff@1d8df66a, oneToMany.Staff@52ae002e, oneToMany.Staff@6b8dce4, oneToMany.Staff@5f45cd73, oneToMany.Staff@4b578699]
View Code

      解決:使用一個屬性,batch-size。

        1、從部門查員工。也就是從單向一對多,從一方查多方,查詢每個部門中的員工有哪些這樣的問題?,那麼就在對映檔案中的set中設定batch-size。有多少個部門,就至少設定多少,意思就是一次性查詢多少個。從上面的例子中可以看出,傳送了5條對staff的查詢語句,所以這裡batch-size至少為5,大於5可以,浪費了,小於5的話,又會多發sql語句。所以如果能夠確定查詢多少個,那麼就寫確定值,如果不能確定,那麼就寫稍微大一點;

            

        可以看結果,只傳送兩條sql語句,第一條是查詢部門的,第二條是一看,使用關鍵字 IN 來將所有的部門ID含括在內,我們應該就知道了,原來原理是這樣,這樣就只需要傳送一條sql語句,來達到傳送5條sql語句才能完成的功能。 這就是批量檢索,其實原理很簡單

 1         List<Dept> list = session.createQuery("from Dept").list();
 2         
 3         for(Dept dept : list){
 4             System.out.println(dept.getStaffSet());
 5         }
 6 //結果,只傳送兩條sql語句
 7 Hibernate: 
 8     select
 9         dept0_.id as id0_,
10         dept0_.name as name0_ 
11     from
12         dept dept0_
13 Hibernate: 
14     select
15         staffset0_.deptId as deptId0_1_,
16         staffset0_.id as id1_,
17         staffset0_.id as id1_0_,
18         staffset0_.name as name1_0_,
19         staffset0_.deptId as deptId1_0_ 
20     from
21         staff staffset0_ 
22     where
23         staffset0_.deptId in (
24             ?, ?, ?, ?, ?
25         )
26 [oneToMany.Staff@4d9875b1, oneToMany.Staff@3ab5ab4c, oneToMany.Staff@456ffab9, oneToMany.Staff@5456df43, oneToMany.Staff@135bd2f7, oneToMany.Staff@6d0128b0, oneToMany.Staff@7177600e, oneToMany.Staff@199f55f4, oneToMany.Staff@4a9810b1, oneToMany.Staff@671672b8]
27 [oneToMany.Staff@4d6a54b0, oneToMany.Staff@41c65839, oneToMany.Staff@718bc0c4, oneToMany.Staff@7fde1684, oneToMany.Staff@1538c1e3, oneToMany.Staff@4d2d1f6d, oneToMany.Staff@157f068f, oneToMany.Staff@1ce89199, oneToMany.Staff@46e1d5d9, oneToMany.Staff@414128f7, oneToMany.Staff@104628c, oneToMany.Staff@4687a14f, oneToMany.Staff@149ebdea, oneToMany.Staff@68aee2a2, oneToMany.Staff@726f6db5, oneToMany.Staff@72c5c2e7, oneToMany.Staff@e1cbe19, oneToMany.Staff@88f2363, oneToMany.Staff@31a12f5f, oneToMany.Staff@590bcaf1]
28 [oneToMany.Staff@59bd770a, oneToMany.Staff@3402d895, oneToMany.Staff@23081b81, oneToMany.Staff@79897ed0, oneToMany.Staff@26d938e0, oneToMany.Staff@7f250e0c, oneToMany.Staff@31e4c806, oneToMany.Staff@25d25f8d, oneToMany.Staff@44ca27eb, oneToMany.Staff@167f3561]
29 [oneToMany.Staff@5ce4c86b, oneToMany.Staff@132ff4b6, oneToMany.Staff@316ae291, oneToMany.Staff@480955df, oneToMany.Staff@30221872, oneToMany.Staff@6945c41e, oneToMany.Staff@6040b6ef, oneToMany.Staff@1f43a98b, oneToMany.Staff@634ec390, oneToMany.Staff@e72fd0e]
30 [oneToMany.Staff@3f574c4a, oneToMany.Staff@56a88251, oneToMany.Staff@7c51aec2, oneToMany.Staff@4d45222c, oneToMany.Staff@38aa3647, oneToMany.Staff@1a66421c, oneToMany.Staff@42c51adb, oneToMany.Staff@5103c049, oneToMany.Staff@2ed18c61, oneToMany.Staff@75c69e55]
View Code

 

        2、上面是從一查多,batch-size放在set中。從多查一呢,在many-to-one中並沒有batch-size這個屬性。注意了,此時batch-size放在一方的class中。看下圖

              

            先不著急看批量檢索後的結果,先來看看如果沒有該屬性,會怎麼傳送sql語句。傳送多少條。

              查詢每個員工所屬的部門資訊。一想,如果每個員工都到部門表中查一次,包括開始查詢自己員工的資訊,也是n+1問題,比如有10個員工,那麼就會傳送11個sql語句,如果你這樣想,就誤解了這個n+1的意思。這個n+1的意思跟上面從部門查詢員工的n+1的意思是一樣的,因為不管有多少個員工,其中總會有一些員工是在同一個部門,既然在同一個部門,那麼就不用一直髮送重複的sql語句了,而是相同部門的員工,就只查詢一次就足夠了。所以,不管有多少員工,傳送的sql語句還是部門的數量加1.也就是n+1,這才是真正的n+1問題。來看不用批量檢索時,員工查詢部門是不是傳送6條sql語句

            

  1         List<Staff> list = session.createQuery("from Staff").list();
  2         
  3         for(Staff staff : list){
  4             System.out.println(staff.getName()+","+staff.getDept());
  5         }
  6 //結果
  7 Hibernate: 
  8     select
  9         staff0_.id as id1_,
 10         staff0_.name as name1_,
 11         staff0_.deptId as deptId1_ 
 12     from
 13         staff staff0_
 14 Hibernate: 
 15     select
 16         dept0_.id as id0_0_,
 17         dept0_.name as name0_0_ 
 18     from
 19         dept dept0_ 
 20     where
 21         dept0_.id=?
 22 qqq,Dept [id=2, name=2部門]
 23 qqq1,Dept [id=2, name=2部門]
 24 qqq2,Dept [id=2, name=2部門]
 25 qqq3,Dept [id=2, name=2部門]
 26 qqq4,Dept [id=2, name=2部門]
 27 qqq5,Dept [id=2, name=2部門]
 28 qqq6,Dept [id=2, name=2部門]
 29 qqq7,Dept [id=2, name=2部門]
 30 qqq8,Dept [id=2, name=2部門]
 31 qqq9,Dept [id=2, name=2部門]
 32 Hibernate: 
 33     select
 34         dept0_.id as id0_0_,
 35         dept0_.name as name0_0_ 
 36     from
 37         dept dept0_ 
 38     where
 39         dept0_.id=?
 40 yyy0,Dept [id=1, name=1部門]
 41 yyy1,Dept [id=1, name=1部門]
 42 yyy2,Dept [id=1, name=1部門]
 43 yyy3,Dept [id=1, name=1部門]
 44 yyy4,Dept [id=1, name=1部門]
 45 yyy5,Dept [id=1, name=1部門]
 46 yyy6,Dept [id=1, name=1部門]
 47 yyy7,Dept [id=1, name=1部門]
 48 yyy8,Dept [id=1, name=1部門]
 49 yyy9,Dept [id=1, name=1部門]
 50 yyy0,Dept [id=2, name=2部門]
 51 yyy1,Dept [id=2, name=2部門]
 52 yyy2,Dept [id=2, name=2部門]
 53 yyy3,Dept [id=2, name=2部門]
 54 yyy4,Dept [id=2, name=2部門]
 55 yyy5,Dept [id=2, name=2部門]
 56 yyy6,Dept [id=2, name=2部門]
 57 yyy7,Dept [id=2, name=2部門]
 58 yyy8,Dept [id=2, name=2部門]
 59 yyy9,Dept [id=2, name=2部門]
 60 Hibernate: 
 61     select
 62         dept0_.id as id0_0_,
 63         dept0_.name as name0_0_ 
 64     from
 65         dept dept0_ 
 66     where
 67         dept0_.id=?
 68 yyy0,Dept [id=3, name=3部門]
 69 yyy1,Dept [id=3, name=3部門]
 70 yyy2,Dept [id=3, name=3部門]
 71 yyy3,Dept [id=3, name=3部門]
 72 yyy4,Dept [id=3, name=3部門]
 73 yyy5,Dept [id=3, name=3部門]
 74 yyy6,Dept [id=3, name=3部門]
 75 yyy7,Dept [id=3, name=3部門]
 76 yyy8,Dept [id=3, name=3部門]
 77 yyy9,Dept [id=3, name=3部門]
 78 Hibernate: 
 79     select
 80         dept0_.id as id0_0_,
 81         dept0_.name as name0_0_ 
 82     from
 83         dept dept0_ 
 84     where
 85         dept0_.id=?
 86 yyy0,Dept [id=4, name=4部門]
 87 yyy1,Dept [id=4, name=4部門]
 88 yyy2,Dept [id=4, name=4部門]
 89 yyy3,Dept [id=4, name=4部門]
 90 yyy4,Dept [id=4, name=4部門]
 91 yyy5,Dept [id=4, name=4部門]
 92 yyy6,Dept [id=4, name=4部門]
 93 yyy7,Dept [id=4, name=4部門]
 94 yyy8,Dept [id=4, name=4部門]
 95 yyy9,Dept [id=4, name=4部門]
 96 Hibernate: 
 97     select
 98         dept0_.id as id0_0_,
 99         dept0_.name as name0_0_ 
100     from
101         dept dept0_ 
102     where
103         dept0_.id=?
104 yyy0,Dept [id=5, name=5部門]
105 yyy1,Dept [id=5, name=5部門]
106 yyy2,Dept [id=5, name=5部門]
107 yyy3,Dept [id=5, name=5部門]
108 yyy4,Dept [id=5, name=5部門]
109 yyy5,Dept [id=5, name=5部門]
110 yyy6,Dept [id=5, name=5部門]
111 yyy7,Dept [id=5, name=5部門]
112 yyy8,Dept [id=5, name=5部門]
113 yyy9,Dept [id=5, name=5部門]
View Code

            自己數一下,確實是傳送的6條sql語句,然後在Dept的class中加上batch-size屬性。

              

              結果就傳送兩條sql語句。

 1         List<Staff> list = session.createQuery("from Staff").list();
 2         
 3         for(Staff staff : list){
 4             System.out.println(staff.getName()+","+staff.getDept());
 5         }
 6 //結果
 7 Hibernate: 
 8     select
 9         staff0_.id as id1_,
10         staff0_.name as name1_,
11         staff0_.deptId as deptId1_ 
12     from
13         staff staff0_
14 Hibernate: 
15     select
16         dept0_.id as id0_0_,
17         dept0_.name as name0_0_ 
18     from
19         dept dept0_ 
20     where
21         dept0_.id in (
22             ?, ?, ?, ?, ?
23         )
24 qqq,Dept [id=2, name=2部門]
25 qqq1,Dept [id=2, name=2部門]
26 qqq2,Dept [id=2, name=2部門]
27 qqq3,Dept [id=2, name=2部門]
28 qqq4,Dept [id=2, name=2部門]
29 qqq5,Dept [id=2, name=2部門]
30 qqq6,Dept [id=2, name=2部門]
31 qqq7,Dept [id=2, name=2部門]
32 qqq8,Dept [id=2, name=2部門]
33 qqq9,Dept [id=2, name=2部門]
34 yyy0,Dept [id=1, name=1部門]
35 yyy1,Dept [id=1, name=1部門]
36 yyy2,Dept [id=1, name=1部門]
37 yyy3,Dept [id=1, name=1部門]
38 yyy4,Dept [id=1, name=1部門]
39 yyy5,Dept [id=1, name=1部門]
40 yyy6,Dept [id=1, name=1部門]
41 yyy7,Dept [id=1, name=1部門]
42 yyy8,Dept [id=1, name=1部門]
43 yyy9,Dept [id=1, name=1部門]
44 yyy0,Dept [id=2, name=2部門]
45 yyy1,Dept [id=2, name=2部門]
46 yyy2,Dept [id=2, name=2部門]
47 yyy3,Dept [id=2, name=2部門]
48 yyy4,Dept [id=2, name=2部門]
49 yyy5,Dept [id=2, name=2部門]
50 yyy6,Dept [id=2, name=2部門]
51 yyy7,Dept [id=2, name=2部門]
52 yyy8,Dept [id=2, name=2部門]
53 yyy9,Dept [id=2, name=2部門]
54 yyy0,Dept [id=3, name=3部門]
55 yyy1,Dept [id=3, name=3部門]
56 yyy2,Dept [id=3, name=3部門]
57 yyy3,Dept [id=3, name=3部門]
58 yyy4,Dept [id=3, name=3部門]
59 yyy5,Dept [id=3, name=3部門]
60 yyy6,Dept [id=3, name=3部門]
61 yyy7,Dept [id=3, name=3部門]
62 yyy8,Dept [id=3, name=3部門]
63 yyy9,Dept [id=3, name=3部門]
64 yyy0,Dept [id=4, name=4部門]
65 yyy1,Dept [id=4, name=4部門]
66 yyy2,Dept [id=4, name=4部門]
67 yyy3,Dept [id=4, name=4部門]
68 yyy4,Dept [id=4, name=4部門]
69 yyy5,Dept [id=4, name=4部門]
70 yyy6,Dept [id=4, name=4部門]
71 yyy7,Dept [id=4, name=4部門]
72 yyy8,Dept [id=4, name=4部門]
73 yyy9,Dept [id=4, name=4部門]
74 yyy0,Dept [id=5, name=5部門]
75 yyy1,Dept [id=5, name=5部門]
76 yyy2,Dept [id=5, name=5部門]
77 yyy3,Dept [id=5, name=5部門]
78 yyy4,Dept [id=5, name=5部門]
79 yyy5,Dept [id=5, name=5部門]
80 yyy6,Dept [id=5, name=5部門]
81 yyy7,Dept [id=5, name=5部門]
82 yyy8,Dept [id=5, name=5部門]
83 yyy9,Dept [id=5, name=5部門]
View Code

 

  

        疑問一:為什麼從一到多,batch-size就放在set中,而從多到一,batch-size也是放在一方的class中?

            這樣去想:一方查多方,在一方的set中就代表著多方的物件,將batch-size放在set中,就可以理解查多方的時候,就使用批量檢索了。

                 多方查一方,在一方的class中設定batch-size。可以理解,當多方查到一方時,在一方的對映檔案中的class部分遇到了batch-size就知道查詢一方時需要批量檢索了

            這樣應該更好理解和記憶。

        

五、總結

        這一章節分析的是三大檢索方式。其目的是讓開發者自己能夠調節效能,就三大塊內容

          1、類級別檢索

              get和load的區別,和原理。

          2、關聯級別檢索

              fetch和lazy屬性的用法。 這裡面要理解的前提是對sql語句比較瞭解,要知道什麼是左外連線,才能進一步討論迫切左外連線是什麼。

              一對多和多對多一組討論其用法

              多對一和一對一為一組討論其用法

          3、批量檢索

              解決n+1問題,要知道n+1描述的是什麼問題。

 

        差不多就到這裡了,一定不要因為學了這章導致基本的hibernate查詢度不會了,這個只是為了調節效能而深入討論的東西。平常該怎麼用就怎麼用,該怎麼用hql或者qbc查詢就怎麼查詢。只是在get、load查詢或者關聯級別查詢時留個心眼就足夠了。

    

 

相關文章