隨筆 - 9  文章 - 1  trackbacks - 0
          <2011年2月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272812345
          6789101112

          不以薪水論英雄,英雄從不爭(zhēng)薪水。

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          hibernate 延遲加載問(wèn)題探討
          文章出處:http://fangyong2006.javaeye.com/blog/117555
          但就此說(shuō)一下關(guān)于lazy機(jī)制
          延遲初始化錯(cuò)誤是運(yùn)用Hibernate開(kāi)發(fā)項(xiàng)目時(shí)最常見(jiàn)的錯(cuò)誤。如果對(duì)一個(gè)類或者集合配置了延遲檢索策略,那么必須當(dāng)代理類實(shí)例或代理集合處于持久化狀態(tài)(即處于Session范圍內(nèi))時(shí),才能初始化它。如果在游離狀態(tài)時(shí)才初始化它,就會(huì)產(chǎn)生延遲初始化錯(cuò)誤。

          下面把Customer.hbm.xml文件的<class>元素的lazy屬性設(shè)為true,表示使用延遲檢索策略:

          <class name="mypack.Customer" table="CUSTOMERS" lazy="true">

          當(dāng)執(zhí)行Session的load()方法時(shí),Hibernate不會(huì)立即執(zhí)行查詢CUSTOMERS表的select語(yǔ)句,僅僅返回Customer類的代理類的實(shí)例,這個(gè)代理類具由以下特征:

          (1) 由Hibernate在運(yùn)行時(shí)動(dòng)態(tài)生成,它擴(kuò)展了Customer類,因此它繼承了Customer類的所有屬性和方法,但它的實(shí)現(xiàn)對(duì)于應(yīng)用程序是透明的。
          (2) 當(dāng)Hibernate創(chuàng)建Customer代理類實(shí)例時(shí),僅僅初始化了它的OID屬性,其他屬性都為null,因此這個(gè)代理類實(shí)例占用的內(nèi)存很少。
          (3)當(dāng)應(yīng)用程序第一次訪問(wèn)Customer代理類實(shí)例時(shí)(例如調(diào)用customer.getXXX()或customer.setXXX()方法), Hibernate會(huì)初始化代理類實(shí)例,在初始化過(guò)程中執(zhí)行select語(yǔ)句,真正從數(shù)據(jù)庫(kù)中加載Customer對(duì)象的所有數(shù)據(jù)。但有個(gè)例外,那就是當(dāng)應(yīng)用程序訪問(wèn)Customer代理類實(shí)例的getId()方法時(shí),Hibernate不會(huì)初始化代理類實(shí)例,因?yàn)樵趧?chuàng)建代理類實(shí)例時(shí)OID就存在了,不必到數(shù)據(jù)庫(kù)中去查詢。

          提示:Hibernate采用CGLIB工具來(lái)生成持久化類的代理類。CGLIB是一個(gè)功能強(qiáng)大的Java字節(jié)碼生成工具,它能夠在程序運(yùn)行時(shí)動(dòng)態(tài)生成擴(kuò)展 Java類或者實(shí)現(xiàn)Java接口的代理類。關(guān)于CGLIB的更多知識(shí),請(qǐng)參考:http://cglib.sourceforge.net/。

          以下代碼先通過(guò)Session的load()方法加載Customer對(duì)象,然后訪問(wèn)它的name屬性:

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

          在運(yùn)行session.load()方法時(shí)Hibernate不執(zhí)行任何select語(yǔ)句,僅僅返回Customer類的代理類的實(shí)例,它的OID為1,這是由load()方法的第二個(gè)參數(shù)指定的。當(dāng)應(yīng)用程序調(diào)用customer.getName()方法時(shí),Hibernate會(huì)初始化Customer代理類實(shí)例,從數(shù)據(jù)庫(kù)中加載Customer對(duì)象的數(shù)據(jù),執(zhí)行以下select語(yǔ)句:

          select * from CUSTOMERS where ID=1;
          select * from ORDERS where CUSTOMER_ID=1;

          當(dāng)<class>元素的lazy屬性為true,會(huì)影響Session的load()方法的各種運(yùn)行時(shí)行為,下面舉例說(shuō)明。

          1.如果加載的Customer對(duì)象在數(shù)據(jù)庫(kù)中不存在,Session的load()方法不會(huì)拋出異常,只有當(dāng)運(yùn)行customer.getName()方法時(shí)才會(huì)拋出以下異常:

          ERROR LazyInitializer:63 - Exception initializing proxy
          net.sf.hibernate.ObjectNotFoundException: No row with the given identifier exists: 1, of class:
          mypack.Customer

          2.如果在整個(gè)Session范圍內(nèi),應(yīng)用程序沒(méi)有訪問(wèn)過(guò)Customer對(duì)象,那么Customer代理類的實(shí)例一直不會(huì)被初始化,Hibernate不會(huì)執(zhí)行任何select語(yǔ)句。以下代碼試圖在關(guān)閉Session后訪問(wèn)Customer游離對(duì)象:

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

          由于引用變量customer引用的Customer代理類的實(shí)例在Session范圍內(nèi)始終沒(méi)有被初始化,因此在執(zhí)行customer.getName()方法時(shí),Hibernate會(huì)拋出以下異常:

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

          由此可見(jiàn),Customer代理類的實(shí)例只有在當(dāng)前Session范圍內(nèi)才能被初始化。

          3.net.sf.hibernate.Hibernate類的initialize()靜態(tài)方法用于在Session范圍內(nèi)顯式初始化代理類實(shí)例,isInitialized()方法用于判斷代理類實(shí)例是否已經(jīng)被初始化。例如:

          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范圍內(nèi)通過(guò)Hibernate類的initialize()方法顯式初始化了Customer代理類實(shí)例,因此當(dāng)Session關(guān)閉后,可以正常訪問(wèn)Customer游離對(duì)象。

          4.當(dāng)應(yīng)用程序訪問(wèn)代理類實(shí)例的getId()方法時(shí),不會(huì)觸發(fā)Hibernate初始化代理類實(shí)例的行為,例如:

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

          當(dāng)應(yīng)用程序訪問(wèn)customer.getId()方法時(shí),該方法直接返回Customer代理類實(shí)例的OID值,無(wú)需查詢數(shù)據(jù)庫(kù)。由于引用變量 customer始終引用的是沒(méi)有被初始化的Customer代理類實(shí)例,因此當(dāng)Session關(guān)閉后再執(zhí)行customer.getName()方法, Hibernate會(huì)拋出以下異常:

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


          解決方法:

          由于hibernate采用了lazy=true,這樣當(dāng)你用hibernate查詢時(shí),返回實(shí)際為利用cglib增強(qiáng)的代理類,但其并沒(méi)有實(shí)際填充;當(dāng)你在前端,利用它來(lái)取值(getXXX)時(shí),這時(shí)Hibernate才會(huì)到數(shù)據(jù)庫(kù)執(zhí)行查詢,并填充對(duì)象,但此時(shí)如果和這個(gè)代理類相關(guān)的session已關(guān)閉掉,就會(huì)產(chǎn)生種錯(cuò)誤.
          在做一對(duì)多時(shí),有時(shí)會(huì)出現(xiàn)"could not initialize proxy - clothe owning Session was sed,這個(gè)好像是hibernate的緩存問(wèn)題.問(wèn)題解決:需要在<many-to-one>里設(shè)置lazy="false". 但有可能會(huì)引發(fā)另一個(gè)異常叫

          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>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>*.do</url-pattern>
          </filter-mapping>
          就可以了;

          參考了:
          Hibernate與延遲加載:

          Hibernate對(duì)象關(guān)系映射提供延遲的與非延遲的對(duì)象初始化。非延遲加載在讀取一個(gè)對(duì)象的時(shí)候會(huì)將與這個(gè)對(duì)象所有相關(guān)的其他對(duì)象一起讀取出來(lái)。這有時(shí)會(huì)導(dǎo)致成百的(如果不是成千的話)select語(yǔ)句在讀取對(duì)象的時(shí)候執(zhí)行。這個(gè)問(wèn)題有時(shí)出現(xiàn)在使用雙向關(guān)系的時(shí)候,經(jīng)常會(huì)導(dǎo)致整個(gè)數(shù)據(jù)庫(kù)都在初始化的階段被讀出來(lái)了。當(dāng)然,你可以不厭其煩地檢查每一個(gè)對(duì)象與其他對(duì)象的關(guān)系,并把那些最昂貴的刪除,但是到最后,我們可能會(huì)因此失去了本想在ORM工具中獲得的便利。


          一個(gè)明顯的解決方法是使用Hibernate提供的延遲加載機(jī)制。這種初始化策略只在一個(gè)對(duì)象調(diào)用它的一對(duì)多或多對(duì)多關(guān)系時(shí)才將關(guān)系對(duì)象讀取出來(lái)。這個(gè)過(guò)程對(duì)開(kāi)發(fā)者來(lái)說(shuō)是透明的,而且只進(jìn)行了很少的數(shù)據(jù)庫(kù)操作請(qǐng)求,因此會(huì)得到比較明顯的性能提升。這項(xiàng)技術(shù)的一個(gè)缺陷是延遲加載技術(shù)要求一個(gè)Hibernate會(huì)話要在對(duì)象使用的時(shí)候一直開(kāi)著。這會(huì)成為通過(guò)使用DAO模式將持久層抽象出來(lái)時(shí)的一個(gè)主要問(wèn)題。為了將持久化機(jī)制完全地抽象出來(lái),所有的數(shù)據(jù)庫(kù)邏輯,包括打開(kāi)或關(guān)閉會(huì)話,都不能在應(yīng)用層出現(xiàn)。最常見(jiàn)的是,一些實(shí)現(xiàn)了簡(jiǎn)單接口的DAO實(shí)現(xiàn)類將數(shù)據(jù)庫(kù)邏輯完全封裝起來(lái)了。一種快速但是笨拙的解決方法是放棄DAO模式,將數(shù)據(jù)庫(kù)連接邏輯加到應(yīng)用層中來(lái)。這可能對(duì)一些小的應(yīng)用程序有效,但是在大的系統(tǒng)中,這是一個(gè)嚴(yán)重的設(shè)計(jì)缺陷,妨礙了系統(tǒng)的可擴(kuò)展性。

          在Web層進(jìn)行延遲加載

          幸運(yùn)的是,Spring框架為Hibernate延遲加載與DAO模式的整合提供了一種方便的解決方法。對(duì)那些不熟悉Spring與Hibernate集成使用的人,我不會(huì)在這里討論過(guò)多的細(xì)節(jié),但是我建議你去了解Hibernate與Spring集成的數(shù)據(jù)訪問(wèn)。以一個(gè)Web應(yīng)用為例,Spring提供了OpenSessionInViewFilter和OpenSessionInViewInterceptor。我們可以隨意選擇一個(gè)類來(lái)實(shí)現(xiàn)相同的功能。兩種方法唯一的不同就在于interceptor在Spring容器中運(yùn)行并被配置在web應(yīng)用的上下文中,而Filter在Spring之前運(yùn)行并被配置在web.xml中。不管用哪個(gè),他們都在請(qǐng)求將當(dāng)前會(huì)話與當(dāng)前(數(shù)據(jù)庫(kù))線程綁定時(shí)打開(kāi)Hibernate會(huì)話。一旦已綁定到線程,這個(gè)打開(kāi)了的Hibernate會(huì)話可以在DAO實(shí)現(xiàn)類中透明地使用。這個(gè)會(huì)話會(huì)為延遲加載數(shù)據(jù)庫(kù)中值對(duì)象的視圖保持打開(kāi)狀態(tài)。一旦這個(gè)邏輯視圖完成了,Hibernate會(huì)話會(huì)在Filter的doFilter方法或者Interceptor的postHandle方法中被關(guān)閉。下面是每個(gè)組件的配置示例:



          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>


          實(shí)現(xiàn)Hibernate的Dao接口來(lái)使用打開(kāi)的會(huì)話是很容易的。事實(shí)上,如果你已經(jīng)使用了Spring框架來(lái)實(shí)現(xiàn)你的Hibernate Dao,很可能你不需要改變?nèi)魏螙|西。方便的HibernateTemplate公用組件使訪問(wèn)數(shù)據(jù)庫(kù)變成小菜一碟,而DAO接口只有通過(guò)這個(gè)組件才可以訪問(wèn)到數(shù)據(jù)庫(kù)。下面是一個(gè)示例的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);
          }
          }


          在業(yè)務(wù)邏輯層中使用延遲加載

          即使在視圖外面,Spring框架也通過(guò)使用AOP 攔截器 HibernateInterceptor來(lái)使得延遲加載變得很容易實(shí)現(xiàn)。這個(gè)Hibernate 攔截器透明地將調(diào)用配置在Spring應(yīng)用程序上下文中的業(yè)務(wù)對(duì)象中方法的請(qǐng)求攔截下來(lái),在調(diào)用方法之前打開(kāi)一個(gè)Hibernate會(huì)話,然后在方法執(zhí)行完之后將會(huì)話關(guān)閉。讓我們來(lái)看一個(gè)簡(jiǎn)單的例子,假設(shè)我們有一個(gè)接口BussinessObject:


          public  interface  BusinessObject  {
          public  void  doSomethingThatInvolvesDaos();
          }
          類BusinessObjectImpl實(shí)現(xiàn)了BusinessObject接口:

          public  class  BusinessObjectImpl  implements  BusinessObject  {
          public  void  doSomethingThatInvolvesDaos()  {
          //  lots of logic that calls
          //  DAO classes Which access
          //  data objects lazily
          }
          }



          通過(guò)在Spring應(yīng)用程序上下文中的一些配置,我們可以讓將調(diào)用BusinessObject的方法攔截下來(lái),再令它的方法支持延遲加載。看看下面的一個(gè)程序片段:



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

          當(dāng)businessObject被調(diào)用的時(shí)候,HibernateInterceptor打開(kāi)一個(gè)Hibernate會(huì)話,并將調(diào)用請(qǐng)求傳遞給BusinessObjectImpl對(duì)象。當(dāng)BusinessObjectImpl執(zhí)行完成后,HibernateInterceptor透明地關(guān)閉了會(huì)話。應(yīng)用層的代碼不用了解任何持久層邏輯,還是實(shí)現(xiàn)了延遲加載。


          在單元測(cè)試中測(cè)試延遲加載

          最后,我們需要用J-Unit來(lái)測(cè)試我們的延遲加載程序。我們可以輕易地通過(guò)重寫TestCase類中的setUp和tearDown方法來(lái)實(shí)現(xiàn)這個(gè)要求。我比較喜歡用這個(gè)方便的抽象類作為我所有測(cè)試類的基類。


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

          posted on 2011-02-18 17:24 稻草 閱讀(251) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 天水市| 济宁市| 龙游县| 乌鲁木齐县| 灌云县| 南宫市| 曲阜市| 师宗县| 城步| 甘谷县| 高阳县| 稷山县| 启东市| 渝中区| 日照市| 公安县| 商水县| 东平县| 鹤壁市| 中牟县| 大石桥市| 枣强县| 甘肃省| 浦县| 嫩江县| 祁连县| 建德市| 托里县| 江川县| 新河县| 梁平县| 九龙城区| 当阳市| 米脂县| 磐安县| 漯河市| 兴山县| 奉新县| 禹州市| 登封市| 连城县|