could not initialize proxy - the owning Session was closed
關鍵字 : 異常引起的
其實這個異常寫的非常之清楚,就是會話關閉,無法對 Hibernate 實體進行操作。造成這樣的情況有很多,什麼書寫錯誤啊,邏輯錯誤啊 , 等等 . 不過 , 偶是因爲 LAZY.
關於 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 屬性:
Transaction 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
此異常解決方案請察看本人博客( 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>
Filter 的配置
實現 Hibernate 的 Dao 接口來使用打開的會話是很容易的。事實上,如果你已經使用了 Spring 框架來實現你的 Hibernate Dao, 很可能你不需要改變任何東西。方便的 HibernateTemplate 公用組件使訪問數據庫變成小菜一碟,而 DAO 接口只有通過這個組件纔可以訪問到數據庫。下面是一個示例的 DAO :
在業務邏輯層中使用延遲加載
即使在視圖外面, 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 的方法攔截下來,再令它的方法支持延遲加載。看看下面的一個程序片段:
當 businessObject 被調用的時候, HibernateInterceptor 打開一個 Hibernate 會話,並將調用請求傳遞給 BusinessObjectImpl 對象。當 BusinessObjectImpl 執行完成後, HibernateInterceptor 透明地關閉了會話。應用層的代碼不用瞭解任何持久層邏輯,還是實現了延遲加載。
在單元測試中測試延遲加載
最後,我們需要用 J-Unit 來測試我們的延遲加載程序。我們可以輕易地通過重寫 TestCase 類中的 setUp 和 tearDown 方法來實現這個要求。我比較喜歡用這個方便的抽象類作爲我所有測試類的基類。