在沒有使用Spring提供的Open Session In View情況下,因需要在service(or Dao)層裏把session關閉,所以lazy loading 爲true的話,要在應用層內把關係集合都初始化,如 company.getEmployees(),否則Hibernate拋session already closed Exception; Open Session In View提供了一種簡便的方法,較好地解決了lazy loading問題.
它有兩種配置方式OpenSessionInViewInterceptor和OpenSessionInViewFilter(具體參看SpringSide),功能相同,只是一個在web.xml配置,另一個在application.xml配置而已。
Open Session In View在request把session綁定到當前thread期間一直保持hibernate session在open狀態,使session在request的整個期間都可以使用,如在View層裏PO也可以lazy loading數據,如 ${ company.employees }。當View 層邏輯完成後,纔會通過Filter的doFilter方法或Interceptor的postHandle方法自動關閉session。
- <beans>
- <bean name="openSessionInViewInterceptor"
- class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
- <property name="sessionFactory">
- <ref bean="sessionFactory"/>
- </property>
- </bean>
- <bean id="urlMapping"
- class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="interceptors">
- <list>
- <ref bean="openSessionInViewInterceptor"/>
- </list>
- </property>
- <property name="mappings">
- ...
- </property>
- </bean>
- ...
- </beans>
- <web-app>
- ...
- <filter>
- <filter-name>hibernateFilter</filter-name>
- <filter-class>
- org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
- </filter-class>
- <!-- singleSession默認爲true,若設爲false則等於沒用OpenSessionInView -->
- <init-param>
- <param-name>singleSession</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- ...
- <filter-mapping>
- <filter-name>hibernateFilter</filter-name>
- <url-pattern>*.do</url-pattern>
- </filter-mapping>
- ...
- </web-app>
很多人在使用OpenSessionInView過程中提及一個錯誤:
- org.springframework.dao.InvalidDataAccessApiUsageException: Write operations
- are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into
- FlushMode.AUTO or remove 'readOnly' marker from transaction definition
看看OpenSessionInViewFilter裏的幾個方法
- protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,FilterChain filterChain)
throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory();
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(
sessionFactory, new SessionHolder(session));
try {
filterChain.doFilter(request, response);
}
finally {
TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
closeSession(session, sessionFactory);
}
}
- protected Session getSession(SessionFactory sessionFactory)
throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
return session;
}
protected void closeSession(Session session, SessionFactory sessionFactory)
throws CleanupFailureDataAccessException {
SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
}
關於綁定session的方式,通過看spring裏TransactionSynchronizationManager的實現,發現:它維護一個java.lang.ThreadLocal類型的resources,resources負責持有線程局部變量,這裏resources持有的是一個HashMap,通過TransactionSynchronizationManager.bindResource()方法在map裏綁定和線程相關的所有變量到他們的標識上,包括如上所述的綁定在sessionFactory上的線程局部session。sessionHolder只不過是存放可以hold一個session並可以和transtaction同步的容器。可以看到OpenSessionInViewFilter在getSession的時候,會把獲取回來的session的flush mode 設爲FlushMode.NEVER。然後把該sessionFactory綁定到TransactionSynchronizationManager,使request的整個過程都使用同一個session,在請求過後再接除該sessionFactory的綁定,最後closeSessionIfNecessary根據該session是否已和transaction綁定來決定是否關閉session。綁定以後,就可以防止每次不會新開一個Session呢?看看HibernateDaoSupport的情況:
我們的DAO將使用這個template進行操作.
public abstract class BaseHibernateObjectDao extends HibernateDaoSupport
implements BaseObjectDao {
protected BaseEntityObject getByClassId(final long id) {
BaseEntityObject obj =(BaseEntityObject) getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
return session.get(getPersistentClass(),new Long(id));
}
});
return obj;
}
public void save(BaseEntityObject entity) {
getHibernateTemplate().saveOrUpdate(entity);
}
public void remove(BaseEntityObject entity) {
try {
getHibernateTemplate().delete(entity);
} catch (Exception e) {
throw new FlexEnterpriseDataAccessException(e);
}
}
public void refresh(final BaseEntityObject entity) {
getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
session.refresh(entity);
return null;
}
});
}
public void replicate(final Object entity) {
getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session)throws HibernateException {
session.replicate(entity,ReplicationMode.OVERWRITE);
return null;
}
});
}
}
而HibernateTemplate試圖每次在execute之前去獲得Session,執行完就力爭關閉Session
而這個SessionFactoryUtils能否得到當前的session以及closeSessionIfNecessary是否真正關閉session,端取決於這個session是否用sessionHolder和這個sessionFactory在我們最開始提到的TransactionSynchronizationManager綁定。
public static void closeSessionIfNecessary(Session session, SessionFactory sessionFactory)
- throws CleanupFailureDataAccessException {
- if (session == null ||
TransactionSynchronizationManager.hasResource(sessionFactory)) { - return;
- }
- logger.debug("Closing Hibernate session");
- try {
- session.close();
- }
- catch (JDBCException ex) {
- // SQLException underneath
- throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex.getSQLException());
- }
- catch (HibernateException ex) {
- throw new CleanupFailureDataAccessException("Could not close Hibernate session", ex);
- }
- }
在這個過程中,若HibernateTemplate 發現自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有寫權限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉爲Flush.AUTO,擁有insert,update,delete操作權限,如果沒有transaction,並且沒有另外人爲地設flush model的話,則doFilter的整個過程都是Flush.NEVER。所以受transaction保護的方法有寫權限,沒受保護的則沒有。
- 可能的解決方式有:
1、將singleSession設爲false,這樣只要改web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,如果Connection Pool的maxPoolSize設得太小,很容易就出問題。
2、在控制器中自行管理Session的FlushMode,麻煩的是每個有Modify的Method都要多幾行程式。
session.setFlushMode(FlushMode.AUTO);
session.update(user);
session.flush();
3、Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),將FlushMode直接改爲Auto。
4、讓方法受Spring的事務控制。這就是常使用的方法:
採用spring的事務聲明,使方法受transaction控制
- <bean id="baseTransaction"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="proxyTargetClass" value="true"/>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean> - <bean id="userService" parent="baseTransaction">
<property name="target">
<bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>
</property>
</bean>
對於上例,則以save,add,update,remove開頭的方法擁有可寫的事務,如果當前有某個方法,如命名爲importExcel(),則因沒有transaction而沒有寫權限,這時若方法內有insert,update,delete操作的話,則需要手動設置flush model爲Flush.AUTO,如
- session.setFlushMode(FlushMode.AUTO);
- session.save(user);
- session.flush();
儘管Open Session In View看起來還不錯,其實副作用不少。看回上面OpenSessionInViewFilter的doFilterInternal方法代碼,這個方法實際上是被父類的doFilter調用的,因此,我們可以大約瞭解的OpenSessionInViewFilter調用流程: request(請求)->open session並開始transaction->controller->View(Jsp)->結束transaction並close session.
一切看起來很正確,尤其是在本地開發測試的時候沒出現問題,但試想下如果流程中的某一步被阻塞的話,那在這期間connection就一直被佔用而不釋放。最有可能被阻塞的就是在寫Jsp這步,一方面可能是頁面內容大,response.write的時間長,另一方面可能是網速慢,服務器與用戶間傳輸時間久。當大量這樣的情況出現時,就有連接池連接不足,造成頁面假死現象。
Open Session In View是個雙刃劍,放在公網上內容多流量大的網站請慎用。
另外:這樣會產生一點危險性,畢竟把數據庫訪問的環境放到了表現層。(:用VO)
Hibernate是對JDBC的輕量級對象封裝,Hibernate本身是不具備Transaction處理功能的,Hibernate的Transaction實際上是底層的JDBC Transaction的封裝,或者是JTA Transaction的封裝,下面我們詳細的分析:
Hibernate可以配置爲JDBCTransaction或者是JTATransaction,這取決於你在hibernate.properties中的配置:
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory
如果你什麼都不配置,默認情況下使用JDBCTransaction,如果你配置爲:
hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
將使用JTATransaction,不管你準備讓Hibernate使用JDBCTransaction,還是JTATransaction,我的忠告就是什麼都不配,將讓它保持默認狀態,如下:
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JTATransactionFactory
#hibernate.transaction.factory_class
net.sf.hibernate.transaction.JDBCTransactionFactory
在下面的分析中我會給出原因。
一、JDBC Transaction
看看使用JDBC Transaction的時候我們的代碼例子:
Session session = sf.openSession();
Transaction tx = session.beginTransactioin();
...
session.flush();
tx.commit();
session.close();
這是默認的情況,當你在代碼中使用Hibernate的Transaction的時候實際上就是JDBCTransaction。那麼JDBCTransaction究竟是什麼東西呢?來看看源代碼就清楚了:
Hibernate2.0.3源代碼中的類
net.sf.hibernate.transaction.JDBCTransaction:
public void begin() throws HibernateException {
...
if (toggleAutoCommit) session.connection().setAutoCommit(false);
...
}
這是啓動Transaction的方法,看到 connection().setAutoCommit(false) 了嗎?是不是很熟悉?
再來看
public void commit() throws HibernateException {
...
try {
if ( session.getFlushMode()!=FlushMode.NEVER ) session.flush();
try {
session.connection().commit();
committed = true;
}
...
toggleAutoCommit();
}
這是提交方法,看到connection().commit() 了嗎?下面就不用我多說了,這個類代碼非常簡單易懂,通過閱讀使我們明白Hibernate的Transaction都在幹了些什麼?我現在把用Hibernate寫的例子翻譯成JDBC,大家就一目瞭然了:
Connection conn = ...; <--- session = sf.openSession();
conn.setAutoCommit(false); <--- tx = session.beginTransactioin();
... <--- ...
conn.commit(); <--- tx.commit(); (對應左邊的兩句)
conn.setAutoCommit(true);
conn.close(); <--- session.close();
看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神祕可言,只不過在Hibernate中,Session打開的時候,就會自動conn.setAutoCommit(false),不像一般的JDBC,默認都是true,所以你最後不寫commit也沒有關係,由於Hibernate已經把AutoCommit給關掉了,所以用Hibernate的時候,你在程序中不寫Transaction的話,數據庫根本就沒有反應。
二、JTATransaction
如果你在EJB中使用Hibernate,或者準備用JTA來管理跨Session的長事務,那麼就需要使用JTATransaction,先看一個例子:
javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction");
Session s1 = sf.openSession();
...
s1.flush();
s1.close();
...
Session s2 = sf.openSession();
...
s2.flush();
s2.close();
tx.commit();
這是標準的使用JTA的代碼片斷,Transaction是跨Session的,它的生命週期比Session要長。如果你在EJB中使用Hibernate,那麼是最簡單不過的了,你什麼Transaction代碼統統都不要寫了,直接在EJB的部署描述符上配置某某方法是否使用事務就可以了。
現在我們來分析一下JTATransaction的源代碼, net.sf.hibernate.transaction.JTATransaction:
public void begin(InitialContext context, ...
...
ut = (UserTransaction) context.lookup(utName);
...
看清楚了嗎? 和我上面寫的代碼 tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); 是不是完全一樣?
public void commit() ...
...
if (newTransaction) ut.commit();
...
JTATransaction的控制稍微複雜,不過仍然可以很清楚的看出來Hibernate是如何封裝JTA的Transaction代碼的。
但是你現在是否看到了什麼問題? 仔細想一下,Hibernate Transaction是從Session中獲得的,tx = session.beginTransaction(),最後要先提交tx,然後再session.close,這完全符合JDBC的Transaction的操作順序,但是這個順序是和JTA的Transactioin操作順序徹底矛盾的!!! JTA是先啓動Transaction,然後啓動Session,關閉Session,最後提交Transaction,因此當你使用JTA的Transaction的時候,那麼就千萬不要使用Hibernate的Transaction,而是應該像我上面的JTA的代碼片斷那樣使用才行。
總結:
1、在JDBC上使用Hibernate
必須寫上Hibernate Transaction代碼,否則數據庫沒有反應。此時Hibernate的Transaction就是Connection.commit而已
2、在JTA上使用Hibernate
寫JTA的Transaction代碼,不要寫Hibernate的Transaction代碼,否則程序會報錯
3、在EJB上使用Hibernate
什麼Transactioin代碼都不要寫,在EJB的部署描述符裏面配置
|---CMT(Container Managed Transaction)
|
|---BMT(Bean Managed Transaction)
|
|----JDBC Transaction
|
|----JTA Transaction
關於session:
1. servlet的session機制基於cookies,關閉瀏覽器的cookies則session失效即不能用網站的登錄功能。
2. Hibernate Session.
1>. session 清理緩存時,按照以下順序執行SQL語句:
session.save()的實體insert
實體的update
對集合的delete
集合元素的delete,update,insert
集合的insert
session.delete()的先後,執行實體的delete
2>. 默認時,session在以下時間點清理緩存:
net.sf.hibernate.Transaction.commit():先清理緩存,再向數據庫提交事務
Session.find()或iterate()時,若緩存中持久化對象的屬性發生了變化,就會先清緩存,以保證查詢結果正確
3>. Session的commit()和flush()的區別:flush()只執行SQL語句,不提交事務;commit()先調用flush(),再提交事務
4>. Session.setFlushMode()用於設定清理緩存的時間點。
清理緩存的模式 | Session的查詢方法 | Session.commit() | Session.flush() |
FlushMode.AUTO | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FlushMode.NEVER | 不清理 | 不清理 | 清理 |