天天看點

Hibernate的延遲加載



----------------------------------------------  一 -------------------------------------------

hibernate延遲加載(懶加載)詳解

一.什麼是懶加載?他的作用?

延遲加載,也叫懶加載,它是hibernate為提高程式執行效率而提供的一種機制,即隻有真正使用該對象的資料時才會建立。

hibernate中主要是通過代理(proxy)機制來實作延遲加載。它的具體過程:hibernate叢資料庫擷取某一個對象資料時、擷取某一個對象的集合屬性值時,或擷取某一個對象所關聯的另一個對象時,由于沒有使用該對象的資料,hibernate并不是資料庫加載真正的資料,而隻是為該對象建立一個代理對象來代表這個對象,這個對象上的所有屬性都是預設值;隻有在真正需要使用該對象的資料時才建立這個真實對象,真正從資料庫中加載它的資料,這樣在某些情況下,就可以提高查詢效率。

有如下程式代碼:

user user=(user)session.load(clazz, id);//直接傳回的是代理對象

system.out.println(user.getid());//沒有發送sql語句到資料庫加載

user.getname();//建立真實的user執行個體,并發送sql語句到資料庫中

注意:1.不能判斷user=null;代理對象不可能為空

代理對象的限制:和代理關聯的session對象,如果session關閉後通路代理則抛異常。session關閉之前通路資料庫

2.getid()方法不行因為參數為id,getclass()方法不用通路資料庫就可以得到的資料

hibernate中預設采用延遲加載的情況主要有以下幾種

1,當調用session上的load()加載一個實體時,會采用延遲加載。

2,當session加載某個實體時,會對這個實體中的集合屬性值采用延遲加載

3當session加載某個實體時,會對這個實體所有單端關聯的另一個實體對象采用延遲加載。

二.關閉延遲加載

       延遲加載确實會給程式的查詢效率帶來好處,但有時明确知道資料需要立即加載,如果hibernate先預設使用延遲加載,而後又必須去資料庫加載,反而會降低效率

1.     加載單個實體,如果不需要延遲加載,就可以使用session的get()方法。

2.     當session加載某個實體時,不需要對這個實體中的集合屬性值延遲加載,而是要立即加載。這是可以在映射檔案中這個集合的配置元素(set bag list)添加屬性lazy=false;

3.     當session加載某個實體時,不需要對這個實體所單端關聯的另一個實體對象延遲加載,就可以在影射檔案中針對這個單端關聯的配置元素(<one-to-one><many-to-one>)添加lazy=false;

三.抓取政策

   通過asm和cglib二個包實作;domain是非final的。

1.session.load懶加載。

2.one-to-one(元素)懶加載:

       必需同時滿足下面三個條件時才能實作懶加載

       (主表不能有constrained=true,是以主表沒有懶加載)

       lazy!=false 2)constrained=true3)fetch=select

3.one-to-many (元素)懶加載:1)lazy!=false 2)fetch=select

4.many-to-one (元素) :1)lazy!=false 2)fetch=select

5.many-to-many (元素) :1)lazy!=false 2)fetch=select

6.能夠懶加載的對象都是被改寫過的代理對象,當相關聯的session沒有關閉時,通路這些懶加載對象(代理對象)的屬性(getid和 getclass除外)hibernate會初始化這些代理,或用hibernate.initialize(proxy)來初始化代理對象;當相關聯的 session關閉後,再通路懶加載的對象将出現異常。

---------------------------------------------------- 二 -------------------------------------------------

ps:hibernate 的延遲加載(lazy load)本質上就是代理模式的應用,我們在過去的歲月裡就經常通過代理模式來降低系統的記憶體開銷、提升應用的運作性能。hibernate 充分利用了代理模式的這種優勢,并結合了 javassist 或 cglib 來動态地生成代理對象,這更加增加了代理模式的靈活性,hibernate 給這種用法一個新名稱:延遲加載。無論怎樣,充分分析、了解這些開源架構的實作可以更好的感受經典設計模式的優勢所在

<a target="_blank" href="http://blog.csdn.net/xc635960736/article/details/7049863">http://blog.csdn.net/xc635960736/article/details/7049863</a>

ps:

如果是在one的一方查詢many,在one的一方的set方法裡加入屬性:lazy="false";

如果是在many的一方查詢one,在many的一方的many-to-one裡加上lazy="false";

--------------------------------------------- 三 ----------------------------------------------

hibernate延時加載包括延遲初始化錯誤,這是運用hibernate開發項目時最常見的錯誤。如果對一個類或者集合配置了延遲檢索政策,那麼必須當代理類執行個體或代理集合處于持久化狀态(即處于session範圍内)時,才能初始化它。如果在遊離狀态時才初始化它,就會産生延遲初始化錯誤。

下面把customer.hbm.xml檔案的&lt; class&gt;元素的lazy屬性設為true,表示使用延遲檢索政策:

&lt; class name="mypack.customer" table="customers" lazy="true"&gt;  

當執行session的load()方法時,hibernate不會立即執行查詢customers表的select語句,僅僅傳回customer類的代理類的執行個體,這個代理類具由以下特征:

(1)由hibernate在運作時動态生成,它擴充了customer類,是以它繼承了customer類的所有屬性和方法,但它的實作對于應用程式是透明的。

(2)當hibernate建立customer代理類執行個體時,僅僅初始化了它的oid屬性,其他屬性都為null,是以這個代理類執行個體占用的記憶體很少。

(3)當應用程式第一次通路customer代理類執行個體時(例如調用customer.getxxx()或customer.setxxx ()方法), hibernate會初始化代理類執行個體,在初始化過程中執行select語句,真正從資料庫中加載customer對象的所有資料。但有個例外,那就是當應用程式通路customer代理類執行個體的getid()方法時,hibernate不會初始化代理類執行個體,因為在建立代理類執行個體時oid就存在了,不必到資料庫中去查詢。

提示:hibernate采用cglib工具來生成持久化類的代理類。cglib是一個功能強大的java位元組碼生成工具,它能夠在程式運作時動态生成擴充 java類或者實作java接口的代理類。

以下代碼先通過session的load()方法加載customer對象,然後通路它的name屬性:

tx = session.begintransaction();   

customer customer=(customer)session.load(customer.class,new long(1));   

customer.getname();   

tx.commit();  

在運作session.load ()方法時hibernate不執行任何select語句,僅僅傳回customer類的代理類的執行個體,它的oid為1,這是由load()方法的第二個參數指定的。當應用程式調用customer.getname()方法時,hibernate會初始化customer代理類執行個體,從資料庫中加載 customer對象的資料,執行以下select語句:

select * from customers where id=1; 

select * from orders where customer_id=1;  

當&lt; class&gt;元素的lazy屬性為true,會影響session的load()方法的各種運作時行為,下面舉例說明。

1.如果加載的customer對象在資料庫中不存在,session的load()方法不會抛出異常,隻有當運作customer.getname()方法時才會抛出以下異常:

error lazyinitializer:63 - exception initializing proxy   

net.sf.hibernate.objectnotfoundexception: no row with the given identifier exists: 1, of class:   

mypack.customer  

2.如果在整個session範圍内,應用程式沒有通路過customer對象,那麼customer代理類的執行個體一直不會被初始化,hibernate不會執行任何select語句。以下代碼試圖在關閉session後通路customer遊離對象:

tx.commit();   

session.close();   

customer.getname();  

由于引用變量customer引用的customer代理類的執行個體在session範圍内始終沒有被初始化,是以在執行customer.getname()方法時,hibernate會抛出以下異常(hibernate延時加載的問題之一):

net.sf.hibernate.hibernateexception: couldnotinitializeproxy-theowningsessionwasclosed  

由此可見,customer代理類的執行個體隻有在目前session範圍内才能被初始化。

3.net.sf.hibernate.hibernate類的initialize()靜态方法用于在session範圍内顯式初始化代理類執行個體,isinitialized()方法用于判斷代理類執行個體是否已經被初始化。例如:

if(!hibernate.isinitialized(customer))   

hibernate.initialize(customer);   

以上代碼在session範圍内通過hibernate類的initialize()方法顯式初始化了customer代理類執行個體,是以當session關閉後,可以正常通路customer遊離對象。

4.當應用程式通路代理類執行個體的getid()方法時,不會觸發hibernate初始化代理類執行個體的行為,例如:

customer.getid();   

當應用程式通路customer.getid()方法時,該方法直接傳回customer代理類執行個體的oid值,無需查詢資料庫。由于引用變量 customer始終引用的是沒有被初始化的customer代理類執行個體,是以當session關閉後再執行customer.getname()方法, hibernate會抛出以下異常(hibernate延時加載的問題之一):

解決方法:

由于hibernate采用了lazy=true,這樣當你用hibernate查詢時,傳回實際為利用cglib增強的代理類,但其并沒有實際填充;當你在前端,利用它來取值(getxxx)時,這時hibernate才會到資料庫執行查詢,并填充對象,但此時如果和這個代理類相關的 session已關閉掉,就會産生種錯誤.

在做一對多時,有時會出現"could not initialize proxy - clothe owning session was sed,這個好像是hibernate的緩存問題.問題解決:需要在&lt; many-to-one&gt;裡設定lazy="false". 但有可能會引發另一個異常叫

failed to lazily initialize a collection of role: xxxxxxxx, no session or session was closed  

解決方法:在web.xml中加入 

&lt; filter&gt;   

    &lt; filter-name&gt;hibernatefilter&lt; /filter-name&gt; 

    &lt; filter-class&gt;   

     org.springframework.orm.hibernate3.support.opensessioninviewfilter   

    &lt; /filter-class&gt;   

&lt; /filter&gt;   

&lt; filter-mapping&gt;   

    &lt; url-pattern&gt;*.do&lt; /url-pattern&gt; 

&lt; /filter-mapping&gt; 

就可以了。

------------------------------------------------- 四  -------------------------------------------------

在web層進行延遲加載

幸運的是,spring架構為hibernate延遲加載與dao模式的整合提供了一種友善的解決方法。對那些不熟悉spring與 hibernate內建使用的人,我不會在這裡讨論過多的細節,但是我建議你去了解hibernate與spring內建的資料通路。以一個web應用為例,spring提供了opensessioninviewfilter和opensessioninviewinterceptor。我們可以随意選擇一個類來實作相同的功能。兩種方法唯一的不同就在于interceptor在spring容器中運作并被配置在web應用的上下文中,而filter在

spring之前運作并被配置在web.xml中。不管用哪個,他們都在請求将目前會話與目前(資料庫)線程綁定時打開hibernate會話。一旦已綁定到線程,這個打開了的hibernate會話可以在dao實作類中透明地使用。這個會話會為延遲加載資料庫中值對象的視圖保持打開狀态。一旦這個邏輯視圖完成了,hibernate會話會在filter的dofilter方法或者interceptor的posthandle方法中被關閉。下面是每個元件的配置示例:

interceptor的配置:

&lt;beans&gt;

&lt;bean id="urlmapping"

class="org.springframework.web.servlet.handler.simpleurlhandlermapping"&gt;

&lt;property name="interceptors"&gt;

&lt;list&gt;

&lt;ref bean="opensessioninviewinterceptor"/&gt;

&lt;/list&gt;

&lt;/property&gt;

&lt;property name="mappings"&gt;

&lt;/bean&gt;

&lt;bean name="opensessioninviewinterceptor"

class="org.springframework.orm.hibernate.support.opensessioninviewinterceptor"&gt;

&lt;property name="sessionfactory"&gt;&lt;ref bean="sessionfactory"/&gt;&lt;/property&gt;

&lt;/beans&gt;

filter的配置

&lt;web-app&gt;

&lt;filter&gt;

&lt;filter-name&gt;hibernatefilter&lt;/filter-name&gt;

&lt;filter-class&gt;

org.springframework.orm.hibernate.support.opensessioninviewfilter

&lt;/filter-class&gt;

&lt;/filter&gt;

&lt;filter-mapping&gt;

&lt;url-pattern&gt;*. spring &lt;/url-pattern&gt;

&lt;/filter-mapping&gt;

&lt;/web-app&gt;

實作hibernate的dao接口來使用打開的會話是很容易的。事實上,如果你已經使用了spring架構來實作你的hibernate dao,很可能你不需要改變任何東西。友善的hibernatetemplate公用元件使通路資料庫變成小菜一碟,而dao接口隻有通過這個元件才可以通路到資料庫。下面是一個示例的dao:

public class hibernateproductdao extends hibernatedaosupport implements productdao {

public product getproduct(integer productid) {

return (product)gethibernatetemplate().load(product.class, productid);

}

public integer saveproduct(product product) {

return (integer) gethibernatetemplate().save(product);

public void updateproduct(product product) {

gethibernatetemplate().update(product);

在業務邏輯層中使用延遲加載

即使在視圖外面,spring架構也通過使用aop 攔截器 hibernateinterceptor來使得延遲加載變得很容易實作。這個hibernate 攔截器透明地将調用配置在spring應用程式上下文中的業務對象中方法的請求攔截下來,在調用方法之前打開一個hibernate會話,然後在方法執行完之後将會話關閉。讓我們來看一個簡單的例子,假設我們有一個接口bussinessobject:

public     interface    businessobject     {

public     void    dosomethingthatinvolvesdaos();

類businessobjectimpl實作了businessobject接口:

public     class    businessobjectimpl    implements    businessobject     {

public     void    dosomethingthatinvolvesdaos()     {

//    lots of logic that calls

//    dao classes which access

//    data objects lazily 

通過在spring應用程式上下文中的一些配置,我們可以讓将調用businessobject的方法攔截下來,再令它的方法支援延遲加載。看看下面的一個程式片段:

&lt;bean id="hibernateinterceptor" class="org.springframework.orm.hibernate.hibernateinterceptor"&gt;

&lt;property name="sessionfactory"&gt;

&lt;ref bean="sessionfactory"/&gt;

&lt;bean id="businessobjecttarget" class="com.acompany.businessobjectimpl"&gt;

&lt;property name="somedao"&gt;&lt;ref bean="somedao"/&gt;&lt;/property&gt;

&lt;bean id="businessobject" class="org.springframework.aop.framework.proxyfactorybean"&gt;

&lt;property name="target"&gt;&lt;ref bean="businessobjecttarget"/&gt;&lt;/property&gt;

&lt;property name="proxyinterfaces"&gt;

&lt;value&gt;com.acompany.businessobject&lt;/value&gt;

&lt;property name="interceptornames"&gt;

&lt;value&gt;hibernateinterceptor&lt;/value&gt;

當businessobject被調用的時候,hibernateinterceptor打開一個hibernate會話,并将調用請求傳遞給 businessobjectimpl對象。當businessobjectimpl執行完成後,hibernateinterceptor透明地關閉了會話。應用層的代碼不用了解任何持久層邏輯,還是實作了延遲加載。

在單元測試中測試延遲加載

最後,我們需要用j-unit來測試我們的延遲加載程式。我們可以輕易地通過重寫testcase類中的setup和teardown方法來實作這個要求。我比較喜歡用這個友善的抽象類作為我所有測試類的基類。

public abstract class mylazytestcase extends testcase {

private sessionfactory sessionfactory;

private session session;

public void setup() throws exception {

super.setup();

sessionfactory sessionfactory = (sessionfactory) getbean("sessionfactory");

session = sessionfactoryutils.getsession(sessionfactory, true);

session s = sessionfactory.opensession();

transactionsynchronizationmanager.bindresource(sessionfactory, new sessionholder(s));

protected object getbean(string beanname) {

//code to get objects from spring application context

public void teardown() throws exception {

super.teardown();

sessionholder holder = (sessionholder) transactionsynchronizationmanager.getresource(sessionfactory);

session s = holder.getsession();

s.flush();

transactionsynchronizationmanager.unbindresource(sessionfactory);

sessionfactoryutils.closesessionifnecessary(s, sessionfactory);