1. 程式人生 > >轉來的一篇關於hibernate的查詢資料,很不錯

轉來的一篇關於hibernate的查詢資料,很不錯

其實這個異常寫的非常之清楚,就是會話關閉,無法對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: Couldnotinitializeproxy-theowningSessionwasclosed

由此可見,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: Couldnotinitializeproxy-theowningSessionwasclosed

解決方法:

由於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

此異常解決方案請察看本人部落格(http://hi.baidu.com/kekemao1)的Hibernate異常中的《failed to lazily initialize a collection of role異常》

?
解決方法:在web.xml中加入
<filter>
    <filter-name>hibernateFilter</filter-name>
    <filter-class>
     org.springframework.orm.hibernate3.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);
}
}