hibernate 延遲加載問題探討

關於 lazy 機制:

延遲初始化錯誤是運用 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接口的代理類。關於CGLIB的更多知識,請參考: http://cglib.sourceforge.net/

如下代碼先經過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 = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,new Long(1));
tx.commit();
session.close();
customer.getName();

因爲引用變量customer引用的Customer代理類的實例在Session範圍內始終沒有被初始化,所以在執行customer.getName()方法時, Hibernate 會拋出如下異常:

ERROR LazyInitializer:63 - Exception initializing proxy
net.sf. hibernate .HibernateException: Could not initialize proxy - the owning Session was closed

因而可知,Customer代理類的實例只有在當前Session範圍內才能被初始化。

3.net.sf. hibernate . Hibernate 類的initialize()靜態方法用於在Session範圍內顯式初始化代理類實例,isInitialized()方法用於判斷代理類實例是否已經被初始化。例如:

tx = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,new Long(1));
if(! Hibernate .isInitialized(customer)) 
Hibernate .initialize(customer);
tx.commit();
session.close();
customer.getName();

以上代碼在Session範圍內經過 Hibernate 類的initialize()方法顯式初始化了Customer代理類實例,所以當Session關閉後,能夠正常訪問Customer遊離對象。

4.當應用程序訪問代理類實例的getId()方法時,不會觸發 Hibernate 初始化代理類實例的行爲,例如:

tx = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,new Long(1));
customer.getId();
tx.commit();
session.close();
customer.getName();

當應用程序訪問customer.getId()方法時,該方法直接返回Customer代理類實例的OID值,無需查詢數據庫。因爲引用變量 customer始終引用的是沒有被初始化的Customer代理類實例,所以當Session關閉後再執行customer.getName()方法,  Hibernate 會拋出如下異常:

ERROR LazyInitializer:63 - Exception initializing proxy
net.sf. hibernate .HibernateException: Could not initialize proxy - the owning Session was closed


解決方法:

因爲 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. hibernate 3.support.OpenSessionInViewFilter
  </filter-class>
</filter
<filter-mapping>
  <filter-name>hibernateFilter</filter-name>
  <url-pattern>*.do</url-pattern>
</filter-mapping>
就能夠了;

參考了:
Hibernate 延遲加載

Hibernate 對象關係映射提供延遲的與非延遲的對象初始化。非 延遲加載 在讀取一個對象的時候會將與這個對象全部相關的其餘對象一塊兒讀取出來。這有時會致使成百的(若是不是成千的話)select語句在讀取對象的時候執行。這個問題有時出如今使用雙向關係的時候,常常會致使整個數據庫都在初始化的階段被讀出來了。固然,你能夠不厭其煩地檢查每個對象與其餘對象的關係,並把那些最昂貴的刪除,可是到最後,咱們可能會所以失去了本想在ORM工具中得到的便利。


一個明顯的解決方法是使用 Hibernate 提供的 延遲加載 機制。這種初始化策略只在一個對象調用它的一對多或多對多關係時纔將關係對象讀取出來。這個過程對開發者來講是透明的,並且只進行了不多的數據庫操做請求,所以會獲得比較明顯的性能提高。這項技術的一個缺陷是 延遲加載 技術要求一個 Hibernate 會話要在對象使用的時候一直開着。這會成爲經過使用DAO模式將持久層抽象出來時的一個主要問題。爲了將持久化機制徹底地抽象出來,全部的數據庫邏輯,包括打開或關閉會話,都不能在應用層出現。最多見的是,一些實現了簡單接口的DAO實現類將數據庫邏輯徹底封裝起來了。一種快速可是笨拙的解決方法是放棄DAO模式,將數據庫鏈接邏輯加到應用層中來。這可能對一些小的應用程序有效,可是在大的系統中,這是一個嚴重的設計缺陷,妨礙了系統的可擴展性。

在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> 
</bean> 
</beans> 

Filter的配置


<web-app> 

<filter> 
<filter-name>hibernateFilter</filter-name> 
<filter-class> 
org.springframework.orm. hibernate .support.OpenSessionInViewFilter 
</filter-class> 
</filter> 

<filter-mapping> 
<filter-name>hibernateFilter</filter-name> 
<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的方法攔截下來,再令它的方法支持 延遲加載 。看看下面的一個程序片斷:



<beans> 
<bean id="hibernateInterceptor" class="org.springframework.orm. hibernate .HibernateInterceptor"> 
<property name="sessionFactory"> 
<ref bean="sessionFactory"/> 
</property> 
</bean> 
<bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl"> 
<property name="someDAO"><ref bean="someDAO"/></property> 
</bean> 
<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> 
<property name="interceptorNames"> 
<list> 
<value>hibernateInterceptor</value> 
</list> 
</property> 
</bean> 
</beans>

當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); 

相關文章
相關標籤/搜索