---------------------------------------------- 一 -------------------------------------------
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檔案的< class>元素的lazy屬性設為true,表示使用延遲檢索政策:
< class name="mypack.customer" table="customers" lazy="true">
當執行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;
當< class>元素的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的緩存問題.問題解決:需要在< many-to-one>裡設定lazy="false". 但有可能會引發另一個異常叫
failed to lazily initialize a collection of role: xxxxxxxx, no session or session was closed
解決方法:在web.xml中加入
< filter>
< filter-name>hibernatefilter< /filter-name>
< filter-class>
org.springframework.orm.hibernate3.support.opensessioninviewfilter
< /filter-class>
< /filter>
< filter-mapping>
< url-pattern>*.do< /url-pattern>
< /filter-mapping>
就可以了。
------------------------------------------------- 四 -------------------------------------------------
在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的配置:
<beans>
<bean id="urlmapping"
class="org.springframework.web.servlet.handler.simpleurlhandlermapping">
<property name="interceptors">
<list>
<ref bean="opensessioninviewinterceptor"/>
</list>
</property>
<property name="mappings">
</bean>
<bean name="opensessioninviewinterceptor"
class="org.springframework.orm.hibernate.support.opensessioninviewinterceptor">
<property name="sessionfactory"><ref bean="sessionfactory"/></property>
</beans>
filter的配置
<web-app>
<filter>
<filter-name>hibernatefilter</filter-name>
<filter-class>
org.springframework.orm.hibernate.support.opensessioninviewfilter
</filter-class>
</filter>
<filter-mapping>
<url-pattern>*. spring </url-pattern>
</filter-mapping>
</web-app>
實作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的方法攔截下來,再令它的方法支援延遲加載。看看下面的一個程式片段:
<bean id="hibernateinterceptor" class="org.springframework.orm.hibernate.hibernateinterceptor">
<property name="sessionfactory">
<ref bean="sessionfactory"/>
<bean id="businessobjecttarget" class="com.acompany.businessobjectimpl">
<property name="somedao"><ref bean="somedao"/></property>
<bean id="businessobject" class="org.springframework.aop.framework.proxyfactorybean">
<property name="target"><ref bean="businessobjecttarget"/></property>
<property name="proxyinterfaces">
<value>com.acompany.businessobject</value>
<property name="interceptornames">
<value>hibernateinterceptor</value>
當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);