hibernate lazy和fetch屬性介紹


lazy延遲加載

lazy(延遲加載)策略可用於<class>標簽,<property>標簽,集合(<set>/<list>)標簽以及<one-to-one>/<many-to-one>標簽上

<class>

class標簽中的lazy可選屬性為true/false,默認為ture,代表默認使用延遲加載策略

 1 public static void main(String[] args) {
 2         Session session = HibernateFactory.currentSession();
 3         Transaction tx = session.beginTransaction();
 4         
 5         Student student = (Student) session.load(Student.class, 1);
 6         
 7         tx.commit();
 8         HibernateFactory.closeSession();
 9         
10         System.out.println(student.getName());
11     }

以上代碼在Session范圍內load了一個Student對象,此時Hibernate不會立即執行查詢student表的select語句,僅僅返回Student類的CGLIB代理類的實例,這個代理類實例有以下特征:

1:由Hibernate在運行時動態生成,繼承了Student類的所有屬性和方法。

2:當Hibernate創建Student代理類實例時,僅僅初始化了它的OID屬性,其他屬性都為null。

3:當應用程序第一次訪問Student代理類實例時(例如調用student.getXXX()或student.setXXX()方法),Hibernate會初始化代理類實例,執行select語句,從數據庫中加載Student對象的所有數據。但應用程序訪問Student代理類實例的getId()方法時,Hibernate不會初始化代理類實例,因為在創建代理類實例時OID就存在了,不必到數據庫中去查詢。 

4:如果在Session范圍內沒有訪問Student代理類實例,而是在Session關閉后訪問了代理類實例,那么就會拋出"could not initialize proxy - no Session"的異常。

 

所以上訴代碼會報錯,如果想要代碼正常運行,有以下四種修改方法

1:將<class>中lazy屬性設置未false,表示不使用延遲加載策略,當Session調用load()函數時會立刻從數據庫中select出student的數據

2:在Session范圍內訪問一下student對象(例如調用student.getName())

3:使用get()方法代替load()方法,get()方法執行的時候會立即向數據庫發出查詢語句

4:使用Hibernate的API initialize來初始化代理類實例,代碼如下

 1 public static void main(String[] args) {
 2         Session session = HibernateFactory.currentSession();
 3         Transaction tx = session.beginTransaction();
 4         
 5         Student student = (Student) session.load(Student.class, 1);
 6         if(!Hibernate.isInitialized(student)) {
 7             Hibernate.initialize(student);
 8         }
 9     
10         tx.commit();
11         HibernateFactory.closeSession();
12         
13         System.out.println(student.getName());
14     }

 

<property>

property標簽中的lazy可選屬性為true/false,這個特性需要類增強。

 

集合(set/list等)

集合標簽中的lazy可選屬性為true/false/extra,默認為true,代表只有在調用這個集合獲取里面的元素對象時,才發出查詢語句,加載其集合元素的數據。

首先插入數據到grade和student表中

public class HibernateTest {
    public static void main(String[] args) {
        Session session = HibernateFactory.currentSession();
        Transaction tx = session.beginTransaction();
        
        Grade grade = new Grade();
        grade.setName("grade1");
        
        Student student1 = new Student();
        student1.setName("student1");
        student1.setGrade(grade);
        
        Student student2 = new Student();
        student2.setName("student2");
        student2.setGrade(grade);
        
        grade.getStudents().add(student1);
        grade.getStudents().add(student2);
        session.save(grade);
    
        tx.commit();
        HibernateFactory.closeSession();
    }
}

 測試程序取出grade以及他對應的student列表(hbm文件中關閉了<class>級別的延遲加載)

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Grade grade = (Grade)session.load(Grade.class, 1);
 7         
 8         tx.commit();
 9         HibernateFactory.closeSession();
10         System.out.println(grade.getStudents());
11     }
12 }

 以上代碼在Session范圍中load了一個Grade對象,因為並沒有對<class>進行延遲加載,所以從數據庫中取到了grade的數據,但在session范圍外去獲取grade的集合屬性時程序缺拋出異常(

 failed to lazily initialize a collection of role),因為Hibernate對集合屬性進行了延遲加載,如果想要代碼正常運行,有以下三種修改方法

1:在集合標簽<set>上設置lazy為false,取消集合的延遲加載,但這樣每次都會把set中的數據全部取出來,占用了大量內存,影響程序性能

2:在集合標簽<set>上設置fetch屬性為"join",這個屬性在后面fetch屬性介紹時會再提到

3:使用hql語句加載grade數據,hql中顯式的對兩個對象進行連接,代碼如下

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6     
 7         Query query = session.createQuery("from Grade as grade left outer join fetch grade.students where grade.id=:id");
 8         query.setParameter("id", 1);
 9         Grade grade = (Grade) query.uniqueResult();
10         
11         tx.commit();
12         HibernateFactory.closeSession();
13         System.out.println(grade.getStudents());
14     }
15 }

 

在集合的lazy屬性中有一個值為extra,這是一種比較聰明的延遲加載策略,即調用集合的size/contains等方法的時候,hibernate並不會去加載整個集合的數據,而是發出一條"聰明"的SQL語句以便獲得需要的值(例如通過sql中的count語句獲取集合的size),只有在真正需要用到這些集合元素對象數據的時候,才去發出查詢語句加載所有對象的數據。

 

<many-to-one>

<many-to-one>標簽中的lazy可選屬性為false/proxy/no-proxy,默認屬性為proxy

public class HibernateTest {
    public static void main(String[] args) {
        Session session = HibernateFactory.currentSession();
        Transaction tx = session.beginTransaction();
        
        Student student = (Student)session.load(Student.class,1);
        
        tx.commit();
        HibernateFactory.closeSession();
        System.out.println(student.getName());
        System.out.println(student.getGrade().getName());
    }
}

以上代碼會報異常,因為lazy的默認屬性為proxy,作用與lazy="true"相似,啟用延遲加載,而在Session范圍外去取grade的值會出錯,如果要使程序正確執行,有以下3種方法

1:lazy="false",關閉延遲加載

2:在session范圍內訪問grade對象或者對其初始化

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Student student = (Student)session.load(Student.class,1);
 7         if(!Hibernate.isInitialized(student.getGrade())) {
 8             Hibernate.initialize(student.getGrade());
 9         }
10         tx.commit();
11         HibernateFactory.closeSession();
12         System.out.println(student.getName());
13         System.out.println(student.getGrade().getName());
14     }
15 }

3:配置Grade.hbm.xml,把它<class>標簽中的lazy設置為false,這樣不管<many-to-one>標簽中lazy的值是什么,都會立刻加載grade對象

 

<many-to-one>的lazy標簽還有一個取值為no-proxy,它和proxy效果一樣,不過proxy對象不是動態,是在編譯的過程中就創建的,需要進行特定的編譯

 

<one-to-one>

<one-to-one>與<many-to-one>基本一致,標簽中的lazy可選屬性為false/proxy/no-proxy,默認屬性為proxy,但是<one-to-one>中有一個屬性為constrained,一旦設置為false(這也是它的默認值),不管lazy設置為何值,Hibernate都會采取預先抓取。

 

fetch抓取策略

<many-to-one>/<one-to-one>

<many-to-one>/<one-to-one>標簽上fetch的可選取值有select/join,默認為select

fetch = "select"是在查詢的時候先查詢出一端的實體,然后在根據一端的查詢出多端的實體,會產生1+n條sql語句;

fetch = "join"是在查詢的時候使用外連接進行查詢,不會差生1+n的現象。此時lazy會失效

 

集合標簽 

集合標簽(例如<set>/<list>)上fetch的可選取值有select/join/subselect,默認為select

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Grade grade = (Grade)session.load(Grade.class,1);
 7         System.out.println(grade.getStudents());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11     }
12 }

當設置為select時,<set name="students" inverse="true" cascade="all" fetch="select">

輸出的sql語句為:

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_0_, 
 4     grade0_.name as name2_0_0_ 
 5 from 
 6     grade grade0_ 
 7 where 
 8     grade0_.id=?
 9 
10 Hibernate: 
11 select 
12     students0_.gradeid as gradeid3_0_0_, 
13     students0_.id as id1_1_0_, 
14     students0_.id as id1_1_1_, 
15     students0_.name as name2_1_1_, 
16     students0_.gradeid as gradeid3_1_1_ 
17 from 
18     student students0_ 
19 where 
20     students0_.gradeid=?

先查詢出一端的實體,當你真正訪問關聯關系的時候,才會執行第二條select語句抓取當前對象的關聯實體或集合。

 

當設置為join時,<set name="students" inverse="true" cascade="all" fetch="join">

輸出的sql語句為:

 1 Hibernate:
 2 select 
 3       grade0_.id as id1_0_0_, 
 4       grade0_.name as name2_0_0_, 
 5       students1_.gradeid as gradeid3_0_1_, 
 6       students1_.id as id1_1_1_, 
 7       students1_.id as id1_1_2_, 
 8       students1_.name as name2_1_2_, 
 9       students1_.gradeid as gradeid3_1_2_ 
10 from 
11      grade grade0_ 
12 left outer join 
13      student students1_ 
14 on 
15      grade0_.id=students1_.gradeid 
16 where 
17      grade0_.id=?

查詢的時候使用外連接進行查詢。

 

當設置為subselect時,<set name="students" inverse="true" cascade="all" fetch="subselect">

使用新的測試程序

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         List<Grade> grades = session.createQuery("from Grade").list();
 7         System.out.println(grades.get(0).getStudents().size());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11         System.out.println(grades.get(1).getStudents());
12     }
13 }

輸出的sql語句為:

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_, 
 4     grade0_.name as name2_0_ 
 5 from 
 6     grade grade0_
 7 
 8 Hibernate: 
 9 select 
10     students0_.gradeid as gradeid3_0_1_, 
11     students0_.id as id1_1_1_, 
12     students0_.id as id1_1_0_, 
13     students0_.name as name2_1_0_, 
14     students0_.gradeid as gradeid3_1_0_ 
15 from 
16     student students0_ 
17 where 
18     students0_.gradeid 
19 in 
20     (select grade0_.id from grade grade0_)

另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實體對象的關聯集合. 這個理解起來有點糊塗, 舉個例子 : 如果你使用 Query 查詢出了2個Grade 實體, 由於開啟了懶加載,那么他們的 students 都沒有被初始化, 此時手動初始化一個Grade 的 students,Hibernate 會將前面查詢到的實體對象(2個Grade)的關聯集合使用一條 Select 語句一次性抓取回來, 這樣減少了與數據庫的交互次數, 一次將每個對象的集合都給初始化了;(他是將上一次查詢的 SQL 語句作為這一次查詢的 SQL語句的 where 子查詢, 所以上次查詢到幾個對象,那么這次就初始化幾個對象的集合)。

分析上面的代碼可以發現,在session范圍外訪問了grades.get(1).getStudents();因為使用的時"subselect"抓取策略,在訪問grades.get(0).getStudents()時會把之前查詢到的實體的關聯集合全部一次性抓取回來,所以程序沒有報錯;但如果使用"select"抓取策略的話,由於session范圍內沒有訪問grades.get(1).getStudents();所以在session范圍外訪問它會報錯,這就是select和subselect的區別。

 

batch-size批量加載屬性

batch-size可以看成是"select"和"subselect"的折中策略,既不想一次只加載一個實體的關聯集合,也不想一次加載所有實體的關聯結合,可以配合使用"select"和"batch-size",具體使用方法如下:

首先數據庫中的數據如下

Grade.hbm.xml的關鍵配置如下

1 <set name="students" inverse="true" cascade="all" fetch="select" batch-size="2">
2             <key column="gradeid"/>
3             <one-to-many class="com.zlt.hibernatedemo.Student"/>
4 </set>

 

測試代碼

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         List<Grade> grades = session.createQuery("from Grade").list();
 7         System.out.println(grades.get(0).getStudents().size());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11         System.out.println(grades.get(1).getStudents());
12     }
13 }

 

輸出的sql語句

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_, 
 4     grade0_.name as name2_0_ 
 5 from 
 6     grade grade0_
 7 
 8 Hibernate: 
 9 select 
10     students0_.gradeid as gradeid3_0_1_, 
11     students0_.id as id1_1_1_, 
12     students0_.id as id1_1_0_, 
13     students0_.name as name2_1_0_, 
14     students0_.gradeid as gradeid3_1_0_ 
15 from 
16     student students0_ 
17 where 
18     students0_.gradeid in (?, ?)

 

結果分析:

以上代碼在Session范圍內抓取了grades.get(0).getStudents(),而在Session范圍外訪問了grades.get(1).getStudents(),由於使用的是"select"抓取策略而不是"subselect",所以應該報異常,但是程序卻正確執行,而且確實抓取到了grades.get(1).getStudents(),這是因為設置了batch-size="2",Hibernate 使用一條 Select 語句一次性抓取2個grade實體對象的關聯集合回來(例子中前一次查詢共查詢出4個grade實體對象,如果使用"subselect"抓取策略會一次性抓取這4個實體對象的關聯集合),所以可以發現sql語句中的第二條用了in。

當抓取grades.get(0).getStudents()時,會一次抓取兩個關聯集合,即grades.get(0)和grades.get(1),此時在Session范圍外訪問grades.get(2)和grades.get(3)會報錯

當抓取grades.get(1).getStudents()時,會一次抓取兩個關聯集合,即grades.get(1)和grades.get(2),此時在Session范圍外訪問grades.get(0)和grades.get(3)會報錯

當抓取grades.get(2).getStudents()時,會一次抓取兩個關聯集合,即grades.get(2)和grades.get(3),此時在Session范圍外訪問grades.get(0)和grades.get(1)會報錯

當抓取grades.get(3).getStudents()時,會一次抓取兩個關聯集合,即grades.get(3)和grades.get(0),此時在Session范圍外訪問grades.get(1)和grades.get(2)會報錯

 

fetch與lazy組合情況

1、當lazy="true"  fetch="select" 的時候,這個時候是使用了延遲策略,開始只查詢出一端實體,多端的不會查詢,只有當用到的時候才會發出sql語句去查詢。

2、當lazy="false"  fetch = "select" 的時候,個時候是使沒有用延遲策略,同時查詢出一端和多端,同時產生1+n條sql。

3、當fetch = "join"的時候,不管lazy設置為什么,這個時候延遲已經沒有什么用了,因為采用的是外連接查詢,同時把一端和多端都查詢出來了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM