報錯: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.
搞開發的時候碰到這個問題,在網上搜了一下原因,歸納成以下幾個知識點,部分摘抄網絡原文:
1、延遲加載:
Hibernate允許關聯對象進行延遲加載,前提是必須保證延遲加載操作是在同一個Session范圍之內進行。如果Service層返回了一個已啟用延遲加載的領域對象給View層,當View層訪問那些需要延遲加載的數據時,由於加載領域對象的Session已經關閉,將導致延遲加載數據的訪問異常(org.hibernate.LazyInitializationException)。
2、OpenSessionInViewFilter:
總所周知,Java類或者方法命名以長著稱,OpenSessionInViewFilter也是,顧名思義,它能夠讓我們在View層保持Session繼續Open。Spring提供的OpenSessionInViewFilter用來把一個Hibernate Session和一次完整的請求過程對應的線程相綁定(整個request過程都是用同一個Session,在請求結束后再解除綁定),允許在事務提交之后延遲加載View層需要的對象。在綁定過程中,它將自動被Spring的事務管理器探測到,所以,OpenSessionInViewFilter 適用於Service層使用HibernateTransactionManager或JtaTransactionManager進行事務管理的環境,也可以用於非事務只讀的數據操作中。
“OpenSessionInViewFilter在getSession的時候,會把獲取回來的session的flush mode設為FlushMode.NEVER。然后把該sessionFactory綁定到 TransactionSynchronizationManager,使request的整個過程都使用同一個session,在請求過后再解除該sessionFactory的綁定,最后closeSessionIfNecessary根據該 session是否已和transaction綁定來決定是否關閉session。在這個過程中,若HibernateTemplate 發現自當前session有不是readOnly的transaction,就會獲取到FlushMode.AUTO Session,使方法擁有寫權限。”
3、解決辦法:
web.xml中配置OpenSessionInViewFilter初始參數:singleSession:true、flushMode:AUTO
<filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>flushMode</param-name> <param-value>AUTO</param-value> </init-param> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping>
或者在Spring配置文件中,將方法的read-only設置為false。我用的是注解,為圖方便,將方法上的@Transactional(readOnly = true)去掉即可。
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<aop:config>
<aop:pointcut id="bussinessService" expression="execution(* com.fan.service.base.*.*(..))" />
<aop:advisor pointcut-ref="bussinessService" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="false" propagation="NOT_SUPPORTED"/>
<tx:method name="find*" read-only="false" propagation="NOT_SUPPORTED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
4、備注:
“盡 管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是個雙刃劍,放在公網上內容多流量大的網站請慎用。”