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.
 at

org.springframework.orm.hibernate3.HibernateTemplate.checkWriteOper

ationAllowed(HibernateTemplate.java:1095)

解决方法:就是把删除,修改,添加放在transaction中。可以用spring的

aop方便解决。
参考:http://calvin.blog.javascud.org/post/46.htm
http://www.javaeye.com/post/310183
在没有使用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配置而已。

    可以看到OpenSessionInViewFilter在getSession的时候,会把获取回来

的session的flush mode 设为FlushMode.NEVER。然后把该sessionFactory绑

定到TransactionSynchronizationManager,使request的整个过程都使用同

一个session,在请求过后再接除该sessionFactory的绑定,最后

closeSessionIfNecessary根据该session是否已和transaction绑定来决定是

否关闭session。在这个过程中,若HibernateTemplate 发现自当前session

有不是readOnly的transaction,就会获取到FlushMode.AUTO Session,使方

法拥有写权限。
    也即是,如果有不是readOnly的transaction就可以由Flush.NEVER转为

Flush.AUTO,拥有insert,update,delete操作权限,如果没有transaction,

并且没有另外人为地设flush model的话,则doFilter的整个过程都是

Flush.NEVER。所以受transaction保护的方法有写权限,没受保护的则没有


摘抄如下:
Open Session In View探讨
作者 johnsonchen 目标文章 项目日志 - 2006-05-12


    在没有使用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.OpenSessionInView

Interceptor">     <property name="sessionFactory">      <ref

bean="sessionFactory"/>    </property>   </bean>   <bean

id="urlMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapp

ing">     <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);}      可以看到OpenSessionInViewFilter在getSession

的时候,会把获取回来的session的flush mode 设为FlushMode.NEVER。然后

把该sessionFactory绑定到TransactionSynchronizationManager,使

request的整个过程都使用同一个session,在请求过后再接除该

sessionFactory的绑定,最后closeSessionIfNecessary根据该session是否

已和transaction绑定来决定是否关闭session。在这个过程中,若

HibernateTemplate 发现自当前session有不是readOnly的transaction,就

会获取到FlushMode.AUTO Session,使方法拥有写权限。

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);    }  }     也即是,如果有不是

readOnly的transaction就可以由Flush.NEVER转为Flush.AUTO,拥有

insert,update,delete操作权限,如果没有transaction,并且没有另外人为

地设flush model的话,则doFilter的整个过程都是Flush.NEVER。所以受

transaction保护的方法有写权限,没受保护的则没有。

采用spring的事务声明,使方法受transaction控制  <bean

id="baseTransaction"

class="org.springframework.transaction.interceptor.TransactionProxy

FactoryBean"           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是个双刃剑,放在公网上内容多流量大的网站请慎用


另一中解决方案:
如果是用Spring 2.x,可以参考下面的配置:

Java代码
<!--设置事务管理  -->        
<bean id="transactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManag

er">    
  <property name="sessionFactory">    
   <ref local="sessionFactory" />    
  </property>    
 </bean>     
 
    <!-- Transactional advice -->  
    <tx:advice id="txAdvice" transaction-

manager="transactionManager">  
        <tx:attributes>  
            <!-- methods starting with 'save', 'update' or 'remove'

use the default transaction settings -->  
            <tx:method name="save*"/>  
            <tx:method name="update*"/>  
            <tx:method name="remove*"/>  
 
            <!-- other methods are set to read only -->  
            <tx:method name="*" read-only="true"/>  
        </tx:attributes>  
    </tx:advice>  
 
    <!-- ensure that the above transactional advice runs for any

execution  
    of an operation defined by specified interface -->  
    <aop:config>  
        <aop:pointcut id="daoOperation" 
            expression="execution(* com.DAO.BasicDAO.*(..))"/>  
        <aop:advisor advice-ref="txAdvice" pointcut-

ref="daoOperation"/>  
    </aop:config>  
 
<bean id="BasicDAO" class="com.DAO.BasicDAO">    
    <property name="sessionFactory">    
        <ref local="sessionFactory" />    
    </property>    
</bean>  
 
<bean name="/login" class="com.struts.LoginAction"

singleton="false">    
    <property name="basicDAO">    
        <ref bean="BasicDAO" />    
    </property>    
</bean> 

posted on 2009-05-27 10:46  sky100  阅读(1793)  评论(0编辑  收藏  举报