跳到主要內容

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

留言

這個網誌中的熱門文章

JavaScript 關掉瀏覽器頁面

如果你直接呼叫 window.close() 來關掉目前的頁面的話,你應該會在 console 看到以下訊息:
Scripts may close only the windows that were opened by it.
( 我在 Chrome, Firefox, IE 11 都試過)
主因就是你並沒有用 JavaScript 開啟這個頁面,所以也不能用 JavaScript 關掉它。這是 HTML window.close() 的規格規定,各家瀏覽器應該都遵循。

隨著瀏覽器安全性增加,以下方法已經不適用了,可參考這個討論


有個變通的辦法就是:
window.open(location, '_self').close();


Code Complete, 2ed (中譯:軟體建構之道) 讀後心得

Code Complete, 2ed (中譯:軟體建構之道)讀後心得

雖然以前在學校有修過軟體工程,但是說實在現在我對建構軟體的整個概念都來自於兩本書:「人月神話」(The Mythical Man-Month)、Code Complete[1]。我畢業後第一份工作是軟體研發工程師,我們公司所開發的系統大約有近百位工程師共同維護,當然各有依功能與專業知識將系統劃分不同的區 塊來維護,在面對大型的系統與這麼多人寫程式是以前在學校沒有遇過的,因此也開始思考怎樣才是好的軟體開發方式。從大學好友那探聽到了Code Complete這本書,花了上千元買了英文版,(現在中文版已出,簡體版聽說只要三分之一的價錢)認真讀了幾篇之後,真是如獲至寶,於是自己讀過後,還跟同事組成讀書會一同研討這本書。
作者Steve McConnell (個人Blog http://www.stevemcconnell.com/)著作不算量大,但是我看過的幾本質量都很好,印象中他曾經在微軟工作過,後來成立一家軟體顧問公司Construx Software
這本書(書本網站 http://cc2e.com/Default.aspx)的討論主題很廣泛,但是並不空泛,從最小的、最基本的如何寫出好的程式, 例如函式(routine)、註解 (comment)、變數的命名等,也談整個軟體建構的過程除錯、測試,最後也談到軟體工程師個人能力與技術生涯的發展,算是各方面都有提及。但是我比較 建議有半年以上的實際程式經驗來讀這本書比較適合,我自認為如果我在學生時代接觸本書,可能沒有什麼太大的感觸,因為面對的都是小格局、人數少的小系統, 書中所探討的原則與例子,很多時候沒有團隊開發或大型系統的經驗,很難體會。但稍有經驗的人,看完之後保證在程式撰寫與軟體開發方面的觀念會有很大的提 昇。
Table of ContentLaying the Foundation
1 Welcome to Software Construction
2 Metaphors for a Richer Understanding of Software Development
3 Measure Twice, Cut Once: Upstream Prerequisites
4 Key Construction DecisionsCr…

AJAX 網頁卡住?可能是超過瀏覽器同時連線數上限 (max HTTP connection)

若你的程式中使用如 comet 這種 server push 技術的話,就會產生一個不中斷的 HTTP 連線,也就是發出的 AJAX request 不會立即收到伺服器的回覆,一般來說並不會造成問題,但若是使用者開啟多個頁面 (tab) 的話,就會產生多個不中斷連線,而各個瀏覽器對於同一個網站所允許的同時連線數的上限一般不大,多為 6 的左右,請參考 Connections per hostname一旦達到連線數上限,之後所發出的 AJAX request 都會卡住而不會送出。從瀏覽器的 developer tool 就可以觀察到,你會發現不會顯示任何 request 相關的細節。更詳細的套論可以參考相關文章: Maximum concurrent connections to the same domain for browsers這個跟 Firefox 的 network.http.max-connections-per-server 不同,這是指 Firefox 本身總共能發出的 HTTP 連線數總量,也就是所有 tab 加起來的數量。