在沒有使用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。
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,若設爲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的狀況:
-
- public final void setSessionFactory(SessionFactory sessionFactory) {
- this.hibernateTemplate = new HibernateTemplate(sessionFactory);
- }
- rotected final HibernateTemplate getHibernateTemplate() {
- return hibernateTemplate;
-
咱們的DAO將使用這個template進行操做.
-
- public abstract class BaseHibernateObjectDao
- extends HibernateDaoSupportimplements 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);
- eturn null;
- }
- });
- }
- }
而HibernateTemplate試圖每次在execute以前去得到Session,執行完就力爭關閉Session
-
- 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);
- }
- finally {
- SessionFactoryUtils.closeSessionIfNecessary(
- session, getSessionFactory());
- }
- }
而這個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保護的方法有寫權限,沒受保護的則沒有。
可能的解決方式有:
一、將singleSession設爲false,這樣只要改web.xml,缺點是Hibernate Session的Instance可能會大增,使用的JDBC Connection量也會大增,若是Connection Pool的maxPoolSize設得過小,很容易就出問題。
二、在控制器中自行管理Session的FlushMode,麻煩的是每一個有Modify的Method都要多幾行程式。
session.setFlushMode(FlushMode.AUTO);
session.update(user);
session.flush();
三、Extend OpenSessionInViewFilter,Override protected Session getSession(SessionFactory sessionFactory),將FlushMode直接改成Auto。
四、讓方法受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_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory
若是你什麼都不配置,默認狀況下使用JDBCTransaction,若是你配置爲:
引用
hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory
將使用JTATransaction,無論你準備讓Hibernate使用JDBCTransaction,仍是JTATransaction,個人忠告就是
什麼都不配,將讓它保持默認狀態,以下:
引用
#hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory
在下面的分析中我會給出緣由。
1、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的話,數據庫根本就沒有反應。
2、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的代碼片段那樣使用才行。
總結:
一、在JDBC上使用Hibernate 必須寫上Hibernate Transaction代碼,不然數據庫沒有反應。此時Hibernate的Transaction就是Connection.commit而已;
二、在JTA上使用Hibernate 寫JTA的Transaction代碼,不要寫Hibernate的Transaction代碼,不然程序會報錯;
三、在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 不清理不清理清javascript
--------------------------------------------------------------------------------------------java
<!-- Hibernate Open Session In View filter-->
<filter>
<filter-name>hibernateOpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>flushMode</param-name>
<param-value>AUTO</param-value>
</init-param>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>hibernateOpenSessionInViewFilter</filter-name>
<url-pattern>*.do</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>web