跳到主要內容

使用Spring的HibernateDAOSupport可能遇到的Lazy initialization問題

在專案中我們使用DAO這個pattern來封裝資料庫存取層,Hibernate Quickly (Manning)中建議我們可以繼承Spring所提供的HibernateDAOSupport來簡化DAO程式碼,這是很常見的作法。

原本method重複性很高的部份:
public void create(Event event) throws DataAccessLayerException {
try {
    startOperation();
    session.save(event);
    tx.commit();
} catch (HibernateException e) {
    handleException(e);
} finally {
    HibernateFactory.close(session);
}
}

可以被簡化為:
public abstract class AbstractSpringDao  extends HibernateDaoSupport{

public AbstractSpringDao() { }

protected void saveOrUpdate(Object obj) {
 getHibernateTemplate().saveOrUpdate(obj);
}

protected void delete(Object obj) {
 getHibernateTemplate().delete(obj);
}
...
}
都透過Sprgin提供的HibernateTemplate幫我們作掉那些重複的開、關session, transaction動作。

以下擷取自Spring原始碼
package org.springframework.orm.hibernate3;
...
public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {
...
public Object execute(HibernateCallback action) throws DataAccessException {
   Session session = (!this.allowCreate ?
       SessionFactoryUtils.getSession(getSessionFactory(),false) :
       SessionFactoryUtils.getSession(getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()));
   boolean existingTransaction =
     TransactionSynchronizationManager.hasResource(getSessionFactory());
   if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {
       session.setFlushMode(FlushMode.NEVER);
   }
   try {
       Object result = action.doInHibernate(session);
       flushIfNecessary(session, existingTransaction);
       return result;
   }
   catch (HibernateException ex) {
       throw convertHibernateAccessException(ex);
   }
   catch (SQLException ex) {
       throw convertJdbcAccessException(ex);
   }
   catch (RuntimeException ex) {
       // callback code threw application exception
       throw ex;
   }
   finally {
       SessionFactoryUtils.closeSessionIfNecessary(session, getSessionFactory());
   }
}
...
}

在execute()最後的finally中,session會被關掉。因此從DAO層的method執行結束後,session就被關掉,因此如果在JSP頁面中存取到某些one-to-many (或其他lazy=true的關聯物件時)就會產生exception:
failed to lazily initialize a collection of ...
因為無法再透過session去抓資料。

通常此時會採用Spring提供的OpenSessionInViewFilter來解決,這個filter會在發出request的時候開啟session直到request結束,因此JSP頁面上仍可以透過這個session來取得因為lazy initialization未取得的資料。

但設定上去後,如果你採用Hibernate 3則會產生另一個問題。
當你呼叫DAO的SaveOrUpdate()資料並沒有存進資料庫,只會顯示一堆select 敘述:
Hibernate: select employee_.EMPLOYEE_ID, employee_.DEPARTMENT_NAME as DEPARTMENT2_0_, employee_.DEPARTMENT_NO as DEPARTMENT3_0_, employee_.INAUGURATION_DATE as INAUGURA4_0_, employee_.DISCHARGE_DATE as DISCHARGE5_0_, employee_.PROFESSIONAL_TITLE as PROFESSI6_0_, employee_.JOB_LEVEL as JOB7_0_, employee_.DIRECT_SUPERVISOR as DIRECT8_0_, employee_.PERSONAL_PHOTO as PERSONAL9_0_, employee_.CHINESE_NAME as CHINESE10_0_, ... from EMPLOYEES employee_ where employee_.EMPLOYEE_ID=?


並且產生以下Exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
/**
 * Get a Session for the SessionFactory that this filter uses.
 * Note that this just applies in single session mode!
 * The default implementation delegates to the
 * SessionFactoryUtils.getSession method and
 * sets the Session's flush mode to "NEVER".
 * 
Can be overridden in subclasses for creating a Session with a * custom entity interceptor or JDBC exception translator. * @param sessionFactory the SessionFactory that this filter uses * @return the Session to use * @throws DataAccessResourceFailureException if the Session could not be created * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean) * @see org.hibernate.FlushMode#NEVER */ protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException { Session session = SessionFactoryUtils.getSession(sessionFactory, true); FlushMode flushMode = getFlushMode(); if (flushMode != null) { session.setFlushMode(flushMode); } return session; }









reference:
1.使用語法高亮度http://sjcywg.blogspot.com/2008/02/syntax-highlighting.html
2.相關問題簡述Lazy Initialization and the DAO pattern with Hibernate and Spring

留言

這個網誌中的熱門文章

iframe DOM 被移動造成重新載入

如果用 javascript 去搬動 iframe DOM 的位置, 瀏覽器會將其內容重新載入,這是現有 HTML 規格 > When an iframe element is inserted into a document that has a browsing context, the user agent must create a nested browsing context, and then process the iframe attributes for the "first time". 範例: http://jsfiddle.net/pZ23B/ 測試結果: * Safari 3.1 / Win: reload * Opera 9.5 / Win: reload * IE10: reload * IE7 / IE8: not reload (部份摘自 https://bugzilla.mozilla.org/show_bug.cgi?id=254144 ) 參考: * https://bugzilla.mozilla.org/show_bug.cgi?id=254144

ZK 與響應式網頁設計(RWD)

響應式網頁設計 (RWD) 就現今而言應該是許多網站必備的條件之一,ZK 框架可以幫你從一個抽象層次較高的角度滿足 RWD,不需要了解 CSS Media Query 的細節。 元件層級 元件層級的 RWD 是由元件內部自動完成的。每個 ZK 元件都能區分瀏覽器的種類,因此會自動依據桌上型或行動裝置瀏覽器來自動調整元件的外觀,不需開發者介入,例如 datebox 在桌面瀏覽器會跳出小月曆: 在手機瀏覽器就會自動變成下方滾動條: 頁面層級 頁面層級的 RWD 因為牽涉到版面設計,因此無法靠元件自動完成,因為畢竟每個頁面需要的版面設計皆不相同,這通常不是一個技術性的決定,而是政策性或設計性上的決定,因此 ZK 提供一些工具讓你實現 RWD。以下根據你想要達成的效果,概述你可用的工具。 寬度、版面自動調整 如果你想要做出的效果是,畫面內各區域自動調整寬度,較窄時調整位置,我非常推薦搭配 Bootstrap Grid 與 ZK 一同使用,因為 易於整合 。使用 Grid system 只要在 div 套上 CSS class,如 col-md-4 ,因此跟 ZK 整合很容易,不需要特別的設定與程式。 元件變換 如果你希望畫面寬、窄時,各用不同的元件來顯示,例如側邊欄在畫面變窄時消失並換成小的選單圖示,這時元件本身當然無法得知要做這樣的轉換,你可以透過 onClientInfo 事件 或 @MatchMedia 來偵測畫面寬度變化,並針對該變化調整畫面元件。 更多細節可以參考以下資源: Use Bootstrap with ZK: Responsive Admin Template 範例專案 線上展示