實現跨事務的Hibernate懶加載

      最近使用Eclipse插件製作一個項目管理工具,由於涉及的數據結構比較複雜,簡單的展示一棵導航樹就涉及到至少20個表關聯查詢(其中包含大量的自關聯及循環關聯),剛開始數據量較小時尚可應付,當運行一段時間後,數據量達到萬級以後,性能就相當差了,仔細看了一下,由Hibernate翻譯過來的一條SQL就顯示了十屏以上,汗一個先,所以下定決心優化一下。
      首先想到是數據庫層面的優化,由於數據結構已經固定,無法再通過增加刪除表來優化,剩下的策略只能是加加索引,增加視圖來減化複雜性(要命的是視圖無法作更新操作),但收效甚微。
      其次想到的是使用Hibernate的二級緩存來優化性能,但由於CS應用的特點決定,數據操作一定是發生在併發的環境中,所以貿然打開二級緩存會帶來更大的麻煩(所謂的髒數據),所以基本放棄了,試着針對幾個常量表開了只讀的二級緩存,但效果依然不明顯。
      最後想到的是使用原來作WEB應用時常用的Open Session In View方式來作基於長事務的懶加載,但與WEB應用不同,WEB應用可以嚴格的界定事務邊界,簡單的加一個Filter就可以實現了,但CS架構的應用沒有明顯的邊界,數據的查詢和更新發生在整個生命週期的任何時刻,想破了頭也沒想到好的方法來實現,至此基本打算放棄了。

      這幾天翻看Hibernate源碼,發現Hibernate實現懶加載並不強制要求被加載對象與宿主對象必須在同一個事務中,只要有一個可用的Session,並且在這個Session的上下文(其實就是Session的一級緩存)中註冊相應的持久類信息即可,而這個Session是否與被加載對象的宿主處在同一個事務中,Hibernate並不關心,基於此準備實現一個跨事務的懶加載機制來簡化複雜業務下的大數據加載問題。

protected Object initialize(final Object proxy) {  
    if (proxy instanceof HibernateProxy)  
        return initializeHibernateProxy((HibernateProxy) proxy);  
    else if (proxy instanceof PersistentCollection)  
        return initializePersistentCollection((PersistentCollection) proxy);  
    return proxy;  
}  
  
private Object initializePersistentCollection(final PersistentCollection persistentCollection) {  
    if (persistentCollection.getRole() != null && !persistentCollection.wasInitialized() && ((AbstractPersistentCollection) persistentCollection).getSession() == null) {  
        return getHibernateTemplate().execute(new HibernateCallback() {  
            public Object doInHibernate(Session session) throws HibernateException, SQLException {  
                final SessionImplementor sessionImplementor = (SessionImplementor) session;  
                final CollectionPersister collectionPersister = sessionImplementor.getFactory().getCollectionPersister(persistentCollection.getRole());  
                sessionImplementor.getPersistenceContext().addUninitializedDetachedCollection(collectionPersister, persistentCollection);  
                persistentCollection.setCurrentSession(sessionImplementor);  
                persistentCollection.forceInitialization();  
                sessionImplementor.getPersistenceContext().clear();  
                persistentCollection.unsetSession(sessionImplementor);  
                return persistentCollection;  
            }  
        });  
    }  
    return persistentCollection;  
}  
  
private Object initializeHibernateProxy(final HibernateProxy proxy) {  
    final LazyInitializer lazyInitializer = proxy.getHibernateLazyInitializer();  
    if (lazyInitializer.isUninitialized() && lazyInitializer.getSession() == null) {  
        return getHibernateTemplate().execute(new HibernateCallback() {  
            public Object doInHibernate(Session session) throws HibernateException, SQLException {  
                lazyInitializer.setSession((SessionImplementor) session);  
                lazyInitializer.initialize();  
                lazyInitializer.unsetSession();  
                return proxy;  
            }  
        });  
    }  
    return proxy;  
}

      按道理,爲了儘可能的減少對原有結構的侵入,應該對所有實體類的GET方法作代理,判斷數據是否已被加載,如果沒加載,則調用initialize方法先加載數據後再返回,可是我至今沒有發現Hibernate提供類似的Intercepter,應該是可以使用第三方的代理來實現,這塊我再研究一下,目前的實現方式是對實體類的GET方法硬編碼:

public ComponentTypeBean getComponentTypeBean() {  
      return (ComponentTypeBean) initialize(componentTypeBean);  
}  
      
@SuppressWarnings("unchecked")  
public Set<PageComponentBean> getPageComponentBeans() {  
      return (Set<PageComponentBean>) initialize(pageComponentBeans);  
}  

      顯然這樣作可以實現預期效果,但並不理想,不知道Hibernate是否提供了實體類的GET代理,等有時間再翻一下文檔。

      至此,基於跨事務的懶加載已經實現,當然方法可能用的並不是很正確,希望有高人再指點一下。經測試實際效果非常理想,總結一下使用跨事務懶加載:
      好處:
      1.原來長篇大論的HQL全部可以簡化爲單表查詢了,換句話說再也不用費盡腦汁的去想需要寫幾個join fech了,只要查詢出主表數據即可,隨時隨地調用GET方法即可實現從表數據的懶加載,徹底告別could not initialize proxy - no Session,could not initialize proxy - the owning Session was closed等異常。
      2.將大SQL拆成若干個單表查詢的小SQL,在特定環境下會爲顯著提升系統性能,改善用戶體驗。
      壞處:
      1.會造成SQL量增大,用的不好反而會造成系統性能下降。
      2.與Open Session In View不同,跨事務懶加載破壞了Hibernte原有的緩存體系,無法發揮事務內緩存帶來的性能提升。

      原文地址: http://www.coolfancy.com/log/31.html

      更多精彩原創文章請關注筆者的原創博客:http://www.coolfancy.com


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章