在沒有使用 Spring 提供的 Open Session In View 情況下,因需要在 service(or Dao) 層里把 session 關(guān)閉,所以 lazy loading 為 true 的話,要在應(yīng)用層內(nèi)把關(guān)系集合都初始化,如 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 狀態(tài),使 session 在 request 的整個期間都可以使用,如在 View 層里 PO 也可以 lazy loading 數(shù)據(jù),如 ${ company.employees } 。當 View 層邏輯完成后,才會通過 Filter 的 doFilter 方法或 Interceptor 的 postHandle 方法自動關(guān)閉 session 。
OpenSessionInViewInterceptor配置
<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>
OpenSessionInViewFilter配置
<web-app> ... <filter> <filter-name>hibernateFilter</filter-name> <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class> <!-- singleSession默認為true,若設(shè)為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);
}
??????????關(guān)于綁定session的方式,通過看spring里TransactionSynchronizationManager的實現(xiàn),發(fā)現(xiàn):它維護一個java.lang.ThreadLocal類型的resources,resources負責持有線程局部變量,這里resources持有的是一個HashMap,通過TransactionSynchronizationManager.bindResource()方法在map里綁定和線程相關(guān)的所有變量到他們的標識上,包括如上所述的綁定在sessionFactory上的線程局部session。sessionHolder只不過是存放可以hold一個session并可以和transtaction同步的容器。可以看到OpenSessionInViewFilter在getSession的時候,會把獲取回來的session的flush mode 設(shè)為FlushMode.NEVER。然后把該sessionFactory綁定到TransactionSynchronizationManager,使request的整個過程都使用同一個session,在請求過后再接除該sessionFactory的綁定,最后closeSessionIfNecessary根據(jù)該session是否已和transaction綁定來決定是否關(guān)閉session。綁定以后,就可以防止每次不會新開一個Session呢?看看HibernateDaoSupport的情況:
1. public final void setSessionFactory(SessionFactory sessionFactory) { 2. this.hibernateTemplate = new HibernateTemplate(sessionFactory); 3. } 4. protectedfinal HibernateTemplate getHibernateTemplate() { 5. return hibernateTemplate; 6. }
??????? 我們的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,執(zhí)行完就力爭關(guān)閉Session
1. public Object execute(HibernateCallback action) throws DataAccessException {??2. ???Session session = (!this.allowCreate ???3. ?????????SessionFactoryUtils.getSession(getSessionFactory(), 4. false) :??5. ?????????SessionFactoryUtils.getSession(getSessionFactory(), 6. getEntityInterceptor(), 7. getJdbcExceptionTranslator()));??8. ????boolean existingTransaction = 9. TransactionSynchronizationManager.hasResource(getSessionFactory());??10.????if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {??11.??????????session.setFlushMode(FlushMode.NEVER);??12.????}??13.????try {??14.??????????Object result = action.doInHibernate(session);??15.??????????flushIfNecessary(session, existingTransaction);??16.??????????return result;??17.????}??18.????catch (HibernateException ex) {??19.??????????throw convertHibernateAccessException(ex);??20.????}??21.????finally {??22.??????????SessionFactoryUtils.closeSessionIfNecessary( 23. session, getSessionFactory());??24.????} 25. }
????? 而這個SessionFactoryUtils能否得到當前的session以及closeSessionIfNecessary是否真正關(guān)閉session,端取決于這個session是否用sessionHolder和這個sessionFactory在我們最開始提到的TransactionSynchronizationManager綁定。
???? publicstaticvoidcloseSessionIfNecessary(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 發(fā)現(xiàn)自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有寫權(quán)限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER轉(zhuǎn)為Flush.AUTO,擁有insert,update,delete操作權(quán)限,如果沒有transaction,并且沒有另外人為地設(shè)flush model的話,則doFilter的整個過程都是Flush.NEVER。所以受transaction保護的方法有寫權(quán)限,沒受保護的則沒有。
- 可能的解決方式有:
1、將singleSession設(shè)為false,這樣只要改web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,如果Connection Pool的maxPoolSize設(shè)得太小,很容易就出問題。
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的事務(wù)控制。這就是常使用的方法:
采用spring的事務(wù)聲明,使方法受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開頭的方法擁有可寫的事務(wù),如果當前有某個方法,如命名為importExcel(),則因沒有transaction而沒有寫權(quán)限,這時若方法內(nèi)有insert,update,delete操作的話,則需要手動設(shè)置flush model為Flush.AUTO,如
session.setFlushMode(FlushMode.AUTO); session.save(user); session.flush();
?????盡管Open Session In View看起來還不錯,其實副作用不少??椿厣厦鍻penSessionInViewFilter的doFilterInternal方法代碼,這個方法實際上是被父類的doFilter調(diào)用的,因此,我們可以大約了解的OpenSessionInViewFilter調(diào)用流程: request(請求)->open session并開始transaction->controller->View(Jsp)->結(jié)束transaction并close session.
???? 一切看起來很正確,尤其是在本地開發(fā)測試的時候沒出現(xiàn)問題,但試想下如果流程中的某一步被阻塞的話,那在這期間connection就一直被占用而不釋放。最有可能被阻塞的就是在寫Jsp這步,一方面可能是頁面內(nèi)容大,response.write的時間長,另一方面可能是網(wǎng)速慢,服務(wù)器與用戶間傳輸時間久。當大量這樣的情況出現(xiàn)時,就有連接池連接不足,造成頁面假死現(xiàn)象。
Open Session In View是個雙刃劍,放在公網(wǎng)上內(nèi)容多流量大的網(wǎng)站請慎用。
?
另外:這樣會產(chǎn)生一點危險性,畢竟把數(shù)據(jù)庫訪問的環(huán)境放到了表現(xiàn)層。(:用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,我的忠告就是什么都不配,將讓它保持默認狀態(tài),如下:
#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的時候?qū)嶋H上就是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都在干了些什么?我現(xiàn)在把用Hibernate寫的例子翻譯成JDBC,大家就一目了然了:
Connection conn = ...;????????????? ?<--- session = sf.openSession();
conn.setAutoCommit(false);?? <--- tx = session.beginTransactioin();
... <--- ...
conn.commit();?????????????????????????? <--- tx.commit(); (對應(yīng)左邊的兩句)
conn.setAutoCommit(true);
conn.close();????????????????????????????? <--- session.close();
看明白了吧,Hibernate的JDBCTransaction根本就是conn.commit而已,根本毫無神秘可言,只不過在Hibernate中,Session打開的時候,就會自動conn.setAutoCommit(false),不像一般的JDBC,默認都是true,所以你最后不寫commit也沒有關(guān)系,由于Hibernate已經(jīng)把AutoCommit給關(guān)掉了,所以用Hibernate的時候,你在程序中不寫Transaction的話,數(shù)據(jù)庫根本就沒有反應(yīng)。?
?二、JTATransaction
如果你在EJB中使用Hibernate,或者準備用JTA來管理跨Session的長事務(wù),那么就需要使用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代碼統(tǒng)統(tǒng)都不要寫了,直接在EJB的部署描述符上配置某某方法是否使用事務(wù)就可以了。
現(xiàn)在我們來分析一下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的控制稍微復(fù)雜,不過仍然可以很清楚的看出來Hibernate是如何封裝JTA的Transaction代碼的。
但是你現(xiàn)在是否看到了什么問題? 仔細想一下,Hibernate Transaction是從Session中獲得的,tx = session.beginTransaction(),最后要先提交tx,然后再session.close,這完全符合JDBC的Transaction的操作順序,但是這個順序是和JTA的Transactioin操作順序徹底矛盾的!??! JTA是先啟動Transaction,然后啟動Session,關(guān)閉Session,最后提交Transaction,因此當你使用JTA的Transaction的時候,那么就千萬不要使用Hibernate的Transaction,而是應(yīng)該像我上面的JTA的代碼片斷那樣使用才行。
總結(jié):
1、在JDBC上使用Hibernate
必須寫上Hibernate Transaction代碼,否則數(shù)據(jù)庫沒有反應(yīng)。此時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
?
?
?
?
關(guān)于session:
1.? servlet的session機制基于cookies,關(guān)閉瀏覽器的cookies則session失效即不能用網(wǎng)站的登錄功能。
2.? Hibernate Session.
????? 1>. session 清理緩存時,按照以下順序執(zhí)行SQL語句:
??????????? session.save()的實體insert?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
??????????? 實體的update
??????????? 對 集合的delete
??????????? 集合元素的delete,update,insert
??????????? 集合的insert
????????????session.delete()的先后,執(zhí)行實體的delete
?????? 2>. 默認時,session在以下時間點清理緩存:
????????????? net.sf.hibernate.Transaction.commit():先清理緩存,再向數(shù)據(jù)庫提交事務(wù)??????????????????????????????????????????????????????????
????????????? Session.find()或iterate()時,若緩存中持久化對象的屬性發(fā)生了變化,就會先清緩存,以保證查詢結(jié)果正確
?
?????? 3>.? Session的commit()和flush()的區(qū)別:flush()只執(zhí)行SQL語句,不提交事務(wù);commit()先調(diào)用flush(),再提交事務(wù)
?????? 4>.? Session.setFlushMode()用于設(shè)定清理緩存的時間點。
清理緩存的模式 | Session的查詢方法 | Session.commit() | Session.flush() |
FlushMode.AUTO | 清理 | 清理 | 清理 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 |
FlushMode.NEVER | 不清理 | 不清理 | 清理 |
???????????
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=797399